前言
继续了解vue transform阶段的流程。
transformeElement
分析元素的处理流程过于庞大的,并且大部分的逻辑都是在处理自身的所有的节点的才会执行,我们只去看主要的逻辑。
首先是预备工作,需要确认的这个元素节点是组件标记(包含动态组件)还是元素标记,要不要使用块,以及拿到标记的一些属性。
接下来是处理props,最主要的是buildProps函数,他是负责构建元素上的完整的propsBuildResult,它主要处理任何放在元素节点上的任何attribute和property(排除一些有单独处理的directive),还需要处理一些情况,比如v-bind="obj",需要和其他的单个属性绑定进行合并,v-on依此。以及class、style的静态和动态合并。还会产生Props的patchFlag。最后返回的结果如下:
执行完buildProps,还需要处理运行时指令。有一些指令的处理程序时在渲染函数执行时执行的,比如v-model,以及用户自定义指令,它们在运行时会调用withDirective和reolsve_directive,处理指令的其他情况,比如指令的exp和arg是否传递等等。
下面就是处理children,children的处理比props处理更加复杂,其中的大部分的逻辑都是构建slots对象,插槽的情况太多样化了,主要是校验v-slot在什么地方使用了、插槽是动态的还是静态的、隐式插槽和具名插槽有没有一起使用,最后确认插槽的类型(分为stable、dynamic、forwaded),返回的结果如下:
如果不是构建成插槽,那么就只有两种情况,一种是直接唯一文本节点(包含:文本、插值、表达式)可以直接传递。另一种就只能去拿node.children。
最后就是去确认的这个元素的patchFlag以及dynamicNameTypes(也就是/* PROPS TEXT */这个东西)
transformElement的目的是为了实现VNodeCall这个接口,transformElement完毕。
transformSlotOutlet
对于插槽处理需要处理传递的模板内容,还需要处理插槽出口,也就是<slot>,需要处理放在出口上可能出现的插槽作用域变量和备用内容以及插槽的名字。
transformSlotOutlet开始先去使用processSlotOutlet解析<slot>上的attr和prop,如果出现除了name以外的属性,说明出现prop,需要使用buildProps构建出slotProps和directive,需要注意的是<slot>不支持运行时指令。
会产生一个slotArgs,这是传递给renderSlot函数的参数,除了$slot是必须的,其他的会根据情况进行变化,比如name没有传递默认就是default。
最后会删除不需要的slotArg之后创建表达式。
transformText
在所有的产生的onExit函数中,transformText的onExit是最先执行的,里面有一个操作可以提高性能:如果插值或者文本或者表达式在一起,并且不是兄弟节点存在元素节点或者其他节点,会进行合并。
首先,vue中会去调用createTextNode去创建文本节点,而插值和表达式最终也会解析完毕后也会变成字符串,存在多个插值或者文本或者表达式,不会一个个用createTextNode去创建,而是解析完拼成一个字符串后再使用createTextNode创建。在transform阶段,会先解析成[exp1,'+', exp2]。
再者就是,如果遇到只带有文本的普通元素,那么请保持原本的,在runtime提供了设置元素的textContent的快速通道。
transformText的最后一步是将前面解析的转换成createTextNode调用。顺便用flag标记动态文本。然后构建出TEXT_CALL。
transformModel
transformModel没有onExit,是直接进行转换,主要是针对v-model指令的派发的修饰符和修饰符,对于表达式只是简单的校验:不能是空表达式或者不是一个可能的是ref的MemberExpression。
首先是创建事件名称和参数,事件名称会根据v-model传递的arg发生变化,比如v-model:updateList,创建出来的事件名称就是onUpdate:updateList,默认会是onUpdate:modelValue。参数是根据是否使用了typescript,选择使用($event: any)或者是($event)。
当然参数处理不会就这样草率,在浏览器下,确实简单,复杂的都是在服务端渲染。
然后就是处理v-model的修饰符,首先是产生一个key,每一个v-model的修饰符都需要一个单独的key,其实就是用arg和Modifiers字符串拼接,arg不存在,默认使用modelModifiers,而修饰符会转换成对象,最后组成一个props返回出去。
transformOn
transformOn主要是处理eventName和modifiers,还有表达式的处理。(这里只关系SSR的情况)。
eventName都会编译成驼峰化的onxxx,比如@click,会变成onClick、@on-test-event变成onTestEvent。唯一区分的是eventName是不是动态的,动态eventName会在运行时再处理,这里只是包装成复合表达式。
在表达式处理开始之前,会先对表达式进行判断,isMemberExp:成员表达式?,inLinineStatement:内联表达式?hasMultipleStatements:存在多条语句?
事件绑定会被处理成props,在处理流程中一大部分逻辑只会在服务端渲染中执行,而在客户端渲染中,只会进行简单的表达式验证(dev模式下),就开始拼接成箭头函数,但是这里还是数组(只有InlineStatement和需要缓存的memberExp才会拼接成箭头函数)。
再往下是执行事件编译增强器,这个方法是从compiler-dom中的vOn.ts中传递过来的,目的是为了处理和修正modifiers。
在vue事件处理中的修饰符一共分为:事件修饰符、按键修饰符、鼠标按钮修饰符、系统修饰符,这是在用法上。如果在编译流程中,会被分为eventOptionModifer、keyModifer、nonKeyModifer,eventOptionsModifier就比较好理解,就是addEventListener ,主要是针对event的处理,比如冒泡、默认行为等,而key和nonKey的区别指modifer放在的event是不是键盘事件(onkeyup、onkeydown、onkeypress)
区分出这三个之后,需要用不同的方法处理,nonKeyModifers和keyModifers通过两个不同的助手函数进行包装,先通过nonKeyModifers包装出一个表达式,再和keyModifers包装成一个表达式,最后在和eventOptionsModifers包装成一个对象。
修正click.right和click.middle,因为它并不会真正的执行,需要修正为正确的事件名称,right => onContextmenu middle => onMouseup。
剩下的部分该缓存缓存、该标记标记,事件处理完成。
transformBind
这里需要注意的是,这里只会处理带有arg的v-bind,不带有arg的v-bind在transformElement中处理了(因为他会影响到整个props的codegen)。
首先需要保证arg不会出现undefined,并且如果出现.canmel,那么arg会被进行驼峰化。绑定属性的时候也会根据修饰符进行判定是attr还是prop,并给他添加前缀(.或者^)。
transformBind最后会产生一个props对象返回出去。
总结
transform阶段的流程过于复杂,代码调用跨度大,其主要作用是先对最外层的root节点先进行优化,再子节点进行优化,这十二个优化皆是在vue中存在着重要作用的。以上仅为个人的分析,还是希望各位哥哥姐姐能指导指导。有说错或者遗漏的欢迎在评论区讲解,谢谢。