前言
前面的文章中提到组件更新时,需要先执行编译器生成的 渲染函数 得到组件的 vnode,而 渲染函数 生成 vnode 则是通过其中的 _c、_l、、_v、_s
等方法实现的. 比如:
普通节点
被编译成了可执行_c
函数v-for
节点被编译成了可执行的_l
函数- ... 那么下面就一起来了解一下,什么是 render helper ?
深入源码
入口位置
通过 generate()
方法的返回值中的 { render: 'with(this){return ${code}}' }
可知,_c、_l、、_v、_s
等方法是属于实例上的方法,当然这需要你对 js
中 with(){}
方法有一定的了解.
既然是实例方法,那么在之前 vue 中的实例 文章中有提到对应文件位置:src\core\instance\render.js
.
export function generate( ast: ASTElement | void, // ast 对象 options: CompilerOptions // 编译选项 ): CodegenResult { /* 实例化 CodegenState 对象,参数是编译选项,最终得到 state ,其中大部分属性和 options 一样 */ const state = new CodegenState(options) /* 生成字符串格式的代码,比如:'_c(tag, data, children, normalizationType)' - data 为节点上的属性组成 JSON 字符串,比如 '{ key: xx, ref: xx, ... }' - children 为所有子节点的字符串格式的代码组成的字符串数组,格式: `['_c(tag, data, children)', ...],normalizationType`, - normalization 是 _c 的第四个参数,表示节点的规范化类型(非重点,可跳过) 注意:code 并不一定就是 _c,也有可能是其它的,比如整个组件都是静态的,则结果就为 _m(0) */ const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")' return { render: `with(this){return ${code}}`, staticRenderFns: state.staticRenderFns } } 复制代码
installRenderHelpers() 方法
根据上面的 src\core\instance\render.js
找到了对应的 renderMixin()
,其中很明显 installRenderHelpers()
就是我们需要找的.
export function renderMixin (Vue: Class<Component>) { // install runtime convenience helpers // 在组件实例上挂载一些运行时需要用到的工具方法 installRenderHelpers(Vue.prototype) // ... } 复制代码
文件位置:src\core\instance\render-helpers\index.js
/** * 在实例上挂载简写的渲染工具函数,这些都是运行时代码 * 这些工具函数在编译器生成的渲染函数中被使用到了 * @param target Vue 实例 */ export function installRenderHelpers (target: any) { /** v-once 指令的运行时帮助程序,为 VNode 加上打上静态标记 注意:包含 v-once 指令的节点都被当作静态节点处理: node.isStatic = true node.key = key node.isOnce = true */ target._o = markOnce // 使用 parseFloat() 将值转换为数字 target._n = toNumber /* 如果传入值为 null,直接返回 '' 如果是数组或者是普通对象,调用 JSON.Stringify() 序列化引用类型 否则使用 String 转换为字符串 */ target._s = toString /* 运行时渲染 v-for 列表的帮助函数,循环遍历 val 值, 依次为每一项执行 render 方法生成 VNode,最终返回一个 VNode 数组 */ target._l = renderList target._t = renderSlot /* 判断两个值是否相等:数组、普通对象、Date 对象、基本类型(全部转成字符串比较) */ target._q = looseEqual /** 相当于数组的 indexOf() 方法,本质就是调用 looseEqual() */ target._i = looseIndexOf /* 运行时负责生成静态树的 VNode 的帮助程序: 1、执行 staticRenderFns 数组中指定下标的渲染函数,生成静态树的 VNode 并缓存, 下次在渲染时从缓存中直接读取(isInFor 必须为 true) 2、为静态树的 VNode 打静态标记 */ target._m = renderStatic target._f = resolveFilter target._k = checkKeyCodes target._b = bindObjectProps /* 为文本节点创建 VNode,其中: VNode.text = 'xxx' VNode.isComment = false */ target._v = createTextVNode /* 为空节点创建 VNode,其中: VNode.text = 'xxx' VNode.isComment = true */ target._e = createEmptyVNode target._u = resolveScopedSlots target._g = bindObjectListeners target._d = bindDynamicKeys target._p = prependModifier } 复制代码
target._o = markOnce
文件位置:src\core\instance\render-helpers\index.js
markOnce() 方法
/** * Runtime helper for v-once. * Effectively it means marking the node as static with a unique key. * 使用唯一的 key 将节点标记为静态节点 */ export function markOnce ( tree: VNode | Array<VNode>, index: number, key: string ) { /* <div v-once key="mydiv"><div> 保证 key 的唯一,即 key = `__once__${index}_mydiv` */ markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true) return tree } 复制代码
markStatic() 方法
function markStatic ( tree: VNode | Array<VNode>, key: string, isOnce: boolean ) { // tree 为数组 if (Array.isArray(tree)) { // 遍历 tree 为每个 tree[i] 通过 markStaticNode() 进行标记 for (let i = 0; i < tree.length; i++) { if (tree[i] && typeof tree[i] !== 'string') { markStaticNode(tree[i], `${key}_${i}`, isOnce) } } } else { // tree 不是数组直接调用 markStaticNode() 进行标记 markStaticNode(tree, key, isOnce) } } 复制代码
markStaticNode() 方法
/* 为 VNode 打静态标记,在 VNode 上添加三个属性: { isStatick: true, key: xx, isOnce: true or false } */ function markStaticNode (node, key, isOnce) { node.isStatic = true node.key = key node.isOnce = isOnce } 复制代码
target._l = renderList
文件位置:src\core\instance\render-helpers\render-list.js
/** * Runtime helper for rendering v-for lists. * * 用于呈现 v-for 列表的运行时帮助程序 * v-for 原理: * 通过循环为类型为 数组、字符串、数字、对象 调用 render 方法, * 得到一个 VNode 数组 */ export function renderList ( val: any, render: ( val: any, keyOrIndex: string | number, index?: number ) => VNode ): ?Array<VNode> { let ret: ?Array<VNode>, i, l, keys, key /* val 是数组 或者 是字符串,如: <div v-for="item in [xxx,xxx,...]"></div> <div v-for="item in 'hello'"></div> */ if (Array.isArray(val) || typeof val === 'string') { ret = new Array(val.length) // 通过循环为每个元素或者字符调用 render 方法,得到 VNode 节点 for (i = 0, l = val.length; i < l; i++) { ret[i] = render(val[i], i) } } else if (typeof val === 'number') { /* val 是数字,如: <div v-for="item in 10"></div> */ ret = new Array(val) // 循环为每个数字调用 render 方法,得到 VNode 节点 for (i = 0; i < val; i++) { ret[i] = render(i + 1, i) } } else if (isObject(val)) { // val 是对象 // val 是个可迭代的对象 if (hasSymbol && val[Symbol.iterator]) { ret = [] /* 获取可迭代对象实例,通过 while 循环为当前迭代对象, 调用 render 方法,得到 VNode 节点 */ const iterator: Iterator<any> = val[Symbol.iterator]() let result = iterator.next() while (!result.done) { ret.push(render(result.value, ret.length)) result = iterator.next() } } else { /* val 是非可迭代对象 通过 Object.keys() 为对象上的每个 key 对应 value 通过循环调用 render 方法 得到 VNode 节点 */ keys = Object.keys(val) ret = new Array(keys.length) for (i = 0, l = keys.length; i < l; i++) { key = keys[i] ret[i] = render(val[key], key, i) } } } /* isDef(ret) --> ret !== undefined && ret !== null !isDef(ret) --> ret === undefined || ret === null */ if (!isDef(ret)) { ret = [] } // 返回 VNode 数组 (ret: any)._isVList = true return ret } 复制代码
target._m = renderStatic
文件位置:src\core\instance\render-helpers\render-list.js
/** * Runtime helper for rendering static trees. * * 运行时负责生成静态树的 VNode 的帮助程序: * 1、执行 staticRenderFns 数组中指定下标的渲染函数, * 生成静态树的 VNode 并缓存,下次在渲染时从缓存中直接读取(isInFor 必须为 true) * 2、为静态树的 VNode 打静态标记 * * @param { number} index 表示当前静态节点的渲染函数在 staticRenderFns 数组中的下标索引 * @param { boolean} isInFor 表示当前静态节点是否被包裹在含有 v-for 指令的节点内部 */ export function renderStatic ( index: number, isInFor: boolean ): VNode | Array<VNode> { // 缓存,静态节点第二次被渲染时就从缓存中直接获取已缓存的 VNode const cached = this._staticTrees || (this._staticTrees = []) let tree = cached[index] /* 如果已渲染静态树且不在 v-for 内,就可以重复使用同一棵树。 */ if (tree && !isInFor) { return tree } /* 如果已渲染静态树且在 v-for 内,那么就需要重新渲染这个树 通过 staticRenderFns 数组,获取并执行对应的 静态渲染函数, 得到新的 VNode 节点,并缓存最新的结果 */ tree = cached[index] = this.$options.staticRenderFns[index].call( this._renderProxy, null, this // for render fns generated for functional component templates ) /* 通过 markStatic 方法,标记静态节点,接收三个参数: tree: VNode | Array<VNode> key: string isOnce: boolean */ markStatic(tree, `__static__${index}`, false) return tree } 复制代码
vm._c = createElement()
文件位置:/src/core/instance/render.js
export function initRender (vm: Component) { ... /* 将 createElement 方法绑定到此实例,方便在其中获得当前的渲染上下文 参数顺序:tag, data, children, normalizationType, alwaysNormalize 内部版本由从模板编译的渲染函数使用 */ vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) ... }