这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」
一、前情回顾 & 背景
本篇小作文在讨论 options.start
中的 closeElement
的内部工具方法:processKey
、processRef
、checkInFor
、processSlotContent
:
-
processKey
:检查元素上的key
属性并检查使用key
的限制; -
processRef
:处理ref
属性,若ref
被v-for
包裹,则ref
指向一个数组; -
checkInFor
:检测当前元素的父辈元素中是否有v-for
指令; -
processSlotContent
:处理插槽内容,获取插槽名、动态插槽以及2.6.x
以后的v-slot
指令;
二、 processSlotOutlet
方法作位置:src/compiler/parser/index.js -> function processSlotOutlet
方法参数:el
,ast
节点对象
方法作用:解析自闭和的 slot
标签,获取具名插槽的名字,设置到 el
属性上。
function processSlotOutlet (el) {
if (el.tag === 'slot') {
el.slotName = getBindingAttr(el, 'name')
// 提示信息,不要在 slot 标签上使用 key
if (process.env.NODE_ENV !== 'production' && el.key) {
warn(
``key` does not work on <slot> because slots are abstract outlets ` +
`and can possibly expand into multiple elements. ` +
`Use the key on a wrapping element instead.`,
getRawBindingAttr(el, 'key')
)
}
}
}
复制代码
三、processComponent
方法位置:src/compiler/parser/index.js -> function processComponent
方法参数:el
,ast
节点对象
方法作用:解析动态组件,设置 el.component = is属性绑定的值
,如果 component
标签上存在 inline-template
属性,设置 el.inlineTemplate = true
以下面代码为例:
<component :is="compName" inline-template>
<div>some content,not slot other than compName's template</div>
</component>
复制代码
function processComponent (el) {
let binding
// 解析 is 属性,得到属性值,及组件名称,el.component = compName
if ((binding = getBindingAttr(el, 'is'))) {
el.component = binding
}
// 组件上存在 inline-template 属性,进行标记:el.inlineTemplate = true
// 所谓内联模板就是子组件标签内的内容不作为插槽分发,而是将其中内容作为该组件模板
if (getAndRemoveAttr(el, 'inline-template') != null) {
el.inlineTemplate = true
}
}
复制代码
四、 transforms
transforms
不是一个方法,而是一个方法数组,这个数组是通过 pluckModuleFunction(options.modules, 'transformNode')
得来的,就像前面的 preTransformNode、postTransform
方法;
再来复习一下下,options.modules
来自 createCompiler(baseOpitons)
,baseOptions
中的 modules
有 klass,style,model
三项目,pluckModuleFunctions
就是从这三个中挑出指定的方法组成一个数组;
transformNode
方法只有 klass
和 style
导出了,model
没有导出;所以 transforms
数组中有两个方法,分别用于处理 ast
的 class
类名和 style
样式;
其中类名处理是为了获取静态类名和动态类名,所谓动态类名就是使用 Vue
动态类名的语法比如:v-bind:class
、:class
等,style
也是同理;处理过后为 el
新增 el.staticClass
、el.classBinding
以及 el.staticStyle
和 el.styleBinding
;
4.1 transformNode <- modules/style.js
方法位置:src/platforms/web/compiler/modules/style.js -> functions transformNode
方法参数:
el
,ast
节点对象options:createCompiler(baseOptions)
方法接收到的就是了
方法作用:从 el
上解析出静态的 style属性动态绑定的
v-bin:style / :style 属性值,分别赋值给
el.staticStyle和
el.styleBinding`
function transformNode (el: ASTElement, options: CompilerOptions) {
const warn = options.warn || baseWarn
// 获取 <div style="color:red"></div> 中 coloer: red
const staticStyle = getAndRemoveAttr(el, 'style')
if (staticStyle) {
if (process.env.NODE_ENV !== 'production') {
const res = parseText(staticStyle, options.delimiters)
// 提示,如果从 xx 中解析到界定符,说明是一个动态的 style
// 比如 <div style={{}} > 给出提示,用 :style="val"
if (res) {
warn(
`style="${staticStyle}": ` +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div style="{{ val }}">, use <div :style="val">.',
el.rawAttrsMap['style']
)
}
}
// 静态 style 样式赋值给 el.staticStyle
el.staticStyle = JSON.stringify(parseStyleText(staticStyle))
}
// 获取动态样式绑定 style 属性,比如 <div :style="val"></div>
// 赋值给 el.styleBinding
const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)
if (styleBinding) {
el.styleBinding = styleBinding
}
}
复制代码
4.2 tranfromNode <- modules/class.js
function transformNode (el: ASTElement, options: CompilerOptions) {
// 日志
const warn = options.warn || baseWarn
// <div class="someStaticClassName"></div>
// 获取元素上的 class 属性的值 someStaticClassName
const staticClass = getAndRemoveAttr(el, 'class')
if (process.env.NODE_ENV !== 'production' && staticClass) {
const res = parseText(staticClass, options.delimiters)
// 提示,同 style 提示一样,不能用 <div class={{}}></div> 这种花括号语法
// <div :class="val"></div> 代替
if (res) {
warn(
`class="${staticClass}": ` +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div class="{{ val }}">, use <div :class="val">.',
el.rawAttrsMap['class']
)
}
}
// 静态 class 属性赋值给 el.staticClass
if (staticClass) {
el.staticClass = JSON.stringify(staticClass.replace(/\s+/g, ' ').trim())
}
// 获取动态绑定的 class 属性值,并复制给 el.classBinding
const classBinding = getBindingAttr(el, 'class', false /* getStatic */)
if (classBinding) {
el.classBinding = classBinding
}
}
复制代码
五、processAttrs
方法位置:src/compiler/parser/index.js -> function processAttrs
方法参数:el
,ast
节点对象
方法作用:遍历 attrsList
上处理 el
上的属性,获取每一项的 name,value
分别是详细步骤:
- 判断属性是否是指令,如果是:
- 1.1 设置
el.hasBinding = true
; - 1.2 解析指令上的修饰符保存到
modifiers
变量,然后将属性上的修饰符部分替换为空字符串; - 1.3 用正则检查属性
name
是否使用v-bind 或 :
动态绑定了值,将属性name
的动态绑定v-bind 或 :
替换为空,只留下被绑定的属性本身;接着解析value
上的过滤器; - 1.4 然后用正则匹配指令上是否有动态参数,结果赋值个
isDynamic
,所谓动态参数,例如<a v-bind:[attributeName]="url"> ... </a>
方括号中attrbuteName
就是动态参数,当有动态参数时获取动态参数时获取方括号中的参数名,即name.slice(1, -1)
- 1.5 如果修饰符
modifiers
不为空时,若没有动态参数时处理prop、camel
修饰符;处理sync
修饰符,sync
修饰符就是个update:属性名
事件名的语法糖,事件触发时执行syncGen
代码,这部分代码负责更新数据(赋值或者$set
) - 1.6 如果是
v-on
指令,处理动态参数名,并获取不带v-on
或者@
的事件名,如果是动态绑定参数则获取不含方括号的部分,例如<div v-on:[someDynamicEventName]></div>
someDynamicEventName
,然后调用addHandler
重新绑定事件; - 1.7 最后是普通指令,获取普通指令名、参数等、判断是否有动态参数,然后调用
addDirective
增加指令:el.directives.push()
- 1.1 设置
- 如果不是指令说明就是普通的字面量的属性,接着调用
addAttr()
把name
和value
设置到el.dynamicAttrs
或者el.attrs
,这里就可以很清晰明了的发现attrs
是静态属性,将el.plain
设为false
;如果不是组件且name
是'muted'
且必须使用prop
绑定属性时调用addProp
绑定muted
属性;
function processAttrs (el) {
const list = el.attrsList
let i, l, name, rawName, value, modifiers, syncGen, isDynamic
for (i = 0, l = list.length; i < l; i++) {
name = rawName = list[i].name
value = list[i].value
if (dirRE.test(name)) {
// 如果是指令
// mark element as dynamic
el.hasBindings = true
// 修饰符
modifiers = parseModifiers(name.replace(dirRE, ''))
// 支持 .foo 简写的语法修饰符
if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {
(modifiers || (modifiers = {})).prop = true
name = `.` + name.slice(1).replace(modifierRE, '')
} else if (modifiers) {
// 不含有修饰符的名字
name = name.replace(modifierRE, '')
}
if (bindRE.test(name)) { // v-bind
name = name.replace(bindRE, '')
value = parseFilters(value)
isDynamic = dynamicArgRE.test(name) // 是否是动态参数
if (isDynamic) {
name = name.slice(1, -1)
}
if (
process.env.NODE_ENV !== 'production' &&
value.trim().length === 0
) {
warn(
`The value for a v-bind expression cannot be empty. Found in "v-bind:${name}"`
)
}
if (modifiers) {
if (modifiers.prop && !isDynamic) {
name = camelize(name)
if (name === 'innerHtml') name = 'innerHTML'
}
if (modifiers.camel && !isDynamic) {
name = camelize(name)
}
if (modifiers.sync) {
// 处理 sync 修饰符,这就是 sync 修饰符的原理
syncGen = genAssignmentCode(value, `$event`)
if (!isDynamic) {
addHandler(
el,
`update:${camelize(name)}`,
syncGen,
null,
false,
warn,
list[i]
)
if (hyphenate(name) !== camelize(name)) {
addHandler(
el,
`update:${hyphenate(name)}`,
syncGen,
null,
false,
warn,
list[i]
)
}
} else {
// handler w/ dynamic event name
addHandler(
el,
`"update:"+(${name})`,
syncGen,
null,
false,
warn,
list[i],
true // dynamic
)
}
}
}
if ((modifiers && modifiers.prop) || (
!el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
)) {
addProp(el, name, value, list[i], isDynamic)
} else {
addAttr(el, name, value, list[i], isDynamic)
}
} else if (onRE.test(name)) { // v-on
name = name.replace(onRE, '')
isDynamic = dynamicArgRE.test(name)
if (isDynamic) {
name = name.slice(1, -1)
}
addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
} else { // normal directives
name = name.replace(dirRE, '')
// parse arg
const argMatch = name.match(argRE)
let arg = argMatch && argMatch[1]
isDynamic = false
if (arg) {
name = name.slice(0, -(arg.length + 1))
if (dynamicArgRE.test(arg)) {
arg = arg.slice(1, -1)
isDynamic = true
}
}
addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
if (process.env.NODE_ENV !== 'production' && name === 'model') {
checkForAliasModel(el, value)
}
}
} else {
// literal attribute
if (process.env.NODE_ENV !== 'production') {
const res = parseText(value, delimiters)
if (res) {
warn(
`${name}="${value}": ` +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div id="{{ val }}">, use <div :id="val">.',
list[i]
)
}
}
addAttr(el, name, JSON.stringify(value), list[i])
// #6887 firefox doesn't update muted state if set via attribute
// even immediately after element creation
if (!el.component &&
name === 'muted' &&
platformMustUseProp(el.tag, el.attrsMap.type, name)) {
addProp(el, name, 'true', list[i])
}
}
}
}
复制代码
六、总结
本篇小作文继续讨论了 closeElement
的工具方法,大致如下:
-
processSlotOutlet
:解析自闭和的slot
标签,获取插槽名字设置到el
上; -
processComponent
:解析动态组件,解析is
属性和inline-template
-
transforms
:class/style
模块导出的transoformNode
方法处理静态、动态的类名或样式 -
processAttrs
:处理元素上的attrs
,处理指令的参数、事件绑定、修饰符等;
这一篇就是 closeElment
的终结篇,本篇过后 options.start
的所有逻辑也就全部完成了