关于 Vue3 setup 语法糖
Vue3 setup
参考:https://juejin.cn/post/7009282373476941831#comment
新的 setup 是组件创建之前, props 被解析之后执行,是组合式 API 的入口
在 setup 中应该避免使用 this, 因为它不会找到示例。setup 的调用发生在 data property,computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取
setup 是一个接收 props 和 context 的函数,setup 返回的所有内容都暴露给组件的其余部分(计算属性、方法、生命周期钩子等等)以及组件的模版,所有 ES 模块导出都被认为是暴露给上下文的值,并包含在 setup()返回对象中。
使用后,script 标签中的内容相当于原本组件声明中 setup() 的函数体,不过也有一定的区别。
使用 script setup 语法糖,组件只需引入不用注册,属性和方法也不用返回,也不用写 setup 函数,也不用洗 export default,甚至是自定义指令也可以在我们的 template 中自动获得。
调用时机
创建组实例,然后初始化 props,紧接着就调用 setup 函数。从生命周期钩子的视角来看,它会在 beforeCreate 钩子之前被调用。
模板中使用
如果 setup 返回(以count例子来看,可以暂时理解为创建)一个对象,则对象的属性将会被合并到组件模板的渲染上下文。
setup 参数
第一个参数接收一个响应式的 props ,这个 props 指向的是外部的 props。如果没有定义 props 选项,setup 中的第一个参数将为 undefined。
- 不要在自组件中修改 props,如果尝试修改,会得到警告甚至报错
- 不要解构 props,解构的 props 会失去响应式
第二个参数提供了一个上下文对象
组件自动注册
导入 component 或 directive 直接 import 即可,无需额外声明
import { MyButton } from "@/components"
import { directive as clickOutside } from 'v-click-outside'
在 script setup 中,引入的组件可以直接使用,无需再通过 components 进行注册,并且无法指定当前组件的名字,它会自动以文件名为主,也就是无法再写 name 属性了。
如果需要定义类似 name 的属性,可以再加个平级的 script 标签,在里面实现即可
组件核心 API 的使用
定义组件的 props
通过 defineProps 指定当前的 props 类型,获得上下文的 props 对象。
<script setup>
import {defineProps} from 'vue'
const props = defineProps({
title: String
})
</script>
或者
<script setup lang="ts">
import {ref, defineProps} from 'vue'
type Props = {
msg: string
}
defineProps<Props>()
</script>
定义 emit
使用 defineEmit 定义当前组件含有的事件,并通过返回的上下文去执行 emit
<script setup>
import { defineEmit } from 'vue'
const emit = defineEmit(['change', 'delete'])
</script>
父子组件通信
defineProps
用来接收父组件传来的 props;defineEmits
用来声明触发的事件
父组件
<template>
<my-son :foo="count" @childClick="childClick"></my-son>
</template>
<script setup lang="ts">
import MySon from "./MySon.vue";
import {ref} from "vue";
let count = ref(0)
let childClick = (e: any): void => {
count.value++
console.log("from son", e)
}
</script>
自组件
<template>
<span @click="sonToFather">信息: {{props.foo}}</span>
</template>
<script lang="ts" setup>
import {defineEmits, defineProps} from "vue";
const emit = defineEmits(["childClick"]); // 声明触发事件
const props = defineProps({foo: Number}) // 获取 props
const sonToFather = () => {
emit("childClick", props.foo)
}
</script>
<style scoped></style>
自组件通过 defineProps 接收父组件传过来的数据,自组件通过 defineEmits 定义事件发送信息给父组件
增强的 props 类型定义
const props2 = defineProps<{
foo: number,
bar?: number
}>()
const emit2 = defineEmits<(
e: 'update' | 'delete',
id: number
) => void>()
采用这种方法将无法使用 props 默认值
定义响应变量、函数、监听、计算属性 computed
watchEffect
<script setup lang="ts">
import {ref, computed, unref, watchEffect} from 'vue';
const count = ref(0) // 不用 return ,直接在 template 中使用
const addCount = () => {
count.value ++
}
// 定义计算属性,使用同上
const howCount = computed(() => "现在count值为: " + count.value)
// 定义监听,使用同上,用于有副作用的监听,会自动收集依赖,和 watch 区别:无需区分deep,immediate,只要依赖的数据发生变化就会调用
watchEffect(() => console.log(count.value))
</script>
reactive
reactive 是一个响应式对象,ref 是一个 { value: 'xxx' }的结构
<script setup lang="ts">
import {reactive, onUnmounted} from "vue";
const state = reactive({
counter: 0
})
// 定时器,每秒都会更新数据
const timer = setInterval(() => {
state.counter ++
}, 1000)
onUnmounted(() => {
clearInterval(timer)
})
</script>
<template>
<div>{{state.counter}}</div>
</template>
ref 暴露变量到模板
无需 export 声明,编译器会自动寻找模板中使用的变量。
生命周期方法
因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。
换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。
可以在生命周期钩子前面加上 "on" 来访问组件的声明周期钩子。

获取 slots 和 attrs
useAttrs
和 useSlots
useAttrs 得到的结果
defineExpose API
传统的写法,我们可以在父组件中,通过 ref 实例的方式去访问自组件的内容,但在 script setup 中,该方法就不能用了,setup 相当于是一个闭包,除了内部的 template 模板,谁都不能访问内部的数据和方法。
如果需要对外暴露 setup 中的数据和方法,需要使用 defineExpose API
const a = 1
const b = ref(2)
defineExpose({a,b})
未亲自测试!!
支持 async await 异步
在 vue3 的源代码中,setup 执行完毕,函数 getCurrentInstance 内部的有个值会释放对 currentInstance 的引用
await 语句会导致后续代码进入异步执行的情况。
所以上述例子中最后一个 getCurrentInstance() 会返回 null,建议使用变量保存第一个 getCurrentInstance() 返回的引用
上述结论还没有亲自实验证实
定义组件其它配置
配置项的缺失,有时候我们需要更改组件选项,在 setup 中我们目前是无法做到的。
我们需要在上方再引入一个 script ,在上方写入对应的 export 即可,需要单开一个 script