「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」。
一、前情回顾 & 背景
本文接着前文讨论了 options.start 的工具方法:
-
processPre:判断元素上是否有v-pre指令,有的话设置el.pre为true; -
getAndRemoveAttr:从el.attrsMap中获取指定属性名的属性值,然后从el.attrsList数组中删除这个属性,如果传递了removeFromMap也要从attrsMap中删除掉这个属性; -
platformIsPreTag:判断是否为 pre 标签; -
processRawAttr:将元素上的属性复制到el.attrs数组中,el.attrs数组的属性均为静态属性,当数据发生变化时忽略这些属性; -
processFor: 处理v-for指令,得到{ for: 可迭代对象, alias: 可迭代对象条目名 };
本文接着讨论 options.start 的工具方法:processIf、processOnce、checkRootConstraints、closeElement` 及其部分内部方法
二、processIf
方法位置:src/compiler/parser/index.js -> fucntion processIf
方法参数:el,ast 节点对象
方法作用:处理 ast 节点对象上的 v-if、v-else-if、v-else 属性,解析这些表达式的值分别挂载到 el.if、el.elseif、el.else 上;
解析到 v-if 时调用 addIfCondition(el, { exp, block }) 给 el.ifConditions 增加项;exp 是 v-if 表达式,block 是 el 这个 ast 节点,标识当 exp 代表的条件成立时,渲染 block 代表的 ast 节点
function processIf (el) {
const exp = getAndRemoveAttr(el, 'v-if')
if (exp) {
el.if = exp
addIfCondition(el, {
exp: exp,
block: el
})
} else {
// v-else 解析到的时候,el.else 值为 true
if (getAndRemoveAttr(el, 'v-else') != null) {
el.else = true
}
const elseif = getAndRemoveAttr(el, 'v-else-if')
if (elseif) {
el.elseif = elseif
}
}
}
2.1 addIfCondition
方法位置:src/compiler/parser/index.js -> function addIfCondition
方法参数:
el,AST节点对象condition:{ exp, block },exp是v-if表达式的值,即条件,block是exp所代表的条件成立时渲染的AST元素
方法作用:给 ast 节点对象的 ifConditions 添加条件对象,如果 el.ifConditions 不存在,则创建 ifConditions 属性值为空数组;
export function addIfCondition (el: ASTElement, condition: ASTIfCondition) {
if (!el.ifConditions) {
el.ifConditions = []
}
el.ifConditions.push(condition)
}
三、processOnce
方法位置:src/compiler/parser/index.js -> function processOnce
方法参数:element,ast 节点对象
方法作用:处理 v-once 指令,如果 ast 节点上存在 v-once 指令给 element.once = true;
function processOnce (el) {
const once = getAndRemoveAttr(el, 'v-once')
if (once != null) {
el.once = true
}
}
四、checkRootConstraints
方法位置:src/compiler/parser/index.js -> function checkRootConstraints
方法参数:el,ast 节点对象
方法作用:检查 root 节点上的一些限制条件,作为模板上的根节点时,有一些能力是要被限制使用的,比如 <slot></slot> 或者 <template></template> 标签是不能作为根元素的,此外根节点上也不能使用 v-for 指令
function checkRootConstraints (el) {
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(
`Cannot use <${el.tag}> as component root element because it may ` +
'contain multiple nodes.',
{ start: el.start }
)
}
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.',
el.rawAttrsMap['v-for']
)
}
}
五、closeElement
方法位置:src/compiler/parser/index.js -> functions closeElement
方法参数:element,ast 节点对象
方法作用:这个 options.start 中的 closeElement 是当判断元素为自闭和标签时,在处理 options.start 即开始标签处理时就直接处理闭合逻辑。
closeElement 主要做了以下几件事:
- 调用
trimEndingWhitespace移除自己节点中的空白符 - 如果
element.processed不为true,则调用processElement处理节点上的属性; - 如果
stack不为空且当前参数elment不是root时,就要看下stack不为空的原因,如果是root上使用v-if、v-else-if等条件语句,这是允许的;但是如果stack不为空又没有使用v-if这是就说明有多个根元素,这在Vue里是不允许的,给出提示; - 组织节点关系,
- 4.1 如果
element上有v-else-if或者v-else,调用processIfConditions处理当前element和currentParent的关系; - 4.2 否则,判断是否有
element.slotScope,有的话向currentParent.scopedSlots新增一项,key是element.slotTarget或者"default", - 4.3 值为当前
element;最后将当前元素push到currentParent.children中,然后将element.parent设置为currentParent;当然,closeElement在处理非自闭合标签时还会将所有的非插槽子元素该元素添加到currentParent.children中;
- 4.1 如果
- 处理
element.children,使之最后的值不包含作用域插槽,此后再次进行trimEndingWhitespace; - 维护
inVPre、inPre - 调用
postTransforms中的方法,对element进行后处理;postTransforms是pluckModuleFunction(options.modules, 'postTransformNode'),在web平台下,modules中的klass/style/model没有任何一个导出了postTransformNode方法,所以postTransforms是空数组
function closeElement (element) {
trimEndingWhitespace(element)
if (!inVPre && !element.processed) {
element = processElement(element, options)
}
// tree management
if (!stack.length && element !== root) {
// 如果 stack 不为空,并且 root 上使用 v-if/v-else-if/v-else,这是允许的
if (root.if && (element.elseif || element.else)) {
addIfCondition(root, {
exp: element.elseif,
block: element
})
} else if (process.env.NODE_ENV !== 'production') {
// 否则就说明有多个根节点了,此时不被允许,给出提示
}
}
if (currentParent && !element.forbidden) {
if (element.elseif || element.else) {
processIfConditions(element, currentParent)
} else {
if (element.slotScope) {
// scoped slot
// keep it in the children list so that v-else(-if) conditions can
// find it as the prev node.
const name = element.slotTarget || '"default"'
;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
}
currentParent.children.push(element)
element.parent = currentParent
}
}
// element.children 不包含作用域插槽
element.children = element.children.filter(c => !(c: any).slotScope)
// 再次移除 children 中的空白符
trimEndingWhitespace(element)
// 维护 inVPre、inPre,
// 如果 element.pre 说明当前元素使用了 v-pre
// 现在是 closeElement,需要再下一次开始处理开始标签之前将 inVPre、inPre 置为 false
if (element.pre) {
inVPre = false
}
if (platformIsPreTag(element.tag)) {
inPre = false
}
// 调用 postTransforms 的处理方法
for (let i = 0; i < postTransforms.length; i++) {
postTransforms[i](element, options)
}
}
5.1 trimEndingWhitespace
方法位置:src/compiler/parser/index.js -> trimEndingWhitespace
方法参数:el,ast 节点对象
方法作用:如果不在 pre 标签中,移除当前节点的所有子元素中的空白元素
function trimEndingWhitespace (el) {
if (!inPre) {
let lastNode
while (
(lastNode = el.children[el.children.length - 1]) &&
lastNode.type === 3 &&
lastNode.text === ' '
) {
el.children.pop()
}
}
}
5.2 processElement
方法位置:src/compiler/parser/index.js -> function processElement
方法参数:
element:ast节点对象;options:createCompiler接收的baseOptions
方法作用:分别处理元素的 key、ref、插槽、自闭和slot、动态组件、class、style、v-bind、v-on等指令,将处理所得信息添加到元素上,最后返回 element;
export function processElement (
element: ASTElement,
options: CompilerOptions
) {
// 处理元素上的 key:设置 el.key = val
processKey(element)
element.plain = (
!element.key &&
!element.scopedSlots &&
!element.attrsList.length
)
// 处理 ref 属性
processRef(element)
// 处理插槽内容
processSlotContent(element)
processSlotOutlet(element)
processComponent(element)
for (let i = 0; i < transforms.length; i++) {
element = transforms[i](element, options) || element
}
processAttrs(element)
return element
}
5.3 processIfConditions
方法位置:src/compiler/parser/index.js -> function processIfConditions
方法参数:
el,ast节点对象;parent,el的父元素ast节点对象;
方法作用:从 parent children 中找到前一个元素节点 prev,如果 prev 有 if 属性,就调用 addIfConditions 添加 elseif 或者 else block
function processIfConditions (el, parent) {
const prev = findPrevElement(parent.children)
if (prev && prev.if) {
addIfCondition(prev, {
exp: el.elseif,
block: el
})
} else if (process.env.NODE_ENV !== 'production') {
warn(
`v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` +
`used on element <${el.tag}> without corresponding v-if.`,
el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else']
)
}
}
六、总结
本篇小作文继续讨论了 options.start 方法的内部方法的作用:
-
processIf:处理ast节点对象上的v-if/v-else-if/v-else,将这些条件渲染信息添加到el.if/el.elseif/el.else; -
processOnce:处理v-once,得到el.once = true; -
checkRootContraints:检查根元素的限制,例如不能用slot和template作为根; -
以及
options.start接收到自闭和标签时执行的closeElement及其工具方法,这里并没有说完closeElement方法,后面两篇将会这个讲述这个过程