`<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 会和在普通实例中一样被自动解包)