下面,我們來系統的梳理關于 Vue 3 <script setup>
語法糖 的基本知識點:
一、<script setup>
核心概念
1.1 什么是 <script setup>
?
<script setup>
是 Vue 3 中 Composition API 的編譯時語法糖,它通過簡化組件聲明方式,顯著減少樣板代碼,提供更符合直覺的開發體驗。
1.2 設計目標與優勢
目標 | 實現方式 | 優勢 |
---|---|---|
減少樣板代碼 | 自動暴露頂層綁定 | 代碼更簡潔 |
提升開發體驗 | 更自然的響應式寫法 | 開發更高效 |
更好的類型支持 | 原生 TypeScript 集成 | 類型安全 |
編譯時優化 | 編譯階段處理 | 運行時更高效 |
邏輯組織 | 組合式 API 原生支持 | 關注點分離 |
1.3 與傳統語法對比
<!-- 選項式 API -->
<script>
export default {props: ['message'],data() {return { count: 0 }},methods: {increment() {this.count++}}
}
</script><!-- Composition API 標準 -->
<script>
import { ref } from 'vue'export default {props: ['message'],setup(props) {const count = ref(0)function increment() {count.value++}return { count, increment }}
}
</script><!-- <script setup> 語法糖 -->
<script setup>
import { ref } from 'vue'const props = defineProps(['message'])
const count = ref(0)function increment() {count.value++
}
</script>
二、核心語法與特性
2.1 基本結構
<script setup>
// 所有頂層綁定都自動暴露給模板
import { ref } from 'vue'// 響應式狀態
const count = ref(0)// 函數
function increment() {count.value++
}// 表達式
const double = computed(() => count.value * 2)
</script><template><button @click="increment">{{ count }} ({{ double }})</button>
</template>
2.2 編譯器轉換原理
輸入:
<script setup>
const msg = 'Hello!'
</script>
輸出:
<script>
export default {setup() {const msg = 'Hello!'return { msg } // 自動暴露所有頂層綁定}
}
</script>
2.3 自動暴露規則
- 所有
import
導入 - 頂層變量聲明 (
const
,let
,var
) - 頂層函數聲明
- 頂層
class
和interface
(TypeScript)
三、組件與指令使用
3.1 組件使用
<script setup>
// 1. 導入組件
import MyComponent from './MyComponent.vue'// 2. 自動注冊,無需components選項
</script><template><!-- 3. 直接在模板中使用 --><MyComponent />
</template>
3.2 動態組件
<script setup>
import { shallowRef } from 'vue'
import Home from './Home.vue'
import About from './About.vue'const currentComponent = shallowRef(Home)
</script><template><component :is="currentComponent" />
</template>
3.3 自定義指令
<script setup>
// 命名規范:vNameOfDirective
const vFocus = {mounted: el => el.focus()
}
</script><template><input v-focus />
</template>
四、Props與Emit處理
4.1 聲明Props
<script setup>
// 選項式聲明
const props = defineProps({title: String,count: {type: Number,required: true}
})// TypeScript接口聲明
interface Props {title?: stringcount: number
}const props = defineProps<Props>()
</script>
4.2 聲明Emit事件
<script setup>
// 數組形式
const emit = defineEmits(['update', 'delete'])// 對象形式(帶驗證)
const emit = defineEmits({update: (payload: { id: number }) => {return !!payload.id // 返回布爾值表示驗證結果}
})// TypeScript類型聲明
const emit = defineEmits<{(e: 'update', payload: { id: number }): void(e: 'delete', id: number): void
}>()
4.3 使用示例
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])function handleInput(e) {emit('update:modelValue', e.target.value)
}
</script><template><input :value="modelValue"@input="handleInput"/>
</template>
五、響應式與生命周期
5.1 響應式狀態管理
<script setup>
import { ref, reactive, computed } from 'vue'// 基本類型
const count = ref(0)// 對象類型
const state = reactive({items: [],loading: false
})// 計算屬性
const total = computed(() => state.items.length)// 偵聽器
watch(count, (newVal, oldVal) => {console.log(`Count changed: ${oldVal} → ${newVal}`)
})
</script>
5.2 生命周期鉤子
<script setup>
import { onMounted, onUnmounted } from 'vue'onMounted(() => {console.log('Component mounted')fetchData()
})onUnmounted(() => {console.log('Component unmounted')cleanup()
})
</script>
六、高級特性與模式
6.1 頂層await
<script setup>
const user = ref(null)
const posts = ref([])// 直接使用await - 自動編譯為async setup()
const userData = await fetchUser()
user.value = userData// 結合watchEffect
watchEffect(async () => {if (user.value) {posts.value = await fetchPosts(user.value.id)}
})
</script>
6.2 使用插槽與上下文
<script setup>
import { useSlots, useAttrs } from 'vue'const slots = useSlots()
const attrs = useAttrs()// 訪問默認插槽內容
const defaultSlotContent = slots.default?.()
</script>
6.3 暴露組件實例
<script setup>
import { ref } from 'vue'const inputRef = ref(null)// 暴露公共方法
function focusInput() {inputRef.value.focus()
}// 顯式暴露
defineExpose({focusInput
})
</script><template><input ref="inputRef">
</template>
七、TypeScript集成
7.1 類型安全Props
<script setup lang="ts">
interface Props {title: stringcount?: numberitems?: string[]
}const props = defineProps<Props>()
</script>
7.2 泛型組件
<script setup lang="ts" generic="T">
import { ref } from 'vue'const items = ref<T[]>([])
const selected = ref<T>()function addItem(item: T) {items.value.push(item)
}
</script>
7.3 類型標注Ref
<script setup lang="ts">
import { ref } from 'vue'interface User {id: numbername: string
}// 顯式類型標注
const user = ref<User | null>(null)// 初始化時推斷
const count = ref(0) // Ref<number>
</script>
八、最佳實踐與組織模式
8.1 代碼組織建議
<script setup>
// ----------------------------
// 1. 導入部分
// ----------------------------
import { ref, onMounted } from 'vue'
import MyComponent from './MyComponent.vue'// ----------------------------
// 2. 定義Props/Emits
// ----------------------------
const props = defineProps({ /* ... */ })
const emit = defineEmits({ /* ... */ })// ----------------------------
// 3. 響應式狀態
// ----------------------------
const count = ref(0)
const state = reactive({ /* ... */ })// ----------------------------
// 4. 計算屬性和偵聽器
// ----------------------------
const double = computed(() => count.value * 2)
watch(count, (val) => { /* ... */ })// ----------------------------
// 5. 函數聲明
// ----------------------------
function increment() { count.value++ }// ----------------------------
// 6. 生命周期鉤子
// ----------------------------
onMounted(() => { /* ... */ })// ----------------------------
// 7. 暴露API
// ----------------------------
defineExpose({ increment })
</script>
8.2 邏輯復用模式
<script setup>
// 使用自定義Hook
import { useCounter } from '@/composables/useCounter'const { count, increment } = useCounter(10)
</script>
8.3 性能優化
<script setup>
// 1. 使用shallowRef減少大型對象的響應式開銷
const bigData = shallowRef(largeDataset)// 2. 使用markRaw避免不必要的響應式轉換
const staticConfig = markRaw({ /* 大型配置對象 */ })// 3. 合理使用computed緩存計算結果
const filteredList = computed(() => bigData.value.filter(item => item.active)
)
</script>
九、常見問題與解決方案
9.1 響應式丟失問題
問題:解構Props導致響應式丟失
<script setup>
const { title } = defineProps(['title']) // 失去響應性
</script>
解決方案:
<script setup>
const props = defineProps(['title'])
// 使用toRefs解構
const { title } = toRefs(props)
</script>
9.2 命名沖突問題
問題:導入組件與局部變量同名
<script setup>
import Button from './Button.vue'
const Button = ref(null) // 命名沖突
</script>
解決方案:
<script setup>
import Button as BaseButton from './Button.vue'
const Button = ref(null) // 避免沖突
</script>
9.3 訪問上下文問題
問題:無法直接訪問 this
<script setup>
// 錯誤:this未定義
const router = this.$router
</script>
解決方案:
<script setup>
import { useRouter } from 'vue-router'const router = useRouter() // 使用Composition API
</script>