Vue 3.3新增了一些语法糖和宏,包括泛型组件、defineSlots、defineEmits、defineOptions
defineProps
- 父子组件传参
<template> <div> <Child name="xiaoman"></Child> </div> </template> <script lang='ts' setup> import Child from './views/child.vue' </script> <style></style>
子组件使用defineProps
接受值
<template> <div> {{ name }} </div> </template> <script lang='ts' setup> defineProps({ name: String }) </script>
- 使用TS字面量模式
<template> <div> {{ name }} </div> </template> <script lang='ts' setup> defineProps<{ name:string }>() </script>
Vue3.3
新增 defineProps 可以接受泛型
<Child :name="['xiaoman']"></Child> //-------------子组件----------------- <template> <div> {{ name }} </div> </template> <script generic="T" lang='ts' setup> defineProps<{ name:T[] }>() </script>
defineEmits
- 父组件
<template> <div> <Child @send="getName"></Child> </div> </template> <script lang='ts' setup> import Child from './views/child.vue' const getName = (name: string) => { console.log(name) } </script> <style></style>
子组件常规方式派发Emit
<template> <div> <button @click="send">派发事件</button> </div> </template> <script lang='ts' setup> const emit = defineEmits(['send']) const send = () => { // 通过派发事件,将数据传递给父组件 emit('send', '我是子组件的数据') } </script>
子组件TS字面量模式派发
<template> <div> <button @click="send">派发事件</button> </div> </template> <script lang='ts' setup> const emit = defineEmits<{ (event: 'send', name: string): void }>() const send = () => { // 通过派发事件,将数据传递给父组件 emit('send', '我是子组件的数据') } </script>
Vue3.3
新写法更简短
<template> <div> <button @click="send">派发事件</button> </div> </template> <script lang='ts' setup> const emit = defineEmits<{ 'send':[name:string] }>() const send = () => { // 通过派发事件,将数据传递给父组件 emit('send', '我是子组件的数据') } </script>
defineExpose
没变化
defineExpose({ name:"张三" })
defineSlots
- 父组件
<template> <div> <Child :data="list"> <template #default="{item}"> <div>{{ item.name }}</div> </template> </Child> </div> </template> <script lang='ts' setup> import Child from './views/child.vue' const list = [ { name: "张三" }, { name: "李四" }, { name: "王五" } ] </script> <style></style>
子组件 defineSlots只做声明不做实现 同时约束slot类型
<template> <div> <ul> <li v-for="(item,index) in data"> <slot :index="index" :item="item"></slot> </li> </ul> </div> </template> <script generic="T" lang='ts' setup> defineProps<{ data: T[] }>() defineSlots<{ default(props:{item:T,index:number}):void }>() </script>
defineOptions
- 主要是用来定义 Options API 的选项
常用的就是定义name 在seutp 语法糖模式发现name不好定义了需要在开启一个script自定义name现在有了defineOptions就可以随意定义name了
defineOptions({ name:"Child", inheritAttrs:false, })
defineModel
由于该API处于实验性特性 可能会被删除暂时不讲
warnOnce( `This project is using defineModel(), which is an experimental ` + `feature. It may receive breaking changes or be removed in the future, so ` + `use at your own risk.\n` + `To stay updated, follow the RFC at https://github.com/vuejs/rfcs/discussions/503.` )
源码解析
- core\packages\compiler-sfc\src\script\defineSlots.ts
export function processDefineSlots( ctx: ScriptCompileContext, node: Node, declId?: LVal ): boolean { //是否调用了defineSlots if (!isCallOf(node, DEFINE_SLOTS)) { return false } //是否重复调用了defineSlots if (ctx.hasDefineSlotsCall) { ctx.error(`duplicate ${DEFINE_SLOTS}() call`, node) } //函数将 ctx 对象的 hasDefineSlotsCall 属性设置为 true,表示已经调用了 DEFINE_SLOTS 函数 ctx.hasDefineSlotsCall = true //然后函数检查传递给 DEFINE_SLOTS 函数的参数个数是否为零,如果不是,则函数抛出错误,指示 DEFINE_SLOTS 函数不接受参数。 if (node.arguments.length > 0) { ctx.error(`${DEFINE_SLOTS}() cannot accept arguments`, node) } //接下来,如果函数接收到了一个可选的表示插槽定义的标识符的节点对象, //则函数使用 ctx.s.overwrite //方法将该节点对象替换为一个表示使用插槽的帮助函数的调用 if (declId) { ctx.s.overwrite( ctx.startOffset! + node.start!, //开始位置 ctx.startOffset! + node.end!, //结束位置 `${ctx.helper('useSlots')}()` //替换的内容 此时就拥有了类型检查 ) } return true }
- core\packages\compiler-sfc\src\script\defineOptions.ts
export function processDefineOptions( ctx: ScriptCompileContext, node: Node ): boolean { //是否调用了defineOptions if (!isCallOf(node, DEFINE_OPTIONS)) { return false } //是否重复调用了defineOptions if (ctx.hasDefineOptionsCall) { ctx.error(`duplicate ${DEFINE_OPTIONS}() call`, node) } //defineOptions()不能接受类型参数 if (node.typeParameters) { ctx.error(`${DEFINE_OPTIONS}() cannot accept type arguments`, node) } //defineOptions()必须接受一个参数 if (!node.arguments[0]) return true //函数将 ctx 对象的 hasDefineOptionsCall 属性设置为 true,表示已经调用了 DEFINE_OPTIONS 函数 ctx.hasDefineOptionsCall = true //函数将 ctx 对象的 optionsRuntimeDecl 属性设置为传递给 DEFINE_OPTIONS 函数的参数 ctx.optionsRuntimeDecl = unwrapTSNode(node.arguments[0]) let propsOption = undefined let emitsOption = undefined let exposeOption = undefined let slotsOption = undefined //遍历 optionsRuntimeDecl 的属性,查找 props、emits、expose 和 slots 属性 if (ctx.optionsRuntimeDecl.type === 'ObjectExpression') { for (const prop of ctx.optionsRuntimeDecl.properties) { if ( (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') && prop.key.type === 'Identifier' ) { if (prop.key.name === 'props') propsOption = prop if (prop.key.name === 'emits') emitsOption = prop if (prop.key.name === 'expose') exposeOption = prop if (prop.key.name === 'slots') slotsOption = prop } } } //禁止使用defineOptions()来声明props、emits、expose和slots if (propsOption) { ctx.error( `${DEFINE_OPTIONS}() cannot be used to declare props. Use ${DEFINE_PROPS}() instead.`, propsOption ) } if (emitsOption) { ctx.error( `${DEFINE_OPTIONS}() cannot be used to declare emits. Use ${DEFINE_EMITS}() instead.`, emitsOption ) } if (exposeOption) { ctx.error( `${DEFINE_OPTIONS}() cannot be used to declare expose. Use ${DEFINE_EXPOSE}() instead.`, exposeOption ) } if (slotsOption) { ctx.error( `${DEFINE_OPTIONS}() cannot be used to declare slots. Use ${DEFINE_SLOTS}() instead.`, slotsOption ) } return true }