在 Vue3 单文件组件中使用 <script setup> + 组合式 API

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。

基本语法

<script> 标签上添加 setup 属性启用该语法,里面的代码会背编译成组件 setup() 函数的内容,每次组件实例被创建时执行。

1
2
3
<script setup>
// 组合式 API 写在这里
</script>

如果要使用 TypeScript,加上 lang="ts" 属性即可。

1
2
<script lang="ts" setup>
</script>

当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用。

响应式

响应式即变量数据变化的时候,视图跟着变化。
响应式状态需要明确使用 响应式 API 来创建.
常用 API:

  • ref()
  • reactive()
  • computed()
  • watch()
  • watchEffect()
  • readonly()

模板引用实例

<script setup> 中可通过 ref(null) 函数声明 ref 同名常量,可获取模板引用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<input ref="input" />
</template>

<script setup>
import { ref, onMounted } from 'vue'

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)

onMounted(() => {
input.value.focus()
})
</script>

组件

使用组件

<script setup> 里的值也能被直接作为自定义组件的标签名使用:

1
2
3
4
5
6
7
8
<template>
<MyComponent /> 推荐使用 PascalCase 格式以保持和变量保持一致。
<my-component>也可以用 kebab-case 格式的标签</my-component>
</template>

<script setup>
import MyComponent from './MyComponent.vue'
</script>

动态组件

:is 绑定动态组件

1
2
3
4
5
6
7
8
9
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>

<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>

递归组件

一个单文件组件可以通过它的文件名被其自己所引用。例如:名为 FooBar.vue 的组件可以在其模板中用 <FooBar /> 引用它自己。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- FooBar.vue -->
<template>
<!-- 递归显示分类及其子分类 -->
<ul v-for="category in categories">
<li>
{{ category.title }}
<FooBar v-if="category.children" :categories="category.children"></FooBar>
</li>
</ul>
</template>

<script>
defineProps(['categories'])
</script>

命名空间组件

可以使用带 . 的组件标签,例如 <Foo.Bar> 来引用嵌套在对象属性中的组件。
这在需要从单个文件中导入多个组件的时候非常有用:

1
2
3
4
5
6
7
8
9
<script setup>
import * as Form from './form-components'
</script>

<template>
<Form.Input>
<Form.Label>label</Form.Label>
</Form.Input>
</template>

通过插槽来分配内容

我们使用 <slot> 作为一个占位符,父组件传递进来的内容就会渲染在这里。

1
2
3
4
5
6
7
<!-- AlertBox.vue -->
<template>
<div class="alert-box">
<strong>This is an Error for Demo Purposes</strong>
<slot />
</div>
</template>
1
2
3
4
5
<template>
<AlertBox>
Something bad happened.
</AlertBox>
</template>

具名插槽:

1
<slot name="header"></slot>

引用具名组件:

1
2
3
4
5
6
7
<template v-slot:header>
<!-- header 插槽的内容放这里 -->
</template>
简写:
<template #header>
<!-- header 插槽的内容放这里 -->
</template>

使用自定义指令

<script setup> 中自定义的指令不需要显式注册就可以直接使用。

1
2
3
4
5
6
7
8
9
10
<script setup>
const vMyDirective = {
beforeMount: (el) => {
// 在元素上做些操作
}
}
</script>
<template>
<h1 v-my-directive>...</h1>
</template>

defineProps() 和 defineEmits()

  • defineProps() 用来声明属性
  • defineEmits() 用来声明事件

defineProps()defineEmits() 是编译器宏,不需要导入,并且只能在 <script setup> 中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script setup>
const props = defineProps({
foo: String
})

// 在 <script setup> 中可使用 defineProps() 返回值变量访问自定义属性
console.log(props.foo)

// 组件声明事件后,还需要触发事件,才能让事件起作用。
const emits = defineEmits(['change', 'delete'])

const onDelete = () => {
// 在 <script setup> 中可以用 defineEmits() 返回值变量触发事件。
emits('delete')
}
</script>

<template>
<p>{{ foo }}</p>
<!-- template 中可以用预置变量 $emit 触发声明的变量 -->
<input @keyup="$emit('change')" />
<button @click="onDelete">删除</button>
</template>

defineProps() 默认值

非 ts 我们可以用 default 来设置默认值,ts 需要用 withDefaults 编译器宏。

1
2
3
4
5
6
7
8
9
10
<script setup>
// 非 ts 声明属性的默认值
defineProps({
msg: {
type: String,
default: 'hello',
required: false
}
});
</script>
1
2
3
4
5
6
7
8
9
10
<script setup lang="ts">
export interface Props {
msg?: string
}

// TypeScript 使用 withDefaults 编译器宏
const props = withDefaults(defineProps<Props>(), {
msg: 'hello'
})
</script>

defineExpose()

通过模板引用或者 $parent 链获取到的组件的公开实例,默认不会暴露任何在 <script setup> 中声明的绑定。
可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的绑定。

更详细的内容前往《组件间互相访问》,包含组件父组件访问子组件、子组件访问父组件、兄弟组件互相访问。

限制

<script setup> 标签不能加 src 属性。

参考

在 Vue3 单文件组件中使用 <script setup> + 组合式 API

https://coderpan.com/front-end/vue3-sfc-composition-script-setup.html

作者

CoderPan

发布于

2023-01-31

更新于

2024-02-08

许可协议

评论