`<script setup>`是`setup`函數編譯時的語法糖。相比于普通的`<script>`語法,它具有更多優勢:
* 更少的樣板內容,更簡潔的代碼。
* 能夠使用純 Typescript 聲明 props 和拋出事件。
* 更好的運行時性能 (其模板會被編譯成與其同一作用域的渲染函數,沒有任何的中間代理)。
* 更好的 IDE 類型推斷性能 (減少語言服務器從代碼中抽離類型的工作)。
## 頂層的綁定會被暴露給模板
不同于`setup`函數,需要將變量或者方法返回給模版使用,在`<script setup>`中聲明的變量、函數以及 `import` 引入的內容都能在模板中直接使用。
~~~ xml
<script setup>
//引入的內容也可以在模版中直接使用
import { capitalize } from './helpers'
// 變量
const msg = 'Hello!'
// 函數
function log() {
console.log(msg)
}
</script>
<template>
<div @click="log">{{ capitalize('hello')}}</div>
</template>
~~~
## 響應式
響應式狀態需要明確使用[響應式 APIs](https://v3.cn.vuejs.org/api/basic-reactivity.html)來創建。和從`setup()`函數中返回值一樣,ref 值在模板中使用的時候會自動解包:
~~~
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
~~~
## 使用組件,無需在components中聲明,可以直接使用
`<script setup>`范圍里的值也能被直接作為自定義組件的標簽名使用:
~~~xml
<script setup>
import MyComponent from './MyComponent.vue'
</script>
<template>
<MyComponent />
</template>
~~~
## 動態組件
因為組件被引用為變量而不是作為字符串鍵來使用的,所以在`<script setup>`中要使用動態組件的時候,就應該使用動態的`:is`來綁定:
~~~xml
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>
~~~
## 遞歸組件
一個單文件組件可以通過它的文件名被其自己所引用。例如:名為`FooBar.vue`的組件可以在其模板中用`<FooBar/>`引用它自己。
請注意這種方式相比于 import 導入的組件優先級更低。如果有命名的 import 導入和組件的推斷名沖突了,可以使用 import 別名導入:
~~~
import { FooBar as FooBarChild } from './components'
~~~
## 命名空間組件
可以使用帶點的組件標記,例如`<Foo.Bar>`來引用嵌套在對象屬性中的組件。這在需要從單個文件中導入多個組件的時候非常有用:
~~~
<script setup>
import * as Form from './form-components'
</script>
<template>
<Form.Input>
<Form.Label>label</Form.Label>
</Form.Input>
</template>
~~~
## 使用自定義指令,本地指令需要符合`vNameOfDirective`命名形式
全局注冊的自定義指令可正常使用,本地注冊的指令也可以直接在模板中使用,但有一個需要注意的限制:必須以`vNameOfDirective`的形式來命名本地自定義指令,以使得它們可以直接在模板中使用。
~~~xml
<script setup>
const vMyDirective = {
beforeMount: (el) => {
// 在元素上做些操作
}
}
</script>
<template>
<h1 v-my-directive>This is a Heading</h1>
</template>
~~~
~~~
<script setup>
// 導入的指令同樣能夠工作,并且能夠通過重命名來使其符合命名規范
import { myDirective as vMyDirective } from './MyDirective.js'
</script>
~~~
## `defineProps`和`defineEmits`
在`<script setup>`中必須使用`defineProps`和`defineEmits`API 來聲明`props`和`emits`,它們具備完整的類型推斷并且在`<script setup>`中是直接可用的:
~~~xml
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
// setup code
</script>
~~~
* `defineProps`和`defineEmits`都是只在`<script setup>`中才能使用的**編譯器宏**。他們不需要導入且會隨著`<script setup>`處理過程一同被編譯掉。
* `defineProps`接收與[`props`選項](https://v3.cn.vuejs.org/api/options-data.html#props)相同的值,`defineEmits`也接收[`emits`選項](https://v3.cn.vuejs.org/api/options-data.html#emits)相同的值。
* `defineProps`和`defineEmits`在選項傳入后,會提供恰當的類型推斷。
## `defineExpose`,暴露通過`ref`或者`$parent`引用的屬性或方法
使用`<script setup>`的組件是**默認關閉**組件內的屬性或方法,必須通過`defineExpose`暴露出去,外部通過`ref`和`$parent`才能訪問。
~~~xml
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
~~~
當父組件通過模板 ref 的方式獲取到當前組件的實例,獲取到的實例會像這樣`{ a: number, b: number }`(ref 會和在普通實例中一樣被自動解包)