<script setup>
<script setup>
是在單檔案元件 (SFC) 中使用組合式 API 的編譯時語法糖。如果您同時使用 SFC 和組合式 API,則推薦使用此語法。它相較於普通的 <script>
語法提供了許多優勢
- 更簡潔的程式碼,更少的模板程式碼
- 使用純 TypeScript 宣告 props 和 emitted 事件的能力
- 更好的執行時效能(模板在相同的作用域內編譯為渲染函式,沒有中間代理)
- 更好的 IDE 型別推斷效能(語言伺服器從程式碼中提取型別的工作量更少)
基本語法
要啟用此語法,請將 setup
屬性新增到 <script>
塊中
vue
<script setup>
console.log('hello script setup')
</script>
內部程式碼被編譯為元件 setup()
函式的內容。這意味著與僅當元件首次匯入時執行一次的普通 <script>
不同,<script setup>
內部的程式碼將在每次建立元件例項時執行。
頂級繫結暴露給模板
當使用 <script setup>
時,在 <script setup>
內部宣告的任何頂級繫結(包括變數、函式宣告和匯入)都可以直接在模板中使用
vue
<script setup>
// variable
const msg = 'Hello!'
// functions
function log() {
console.log(msg)
}
</script>
<template>
<button @click="log">{{ msg }}</button>
</template>
匯入也是以相同的方式暴露。這意味著您可以直接在模板表示式中使用匯入的幫助函式,而無需透過 methods
選項暴露它
vue
<script setup>
import { capitalize } from './helpers'
</script>
<template>
<div>{{ capitalize('hello') }}</div>
</template>
響應性
響應式狀態需要使用 響應式API 顯式建立。與 setup()
函式返回的值類似,在模板中引用時,refs 會自動解包。
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
使用元件
<script setup>
作用域內的值也可以直接用作自定義元件標籤名。
vue
<script setup>
import MyComponent from './MyComponent.vue'
</script>
<template>
<MyComponent />
</template>
將 MyComponent
視為一個變數引用。如果你使用過 JSX,這裡的思維模型是類似的。其短橫線等價形式 <my-component>
也在模板中有效 - 然而,為了保持一致性,強烈建議使用 PascalCase 元件標籤。這也幫助區分於原生自定義元素。
動態元件
由於元件是以變數形式引用的,而不是在字串鍵下注冊,因此在 <script setup>
內使用動態元件時應使用動態的 :is
繫結。
vue
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>
注意元件可以在三元表示式中作為變數使用。
遞迴元件
SFC 可以透過其檔名隱式引用自身。例如,一個名為 FooBar.vue
的檔案可以在其模板中使用 <FooBar/>
來引用自身。
注意這比匯入元件的優先順序低。如果你有一個與元件推斷名稱衝突的命名匯入,你可以給匯入重新命名。
js
import { FooBar as FooBarChild } from './components'
名稱空間元件
你可以使用帶點的元件標籤,如 <Foo.Bar>
,來引用物件屬性下的元件。當你從單個檔案匯入多個元件時,這很有用。
vue
<script setup>
import * as Form from './form-components'
</script>
<template>
<Form.Input>
<Form.Label>label</Form.Label>
</Form.Input>
</template>
使用自定義指令
全域性註冊的自定義指令正常工作。區域性自定義指令不需要顯式透過 <script setup>
註冊,但它們必須遵循 vNameOfDirective
的命名方案。
vue
<script setup>
const vMyDirective = {
beforeMount: (el) => {
// do something with the element
}
}
</script>
<template>
<h1 v-my-directive>This is a Heading</h1>
</template>
如果你從其他地方匯入指令,它可以被重新命名為符合所需的命名方案。
vue
<script setup>
import { myDirective as vMyDirective } from './MyDirective.js'
</script>
defineProps() & defineEmits()
為了宣告具有完整型別推斷支援的選項,如 props
和 emits
,我們可以使用 defineProps
和 defineEmits
API,這些 API 在 <script setup>
內自動可用。
vue
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
// setup code
</script>
defineProps
和defineEmits
是僅能在<script setup>
內使用的 編譯器宏。它們不需要匯入,並且在<script setup>
被處理時會編譯掉。defineProps
接受與props
選項相同的值,而defineEmits
接受與emits
選項相同的值。defineProps
和defineEmits
提供基於傳遞的選項的正確型別推斷。傳遞給
defineProps
和defineEmits
的選項將被提升到模組作用域中。因此,選項不能引用在 setup 作用域中宣告的區域性變數。這樣做會導致編譯錯誤。然而,它 可以 引用匯入的繫結,因為它們也在模組作用域中。
僅型別 props/emit 宣告
可以使用純型別語法透過將字面量型別引數傳遞給 defineProps
或 defineEmits
來宣告 props 和 emits。
ts
const props = defineProps<{
foo: string
bar?: number
}>()
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
// 3.3+: alternative, more succinct syntax
const emit = defineEmits<{
change: [id: number] // named tuple syntax
update: [value: string]
}>()
defineProps
或defineEmits
只能使用執行時宣告或型別宣告中的一種。同時使用兩種宣告會導致編譯錯誤。當使用型別宣告時,從靜態分析自動生成等效的執行時宣告,以消除雙重宣告的需求,並確保正確的執行時行為。
在開發模式下,編譯器將嘗試從型別推斷相應的執行時驗證。例如,此處從
foo: string
型別推斷出foo: String
。如果型別是匯入型別的引用,由於編譯器沒有外部檔案的資訊,推斷結果將是foo: null
(等同於any
型別)。在生產模式下,編譯器將生成陣列格式宣告以減少包大小(這裡的屬性將編譯為
['foo', 'bar']
)
在版本 3.2 及以下中,
defineProps()
的泛型型別引數僅限於型別字面量或區域性介面的引用。此限制已在 3.3 中解決。Vue 的最新版本支援在型別引數位置引用匯入的有限複雜型別。然而,由於型別到執行時的轉換仍然是基於 AST 的,因此不支援需要實際型別分析的一些複雜型別,例如條件型別。您可以用於單個屬性的型別條件型別,但不能用於整個屬性物件。
響應式屬性解構
在 Vue 3.5 及以上版本中,從 defineProps
的返回值解構的變數是響應式的。Vue 的編譯器會自動在相同的 <script setup>
塊中訪問從 defineProps
解構的變數時,將 props.
預先新增到程式碼中。
ts
const { foo } = defineProps(['foo'])
watchEffect(() => {
// runs only once before 3.5
// re-runs when the "foo" prop changes in 3.5+
console.log(foo)
})
上面的程式碼編譯為以下等效程式碼
js
const props = defineProps(['foo'])
watchEffect(() => {
// `foo` transformed to `props.foo` by the compiler
console.log(props.foo)
})
此外,您可以使用 JavaScript 的原生預設值語法為屬性宣告預設值。這在使用基於型別的屬性宣告時特別有用。
ts
interface Props {
msg?: string
labels?: string[]
}
const { msg = 'hello', labels = ['one', 'two'] } = defineProps<Props>()
使用型別宣告時的預設屬性值
在 3.5 及以上版本中,可以使用響應式屬性解構自然地宣告預設值。但在 3.4 及以下版本中,響應式屬性解構預設未啟用。為了使用基於型別的宣告宣告屬性預設值,需要 withDefaults
編譯器宏。
ts
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
這將編譯為等效的執行時屬性 default
選項。此外,withDefaults
輔助程式為預設值提供型別檢查,並確保返回的 props
型別已刪除具有預設值宣告的屬性的必需標誌。
INFO
請注意,當使用 withDefaults
時,對於可變引用型別(如陣列或物件)的預設值應使用函式包裝,以避免意外修改和外部副作用。這確保了每個元件例項都得到其自己的預設值副本。使用解構的預設值時不需要這樣做。
defineModel()
- 僅適用於 3.4+
此宏可用於宣告一個雙向繫結屬性,可以從父元件透過 v-model
使用。在 元件 v-model
指南 中也討論了示例用法。
在底層,此宏聲明瞭一個模型屬性及其相應的值更新事件。如果第一個引數是字面量字串,則將其用作屬性名;否則,屬性名將預設為"modelValue"
。在兩種情況下,您還可以傳遞一個額外的物件,其中可以包含屬性的選項和模型引用的值轉換選項。
js
// declares "modelValue" prop, consumed by parent via v-model
const model = defineModel()
// OR: declares "modelValue" prop with options
const model = defineModel({ type: String })
// emits "update:modelValue" when mutated
model.value = 'hello'
// declares "count" prop, consumed by parent via v-model:count
const count = defineModel('count')
// OR: declares "count" prop with options
const count = defineModel('count', { type: Number, default: 0 })
function inc() {
// emits "update:count" when mutated
count.value++
}
警告
如果您為defineModel
屬性指定了default
值,並且您沒有從父元件為該屬性提供任何值,這可能會導致父元件和子元件之間的不同步。在下面的示例中,父元件的myRef
是未定義的,但子元件的model
是1
js
// child component:
const model = defineModel({ default: 1 })
// parent component:
const myRef = ref()
html
<Child v-model="myRef"></Child>
修飾符和轉換器
要訪問與v-model
指令一起使用的修飾符,我們可以像這樣解構defineModel()
的返回值
js
const [modelValue, modelModifiers] = defineModel()
// corresponds to v-model.trim
if (modelModifiers.trim) {
// ...
}
當存在修飾符時,我們可能需要在讀取或將其同步回父元件時轉換值。我們可以透過使用get
和set
轉換器選項來實現這一點
js
const [modelValue, modelModifiers] = defineModel({
// get() omitted as it is not needed here
set(value) {
// if the .trim modifier is used, return trimmed value
if (modelModifiers.trim) {
return value.trim()
}
// otherwise, return the value as-is
return value
}
})
與TypeScript一起使用
與defineProps
和defineEmits
類似,defineModel
也可以接收型別引數來指定模型值和修飾符的型別
ts
const modelValue = defineModel<string>()
// ^? Ref<string | undefined>
// default model with options, required removes possible undefined values
const modelValue = defineModel<string>({ required: true })
// ^? Ref<string>
const [modelValue, modifiers] = defineModel<string, 'trim' | 'uppercase'>()
// ^? Record<'trim' | 'uppercase', true | undefined>
defineExpose()
使用<script setup>
的元件預設是封閉的 - 即透過模板引用或$parent
鏈檢索到的元件的公共例項將不會暴露<script setup>
內部宣告的任何繫結。
要顯式在<script setup>
元件中暴露屬性,請使用defineExpose
編譯器宏
vue
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
當父元件透過模板引用獲取此元件的例項時,檢索到的例項將是{ a: number, b: number }
的形式(引用就像在正常例項上一樣自動展開)。
defineOptions()
- 僅在3.3+版本中支援
此宏可用於在<script setup>
中直接宣告元件選項,而無需使用單獨的<script>
塊
vue
<script setup>
defineOptions({
inheritAttrs: false,
customOptions: {
/* ... */
}
})
</script>
- 這是一個宏。選項將被提升到模組作用域,並且不能訪問
<script setup>
中不是字面量常量的本地變數
defineSlots()
- 僅在3.3+版本中支援
此宏可用於為IDE提供型別提示,以進行插槽名稱和屬性型別檢查。
defineSlots()
只接受一個型別引數,不接受執行時引數。型別引數應該是型別文字,其中屬性鍵是插槽名稱,值型別是插槽函式。函式的第一個引數是插槽期望接收的屬性,其型別將用於模板中的插槽屬性。返回型別目前被忽略,可以是any
,但我們可能會在將來利用它進行插槽內容檢查。
它還返回slots
物件,該物件與在setup上下文上公開的slots
物件或由useSlots()
返回的物件等效。
vue
<script setup lang="ts">
const slots = defineSlots<{
default(props: { msg: string }): any
}>()
</script>
useSlots()
& useAttrs()
在 `