跳轉到內容

<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()

為了宣告具有完整型別推斷支援的選項,如 propsemits,我們可以使用 definePropsdefineEmits API,這些 API 在 <script setup> 內自動可用。

vue
<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup code
</script>
  • definePropsdefineEmits 是僅能在 <script setup> 內使用的 編譯器宏。它們不需要匯入,並且在 <script setup> 被處理時會編譯掉。

  • defineProps 接受與 props 選項相同的值,而 defineEmits 接受與 emits 選項相同的值。

  • definePropsdefineEmits 提供基於傳遞的選項的正確型別推斷。

  • 傳遞給 definePropsdefineEmits 的選項將被提升到模組作用域中。因此,選項不能引用在 setup 作用域中宣告的區域性變數。這樣做會導致編譯錯誤。然而,它 可以 引用匯入的繫結,因為它們也在模組作用域中。

僅型別 props/emit 宣告

可以使用純型別語法透過將字面量型別引數傳遞給 definePropsdefineEmits 來宣告 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]
}>()
  • definePropsdefineEmits 只能使用執行時宣告或型別宣告中的一種。同時使用兩種宣告會導致編譯錯誤。

  • 當使用型別宣告時,從靜態分析自動生成等效的執行時宣告,以消除雙重宣告的需求,並確保正確的執行時行為。

    • 在開發模式下,編譯器將嘗試從型別推斷相應的執行時驗證。例如,此處從 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) {
  // ...
}

當存在修飾符時,我們可能需要在讀取或將其同步回父元件時轉換值。我們可以透過使用getset轉換器選項來實現這一點

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一起使用

definePropsdefineEmits類似,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()

在 `