
上篇文章中我们分析了是如何处理setup block的ast的,这篇文章我们接着往下分析,如果没看过可以去看下~。
坏蛋Dan:vue/compiler-sfc源码分析学习--part2:如何处理script--day3
老规矩我们说的non setup block或者non setup script block指的是<script>...</script>这样,反之则是<script setup>...</script>
老样子
这个就不多说了,前面的文章也说过了。就是收集使用了$ref这种语法糖的变量,不过这里还有一个点需要看下,propsDestructuredBindings把这东西也传进去了。propsDestructuredBindings: 是之前我们收集到的props解构声明变量集合。把这个东西传进去可能是有什么操作需要,所以我们需要分析下里面这里做了什么才行。分析放其它中,因为不是这个包中的代码。这里简单的说下即可,简单的说就是和处理$ref一样,需要将解构的prop处理成__prop.xxx,因为这样才不会失去响应式。
runtime propspropsTypeDecl: 上篇文章中我们在分析processDefineProps时有提到过,这个变量是针对typescript的only-type[1]场景的,存放的是props的ts类型声明的节点,只要是ts的东西,除了enum,几乎都得在编译的时候就处理掉。typeDeclaredProps: props的类型声明的集合,此时这里还是空的。declaredProps: 上篇末尾有提到的,如果是typescript下的会进行收集类型定义并存放到这个变量中,然后移除源码中相关的代码,毕竟浏览器不认识ts代码。isProd: 是否是生产环境。interRuntimeType: 看下代码看名字就知道,这是用来定义runtime时的类型,typescript的类型都会被转换为浏览器知道的类型。
这一个小标题的代码很好理解,就是收集props类型声明,也就是上面说的typeDeclaredProps并统一格式为{ key, required, type }。
runtime emits我把调用的方法都放进来了,代码有些长,但是挺好理解的。
emitsTypeDecl: 这里自然指的是被收集的emits的only-type[2]类型声明的节点TSInterfaceBody[3]: 看名字就知道是typescript的interface类型声明的主体。TSCallSignatureDeclaration: 调用函数声明。。这是我个人的翻译。。比如这样的TSTypeAnnotation[4]: 函数参数类型声明,就是上边这个number/string。TSLiteralType[5]: 比如这种type A = "test",literal指的是字面的,也就是说写死的数字\字符串等都符合这个类型。TSUnionType[6]: 比如这种 a: number | string。其他类型之前都讲过了,这里就不多赘述。
这个小标题也挺简单的, 和上面一样,也是在处理并收集emit的类型定义,不过这里收集的是function的名字。
这个报错相信各位学vue3.x的时候都遇到过,比如以下的写法
这个时候就会报错了,那么为什么会有这报错?看着报错内容其实就能知道这一段在干嘛了。
it will be hoisted outside of the setup() function. 它(defineProps)将被提升到setup函数之外,而引用的变量还是在setup函数内,自然就有作用域问题。
实际上props的定义会被迁移到和setup函数同级,参考vue2的props定义。
那么这个时候要怎么解决这个问题呢?
有两种方式:
normal script block中,也就是我常说的non setup block,为什么这样就可以呢?因为normal script block里的东西会被放到代码头,也就是说是script中的最外层作用域。import的方式引入,import导入的也是会被放到代码头,作用域自然包含了setup函数。walkIdentifiers这个方法就不看代码了,由于后面还会用到,所以简单的说下做了什么,过一遍传入的节点里的所有子节点,判断他们是否引用了本地(local)变量,然后执行回调,在这个defineProps的场景中执行的回调则是上面说的那个报错。
上面场景同样适用于props解构的变量、props使用type-only的默认值以及emits。
non setup block的代码前面以及处理完毕setup block和non setup block了,那么这里就把这块non setup script block移除,因为后面是会合并到一起导出的。
metaData)analyzeScriptBindings: 这个方法之前分析non script block的时候说到过了,简单的说就是在分析ast节点,给他们定义为某个类型getObjectOrArrayExpressionKeys: 这个方法之前也说过了,看名字就知道是在获取对象/数组表达式的key。typeDeclaredProps: defineProps的type-only场景获取到的类型定义数据propsDestructureDecl: 说了好几次了,defineProps赋值解构的那个节点propsDestructureRestId: 这个前面貌似没有说过,估计都忘了,这个是defineProps解构赋值中的拓展运算符...。propsDestructuredBindings: 解构赋值时的本地(local)变量和默认赋值,比如: const { a: _a = 123 } = xxx。userImports: 也是老面孔,存放所有(包括non setup block)import的变量,注意这里的key是local的,而真名是在userImports[xx].imported这个变量中isType指的是这个导入的变量是否是类型,ts中的import type xxx from xxx;source:引入的路径refBindings: 同样是老面孔,ref赋值对象,有一点需要注意的是这里面是包括了使用语法糖$ref的,毕竟只是语法糖,编译完还是长一样的。这一大段看着挺长,实际上做的事情非常简单,就是在将前面的数据分门别类,组装到bindingsMetadata中。
来看下最终长啥样
css变量genCssVarsCode: 这个方法其实之前讲过了,可以去看下这篇,这里就不多说了。坏蛋Dan:vue/compiler-sfc源码分析学习--part2:如何处理script--day1
这一段很简单,就是将css变量注入到js代码中执行,不过需要注意的是这里的css变量转换出来的js其实是和scopedId关联的,因为得用在css中。 这段代码会在组件创建时调用,至于css中如何使用,我们到时候分析compileCss的时候再说。
setup参数propsTypeDecl: 这个老面孔,是only-type的类型定义propsIdentifier: 如果defineProps赋值的对象不是用的解构赋值,那么就能确定是普通变量赋值,比如const a = defineProps({});这个a即是这个propsIndentifiergenSetupPropsType: 代码就不看了,简单的说就是用来处理only-type这种类型定义的,将ts的类型转换成能被解析的runtime类型,毕竟ts并不是能被浏览器识别的代码。helper:将辅助函数存储在helperImports中,之前async以及$ref语法糖的辅助函数都在这。
hasAwait:之前的文章里有说到过,如果你直接在setup block中直接用await是没有问题的,因为编译的时候会帮你转换成立即执行函数,这个hasAwait表示是否有使用await。坏蛋Dan:vue/compiler-sfc源码分析学习--part2:如何处理script--day3
而这里插入的两个变量__temp和__restore就是这个立即执行函数需要的变量。
hasDefineExposeCall: 是否调用了defineExpose将需要暴露出去的变量暴露出去,defineExpose被转换成runtime中的expose,前面的文章中有说到,这里就不多说了。emitIdentifier: 和props的一样,这里自然就是defineEmits赋值的变量,和props不同,defineEmits不能解构,所以这里只能是常规变量赋值。emitsTypeDecl: defineEmits的only-type这一大段看着也挺长,实际上做的事情很简单,就是在组装setup需要的参数,这里不是指setup语法糖,而是非语法糖的setup方法的参数, props/emits/expose。 当然,还有给await加临时变量等其它的。
这里面你也可以看出来了,props、emit是和setup函数相关的,需要特殊处理,至于要怎么用,后面会有分析。
今天(好几天)我们主要了解的都是如何处理props、emits相关的,当然,主要都是props的,因为它的场景有三种
typescript下使用type-only加上withDefaultsruntime type(也就是我们经常写的,直接传给defineProps),变量赋值时是用的解构赋值其中场景二又有些小场景判断,比如有默认值,有...拓展运算符,有本地命名(local)。
reactivityTransform处理props这里只贴我们需要的代码
isStaticProperty: 一个静态属性,类型属于正常类型,最重要的是computed不能为true,为true则表明这个node被重写了setter以及getter。如果你是通过解构的方式获取到props,那么这里会转化成__props.xxx。比如const { a } from defineProps({}); 这里的a会变成__props.a。
这里还有一段代码能解释我们的$ref语法糖: s.appendLeft(id.end!+ offset,'.value'),再加上importHelper也就是import { ref as _ref } from 'vue';所以编译出来后和你使用的xxx.value效果是一致的。
编辑于 2022-12-05 16:37・IP 属地广东