前言
继续了解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中存在着重要作用的。以上仅为个人的分析,还是希望各位哥哥姐姐能指导指导。有说错或者遗漏的欢迎在评论区讲解,谢谢。