优势
<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。在 Vue3中只需要在 script 标签上加上 setup 属性,无需 return,template 便可直接使用。相比于普通的
- 更少的样板内容,更简洁的代码。
- 能够使用纯 TypeScript 声明 props 和抛出事件。
- 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
- 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。
tips:Vue3.2 版本开始才能使用语法糖!
一、基本语法
要使用这个语法,需要将 setup attribute 添加到 <script> 代码块上:
1 2 3 4
| <script setup> console.log('hello script setup') </script>
|
二、设置name
新增加一个script标签,在这个标签中写入name属性,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <button>demo</button> </template>
<script> export default { name: "VButton", }; </script>
<script setup> </script>
<style scoped lang="less"> </style>
|
三、data的设置和使用
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <div class="home"> <button>+1</button> {{ num }} </div> </template>
<script setup> import { ref } from 'vue'; const num = ref(0); </script>
|
四、method使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div class="home"> <button @click="btnClick">+1</button> {{ num }} </div> </template>
<script setup> import { ref } from 'vue'; const num = ref(0);
const btnClick = () => { num.value = num.value + 1 } </script>
|
五、store仓库的使用
1 2 3 4 5 6
| <script setup> import { useStore } from '@/store'; const store = useStore(); store.getters.userInfo </script>
|
六、computed计算属性的使用
接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script setup> import { computed, reactive } from 'vue'; import { useStore } from '@/store'; const store = useStore(); const userInfo = computed(() => store.getters.userInfo);
let person = reactive({ firstName:'小', lastName:'叮当' }) const fullName = computed(() => { return person.firstName + '-' + person.lastName }) </script>
|
七、路由userRoute和userRouter使用
1 2 3 4 5 6 7 8 9 10
| <script setup> import { useRoute, useRouter } from 'vue-router'; const route = useRoute(); const router = useRouter(); route.query // 获取路由query route.params // 获取路由params
router.push({ path: '/login', query: {} }); </script>
|
八、使用组件以及动态组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <template> <!-- PascalCase,建议使用,有助于区分原生的自定义元素 --> <MyComponent /> <!-- kebab-case --> <my-component /> <component :is="Foo" /> <component :is="someCondition ? Foo : Bar" /> <FooBarChild /> <Form.Input> <Form.Label>label</Form.Label> </Form.Input> </template>
<script setup> import MyComponent from './MyComponent.vue' import Foo from './Foo.vue' import Bar from './Bar.vue'
// 有命名的 import 导入和组件的推断名冲突了,可以使用 import 别名导入 import { FooBar as FooBarChild } from './components'
// 从单个文件中导入多个组件 import * as Form from './form-components'
</script>
|
九、使用指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <h1 v-my-directive>This is a Heading</h1> <h1 py-directive>This is a Heading</h1> </template>
<script setup> // 内部定义 const vMyDirective = { beforeMount: (el) => { // 在元素上做些操作 } } // 外部导入的指令同样能够工作,并且能够通过重命名来使其符合命名规范 import { myDirective as pyDirective } from './MyDirective.js' </script>
|
十、emit和defineEmits的使用
1 2 3 4 5 6 7
| <script setup> const emit = defineEmits(['change', 'delete']) // setup code emits('change', 'cancel'); emits('delete'); </script>
|
十一、props和defineProps的使用
1 2 3 4 5 6 7
| <script setup> const props = defineProps({ foo: String, }); console.log(props.foo); </script>
|
十二、watch的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <script setup> import { ref, watch, watchEffect } from 'vue'; import { useRoute } from 'vue-router'; const num = ref(0);
// 监听一个数据源 watch(num, (num, prevNum) => { /* ... */ })
const fooRef = ref(1); const barRef = ref(2); // 监听多个数据源 watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ console.log('fooRef或barRef变了') })
// 监听路由参数 const route = useRoute(); watch( () => route.fullPath, () => { // code } ); </script>
|
十三、watchEffect的使用
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
1 2 3 4 5 6 7 8 9 10 11 12
| <script setup> const count = ref(0)
watchEffect(() => console.log(count.value)) // -> logs 0
setTimeout(() => { count.value++ // -> logs 1 }, 100) </script>
|
十四、defineExpose的使用
使用<script setup> 的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。
为了在<script setup> 组件中明确要暴露出去的属性,使用 defineExpose 编译器宏:
1 2 3 4 5 6 7 8 9 10 11
| <script setup> import { ref } from 'vue'
const a = 1 const b = ref(2)
defineExpose({ a, b }) </script>
|
当父组件通过模板 ref 的方式获取到当前组件的实例,获取到的实例会像这样 { a: number, b: number } (ref 会和在普通实例中一样被自动解包)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <template> <button @click="btnClick">点击</button> <Content ref="content" /> </template>
<script setup> import { ref } from 'vue'
// content组件ref const content = ref('content') // 点击设置 const btnClick = () => { console.log(content.value.b) }
</script>
|
十五、provide 和 inject 的使用
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script setup> import { ref, provide } from 'vue'
let name = ref('张三') // 使用provide provide('provideState', { name, changeName: () => { name.value = '李四' } }) </script>
|
1 2 3 4 5 6 7
| <script setup> import { inject } from 'vue' const changeState = inject('changeState')
changeState.changeName() </script>
|
十六、useSlots 和 useAttrs的使用
1 2 3 4 5 6 7
| <script setup> import { useSlots, useAttrs } from 'vue'
const slots = useSlots() const attrs = useAttrs() </script>
|
十七、await的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script setup> const handleLogin = async (phone: string, code: string) => { try { const { token } = await requestAuthLogin({ phone, code, }); console.log(token); } catch (e) { console.log(e); } finally { } }; </script>
|
十八、style特性
单文件组件的 <style> 标签可以通过 v-bind 这一 CSS 函数将 CSS 的值关联到动态的组件状态上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <script setup> const theme = { color: 'red' } </script>
<template> <p>hello</p> </template>
<style scoped> p { color: v-bind('theme.color'); } </style>
|
实际的值会被编译成 hash 的 CSS 自定义 property,CSS 本身仍然是静态的。自定义 property 会通过内联样式的方式应用到组件的根元素上,并且在源值变更的时候响应式更新。
十九、使用vue实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <script setup> import { getCurrentInstance } from 'vue'; // 获取当前组件实例 const instance = getCurrentInstance(); // 获取当前组件的上下文,下面两种方式都能获取到组件的上下文。 // 方式一,这种方式只能在开发环境下使用,生产环境下的ctx将访问不到 const { ctx } = getCurrentInstance();
// 方式二,此方法在开发环境以及生产环境下都能放到组件上下文对象(推荐) const { proxy } = getCurrentInstance(); // ctx 中包含了组件中由ref和reactive创建的响应式数据对象,以及以下对象及方法; proxy.$attrs proxy.$data proxy.$el proxy.$emit proxy.$forceUpdate proxy.$nextTick proxy.$options proxy.$parent proxy.$props proxy.$refs proxy.$root proxy.$slots proxy.$watch </script>
|
二十、仅限 TypeScript 的功能
设置类型声明时的默认 props 值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script setup> interface IProps { foo: string bar?: number } const props = defineProps<IProps>()
const emit = defineEmits<{ (event: 'change', id: number): void; (event: 'update', value: string): void; (event: 'close'): void; }>()
</script>
|
仅限类型的 defineProps 声明的不足之处在于,它没有可以给 props 提供默认值的方式。为了解决这个问题,提供了 withDefaults 编译器宏:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script setup> interface Props { msg?: string labels?: string[] }
const props = withDefaults(defineProps<Props>(), { msg: 'hello', labels: () => ['one', 'two'] })
</script>
|
- 注意事项
setup执行的时机:在beforeCreate之前执行一次,this是undefined。
- 由于模块执行语义的差异,
<script setup> 中的代码依赖单文件组件的上下文。当将其移动到外部的 .js 或者 .ts 文件中的时候,对于开发者和工具来说都会感到混乱。因而 <script setup> 不能和 src attribute 一起使用。