Vue3.3 编译宏

image.png

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
}

猜你喜欢

转载自blog.csdn.net/qq1195566313/article/details/131618820