preTransformNode() 方法
文件位置:src\platforms\web\compiler\modules\model.js
这里涉及到了下面几个方法:
- getBindingAttr
- getAndRemoveAttr
- processFor
- addRawAttr
- processElement
/* * 处理存在 v-model 的 input 标签,但没处理 v-model 属性 * 分别处理了 input 为 checkbox、radio 和 其它的情况 * input 具体是哪种情况由 el.ifConditions 中的条件来判断 * <input v-mode="test" :type="checkbox || radio || other(比如 text)" /> * @param {*} el * @param {*} options * @returns branch0 */ function preTransformNode (el: ASTElement, options: CompilerOptions) { // 属于 input 标签 if (el.tag === 'input') { const map = el.attrsMap // 不存在 v-model 属性,直接结束 if (!map['v-model']) { return } // 获取 :type 的值 let typeBinding if (map[':type'] || map['v-bind:type']) { typeBinding = getBindingAttr(el, 'type') } if (!map.type && !typeBinding && map['v-bind']) { typeBinding = `(${map['v-bind']}).type` } // type 类型存在 if (typeBinding) { // 获取 v-if 的值,比如: <input v-model="test" :type="checkbox" v-if="isShow" /> const ifCondition = getAndRemoveAttr(el, 'v-if', true) // 得到 &&isShow const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : `` // 是否存在 v-else 属性,<input v-else /> const hasElse = getAndRemoveAttr(el, 'v-else', true) != null // 获取 v-else-if 属性的值 <inpu v-else-if="isShow" /> const elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true) /* 克隆一个新的 el 对象,分别处理 input 为 chekbox、radio 或 其它的情况 具体是哪种情况,通过 el.ifConditins 条件来判断 */ // 1. checkbox const branch0 = cloneASTElement(el) // process for on the main node /* <input v-for="item in arr" :key="item" /> 处理 v-for 表达式,得到: branch0.for = arr; branch0.alias = item; */ processFor(branch0) // 在 branch0.attrsMap 和 branch0.attrsList 对象中添加 type 属性 addRawAttr(branch0, 'type', 'checkbox') /* 分别处理元素节点: key、ref、插槽、自闭合的 slot 标签、动态组件、class、style、v-bind、v-on、 其它指令和一些原生属性 */ processElement(branch0, options) // 标记当前对象已经被处理过了 branch0.processed = true // prevent it from double-processed // 得到 true&&isShow || false&&isShow,标记当前 input 是否为 checkbox branch0.if = `(${typeBinding})==='checkbox'` + ifConditionExtra // 在 branch0.ifConfitions 数组中放入 { exp, block } 对象 addIfCondition(branch0, { exp: branch0.if, block: branch0 }) // 克隆一个新的 ast 对象 // 2. add radio else-if condition const branch1 = cloneASTElement(el) // 获取 v-for 属性值 getAndRemoveAttr(branch1, 'v-for', true) // 在 branch1.attrsMap 和 branch1.attrsList 对象中添加 type 属性 addRawAttr(branch1, 'type', 'radio') /* 分别处理元素节点: key、ref、插槽、自闭合的 slot 标签、动态组件、class、style、v-bind、v-on、 其它指令和一些原生属性 */ processElement(branch1, options) addIfCondition(branch0, { exp: `(${typeBinding})==='radio'` + ifConditionExtra, block: branch1 }) // 3. other input 为其它的情况 const branch2 = cloneASTElement(el) // 获取 v-for 属性 getAndRemoveAttr(branch2, 'v-for', true) addRawAttr(branch2, ':type', typeBinding) processElement(branch2, options) addIfCondition(branch0, { exp: ifCondition, block: branch2 }) // 给 branch0 设置 else 或 elseif 条件 if (hasElse) { branch0.else = true } else if (elseIfCondition) { branch0.elseif = elseIfCondition } // 返回 return branch0 } } } 复制代码
getBindingAttr、getAndRemoveAttr、addRawAttr 方法
文件位置:src\compiler\helpers.js
getBindingAttr
// 获取 el 对象上执行属性 name 的值 export function getBindingAttr ( el: ASTElement, name: string, getStatic?: boolean ): ?string { // 获取指定属性的值 const dynamicValue = getAndRemoveAttr(el, ':' + name) || getAndRemoveAttr(el, 'v-bind:' + name) if (dynamicValue != null) { return parseFilters(dynamicValue) } else if (getStatic !== false) { const staticValue = getAndRemoveAttr(el, name) if (staticValue != null) { return JSON.stringify(staticValue) } } } 复制代码
getAndRemoveAttr
/** 从 el.attrsList 中删除指定的属性 name 如果 removeFromMap 为 true,则同样删除 el.attrsMap 对象中的该属性, 比如 v-if、v-else-if、v-else 等属性就会被移除, 不过一般不会删除该对象上的属性,因为从 ast 生成 代码期间还需要使用该对象,返回指定属性的值 */ export function getAndRemoveAttr ( el: ASTElement, name: string, removeFromMap?: boolean ): ?string { let val // 将执行属性 name 从 el.attrsList 中移除 if ((val = el.attrsMap[name]) != null) { const list = el.attrsList for (let i = 0, l = list.length; i < l; i++) { if (list[i].name === name) { list.splice(i, 1) break } } } // 如果 removeFromMap 为 true,则从 el.attrsMap 中移除指定的属性 name // 不过一般不会移除 el.attsMap 中的数据,因为从 ast 生成 代码期间还需要使用该对象 if (removeFromMap) { delete el.attrsMap[name] } // 返回执行属性的值 return val } 复制代码
addRawAttr
// 获取 el 对象上执行属性 name 的值 export function getBindingAttr ( el: ASTElement, name: string, getStatic?: boolean ): ?string { // 获取指定属性的值 const dynamicValue = getAndRemoveAttr(el, ':' + name) || getAndRemoveAttr(el, 'v-bind:' + name) if (dynamicValue != null) { return parseFilters(dynamicValue) } else if (getStatic !== false) { const staticValue = getAndRemoveAttr(el, name) if (staticValue != null) { return JSON.stringify(staticValue) } } } 复制代码
processFor、processRef、processKey、processElement
文件位置:/src/compiler/parser/index.js
processFor
/** * 处理 v-for,将结果设置到 el 对象上,得到: * el.for = 可迭代对象,比如 arr * el.alias = 别名,比如 item * @param {*} el 元素的 ast 对象 */ export function processFor (el: ASTElement) { let exp // 获取 el 上的 v-for 属性的值 if ((exp = getAndRemoveAttr(el, 'v-for'))) { // 解析 v-for 的表达式,得到 { for: 可迭代对象, alias: 别名 } // 比如 { for: arr, alias: item } const res = parseFor(exp) if (res) { // 将 res 对象上的属性拷贝到 el 对象上 extend(el, res) } else if (process.env.NODE_ENV !== 'production') { warn( `Invalid v-for expression: ${exp}`, el.rawAttrsMap['v-for'] ) } } } 复制代码
processRef
/** * 处理元素上的 ref 属性 * el.ref = refVal * el.refInFor = boolean */ function processRef (el) { const ref = getBindingAttr(el, 'ref') if (ref) { el.ref = ref // 判断包含 ref 属性的元素是否包含在具有 v-for 指令的元素内或后代元素中 // 如果是,则 ref 指向的则是包含 DOM 节点或组件实例的数组 el.refInFor = checkInFor(el) } } 复制代码
processKey
// 处理元素上的 key 属性,设置 el.key = val function processKey (el) { // 拿到 key 的属性值 const exp = getBindingAttr(el, 'key') // 关于 key 使用上的异常处理 if (exp) { // template 标签不允许设置 key if (process.env.NODE_ENV !== 'production') { if (el.tag === 'template') { warn( `<template> cannot be keyed. Place the key on real elements instead.`, getRawBindingAttr(el, 'key') ) } // 不要在 <transition=group> 的子元素上使用 v-for 的 index 作为 key // 否则等价与没有使用 key if (el.for) { const iterator = el.iterator2 || el.iterator1 const parent = el.parent if (iterator && iterator === exp && parent && parent.tag === 'transition-group') { warn( `Do not use v-for index as key on <transition-group> children, ` + `this is the same as not using keys.`, getRawBindingAttr(el, 'key'), true /* tip */ ) } } } // 设置 el.key = exp el.key = exp } } 复制代码
processElement
/** * 分别处理元素节点的 key、ref、插槽、自闭合的 slot 标签、动态组件、class、style、v-bind、v-on、其它指令和一些原生属性 * 然后在 el 对象上添加如下属性: * el.key、ref、refInFor、scopedSlot、slotName、component、inlineTemplate、staticClass * el.bindingClass、staticStyle、bindingStyle、attrs * @param {*} element 被处理元素的 ast 对象 * @param {*} options 配置项 * @returns */ export function processElement ( element: ASTElement, options: CompilerOptions ) { processKey(element) // determine whether this is a plain element after // removing structural attributes // 确定 element 是否为一个普通元素 element.plain = ( !element.key && !element.scopedSlots && !element.attrsList.length ) // el.ref = val, el.refInFor = boolean processRef(element) // 处理作为插槽传递给组件的内容,得到 插槽名称、是否为动态插槽、作用域插槽的值, // 以及插槽中的所有子元素,子元素放到插槽对象的 children 属性中 processSlotContent(element) // 处理自闭合的 slot 标签,得到插槽名称 => el.slotName = xx processSlotOutlet(element) // 处理动态组件,<component :is="compoName"></component>得到 el.component = compName, // 以及标记是否存在内联模版,el.inlineTemplate = true of false processComponent(element) /* 为 element 对象分别执行 class、style、model 模块中的 transformNode 方法 不过 web 平台只有 class、style 模块有 transformNode 方法,分别用来处理 class 属性和 style 属性 得到 el.staticStyle、 el.styleBinding、el.staticClass、el.classBinding 分别存放静态 style 属性的值、动态 style 属性的值,以及静态 class 属性的值和动态 class 属性的值 */ for (let i = 0; i < transforms.length; i++) { element = transforms[i](element, options) || element } /** 处理元素上的所有属性: 1. v-bind 指令变成:el.attrs 或 el.dynamicAttrs = [{ name, value, start, end, dynamic }, ...], 或者是必须使用 props 的属性,变成了 el.props = [{ name, value, start, end, dynamic }, ...] 2. v-on 指令变成:el.events 或 el.nativeEvents = { name: [{ value, start, end, modifiers, dynamic }, ...] } 3. 其它指令:el.directives = [{name, rawName, value, arg, isDynamicArg, modifier, start, end }, ...] 4. 原生属性:el.attrs = [{ name, value, start, end }],或者一些必须使用 props 的属性, 变成了:el.props = [{ name, value: true, start, end, dynamic }] */ processAttrs(element) return element } 复制代码
processSlotContent() 方法
文件位置:/src/compiler/parser/index.js
/* 处理作为插槽传递给组件的内容,得到: slotTarget => 插槽名 slotTargetDynamic => 是否为动态插槽 slotScope => 作用域插槽的值 直接在 <comp> 标签上使用 v-slot 语法时,将上述属性放到 el.scopedSlots 对象上,其它情况直接放到 el 对象上 handle content being passed to a component as slot, e.g. <template slot="xxx">, <div slot-scope="xxx"> */ function processSlotContent (el) { let slotScope if (el.tag === 'template') { /* template 标签上使用 scope 属性的提示 scope 已经弃用,并在 2.5 之后使用 slot-scope 代替 slot-scope 即可以用在 template 标签也可以用在普通标签上 */ slotScope = getAndRemoveAttr(el, 'scope') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && slotScope) { warn( `the "scope" attribute for scoped slots have been deprecated and ` + `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` + `can also be used on plain elements in addition to <template> to ` + `denote scoped slots.`, el.rawAttrsMap['scope'], true ) } // el.slotScope = val el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope') } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) { warn( `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` + `(v-for takes higher priority). Use a wrapper <template> for the ` + `scoped slot to make it clearer.`, el.rawAttrsMap['slot-scope'], true ) } el.slotScope = slotScope } // slot="xxx" // 获取 slot 属性的值 // slot="xxx",旧的具名插槽的写法 const slotTarget = getBindingAttr(el, 'slot') if (slotTarget) { // el.slotTarget = 插槽名(具名插槽) el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget // 动态插槽名 el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot']) // preserve slot as an attribute for native shadow DOM compat // only for non-scoped slots. if (el.tag !== 'template' && !el.slotScope) { addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot')) } } // 2.6 v-slot syntax if (process.env.NEW_SLOT_SYNTAX) { if (el.tag === 'template') { // v-slot on <template> // v-slot 在 tempalte 标签上,得到 v-slot 的值 // v-slot on <template> const slotBinding = getAndRemoveAttrByRegex(el, slotRE) if (slotBinding) { if (process.env.NODE_ENV !== 'production') { if (el.slotTarget || el.slotScope) { // 不同插槽语法禁止混合使用 warn( `Unexpected mixed usage of different slot syntaxes.`, el ) } if (el.parent && !maybeComponent(el.parent)) { /* <template v-slot> 只能出现在组件的根位置,比如: <comp> <template v-slot>xx</template> </comp> 而不能是 <comp> <div> <template v-slot>xxx</template> </div> </comp> */ warn( `<template v-slot> can only appear at the root level inside ` + `the receiving component`, el ) } } // 得到插槽名称 const { name, dynamic } = getSlotName(slotBinding) // 将插槽名称保存到 el.slotTarget 上 el.slotTarget = name // 是否为动态插槽 el.slotTargetDynamic = dynamic // 作用域插槽的值 el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf } } else { /* 处理组件上的 v-slot,<comp v-slot:header /> slotBinding = { name: "v-slot:header", value: "", start, end} v-slot on component, denotes default slot */ const slotBinding = getAndRemoveAttrByRegex(el, slotRE) if (slotBinding) { if (process.env.NODE_ENV !== 'production') { // el 不是组件的话,提示,v-slot 只能出现在组件上或 template 标签上 if (!maybeComponent(el)) { warn( `v-slot can only be used on components or <template>.`, slotBinding ) } // 语法混用 if (el.slotScope || el.slotTarget) { warn( `Unexpected mixed usage of different slot syntaxes.`, el ) } // 为了避免作用域歧义,当存在其他命名槽时,默认槽也应该使用<template>语法 if (el.scopedSlots) { warn( `To avoid scope ambiguity, the default slot should also use ` + `<template> syntax when there are other named slots.`, slotBinding ) } } // 将组件的孩子添加到它的默认插槽内 // add the component's children to its default slot const slots = el.scopedSlots || (el.scopedSlots = {}) // 获取插槽名称以及是否为动态插槽 const { name, dynamic } = getSlotName(slotBinding) // 创建一个 template 标签的 ast 对象,用于容纳插槽内容,父级是 el const slotContainer = slots[name] = createASTElement('template', [], el) // 插槽名 slotContainer.slotTarget = name // 是否为动态插槽 slotContainer.slotTargetDynamic = dynamic // 所有的孩子,将每一个孩子的 parent 属性都设置为 slotContainer slotContainer.children = el.children.filter((c: any) => { if (!c.slotScope) { // 给插槽内元素设置 parent 属性为 slotContainer,也就是 template 元素 c.parent = slotContainer return true } }) slotContainer.slotScope = slotBinding.value || emptySlotScopeToken // remove children as they are returned from scopedSlots now el.children = [] // mark el non-plain so data gets generated el.plain = false } } } }