这篇文章主要讲解了“vue编译器如何生成渲染函数”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue编译器如何生成渲染函数”吧!
深入源码
createCompiler() 方法 —— 入口
文件位置:/src/compiler/index.js
其中最主要的就是 generate(ast, options)
方法,它负责从 AST
语法树生成渲染函数.
export const createCompiler = createCompilerCreator(function baseCompile( template: string, options: CompilerOptions): CompiledResult { const ast = parse(template.trim(), options) if (options.optimize !== false) { optimize(ast, options) } const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns }})
generate() 方法
文件位置:src\compiler\codegen\index.js
其中在给 code
赋值时,主要的内容是通过 genElement(ast, state)
方法进行生成的.
export function generate ( ast: ASTElement | void, // ast 对象 options: CompilerOptions // 编译选项): CodegenResult { const state = new CodegenState(options) const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")' return { render: `with(this){return ${code}}`, staticRenderFns: state.staticRenderFns }}
genElement() 方法
文件位置:src\compiler\codegen\index.js
export function genElement (el: ASTElement, state: CodegenState): string { if (el.parent) { el.pre = el.pre || el.parent.pre } if (el.staticRoot && !el.staticProcessed) { return genStatic(el, state) } else if (el.once && !el.onceProcessed) { return genOnce(el, state) } else if (el.for && !el.forProcessed) { return genFor(el, state) } else if (el.if && !el.ifProcessed) { return genIf(el, state) } else if (el.tag === 'template' && !el.slotTarget && !state.pre) { return genChildren(el, state) || 'void 0' } else if (el.tag === 'slot') { return genSlot(el, state) } else { let code if (el.component) { code = genComponent(el.component, el, state) } else { // 处理普通元素(自定义组件、原生标签) let data if (!el.plain || (el.pre && state.maybeComponent(el))) { data = genData(el, state) } const children = el.inlineTemplate ? null : genChildren(el, state, true) code = `_c('${el.tag}'${ data ? `,${data}` : '' // data }${ children ? `,${children}` : '' // children })` } for (let i = 0; i < state.transforms.length; i++) { code = state.transforms[i](el, code) } // 返回 code return code }}
genChildren() 方法
文件位置:src\compiler\codegen\index.js
export function genChildren ( el: ASTElement, state: CodegenState, checkSkip?: boolean, altGenElement?: Function, altGenNode?: Function): string | void { // 获取所有子节点 const children = el.children if (children.length) { // 第一个子节点 const el: any = children[0] // optimize single v-for if (children.length === 1 && el.for && el.tag !== 'template' && el.tag !== 'slot' ) { const normalizationType = checkSkip ? state.maybeComponent(el) ? `,1` : `,0` : `` return `${(altGenElement || genElement)(el, state)}${normalizationType}` } // 获取节点规范化类型,返回一个 number: 0、1、2(非重点,可跳过) const normalizationType = checkSkip ? getNormalizationType(children, state.maybeComponent) : 0 // 是一个函数,负责生成代码的一个函数 const gen = altGenNode || genNode return `[${children.map(c => gen(c, state)).join(',')}]${ normalizationType ? `,${normalizationType}` : '' }` }}
genNode() 方法
文件位置:src\compiler\codegen\index.js
function genNode (node: ASTNode, state: CodegenState): string { // 处理普通元素节点 if (node.type === 1) { return genElement(node, state) } else if (node.type === 3 && node.isComment) { // 处理文本注释节点 return genComment(node) } else { // 处理文本节点 return genText(node) }}
genComment() 方法
文件位置:src\compiler\codegen\index.js
// 得到返回值,格式为:`_e(xxxx)`export function genComment (comment: ASTText): string { return `_e(${JSON.stringify(comment.text)})`}
genText() 方法
文件位置:src\compiler\codegen\index.js
// 得到返回值,格式为:`_v(xxxxx)`export function genText (text: ASTText | ASTExpression): string { return `_v(${text.type === 2 ? text.expression // no need for () because already wrapped in _s() : transformSpecialNewlines(JSON.stringify(text.text)) })`}
genData() 方法
文件位置:src\compiler\codegen\index.js
export function genData(el: ASTElement, state: CodegenState): string { // 节点的属性组成的 JSON 字符串 let data = '{' const dirs = genDirectives(el, state) if (dirs) data += dirs + ',' // key,data = { key: xxx } if (el.key) { data += `key:${el.key},` } // ref,data = { ref: xxx } if (el.ref) { data += `ref:${el.ref},` } // 带有 ref 属性的节点在带有 v-for 指令的节点的内部,data = { refInFor: true } if (el.refInFor) { data += `refInFor:true,` } // pre,v-pre 指令,data = { pre: true } if (el.pre) { data += `pre:true,` } // 动态组件 <component is="xxx">,data = { tag: 'component' } if (el.component) { data += `tag:"${el.tag}",` } for (let i = 0; i < state.dataGenFns.length; i++) { data += state.dataGenFns[i](el) } if (el.attrs) { data += `attrs:${genProps(el.attrs)},` } // DOM props,结果 el.attrs 相同 if (el.props) { data += `domProps:${genProps(el.props)},` } if (el.events) { data += `${genHandlers(el.events, false)},` } if (el.nativeEvents) { data += `${genHandlers(el.nativeEvents, true)},` } if (el.slotTarget && !el.slotScope) { data += `slot:${el.slotTarget},` } // scoped slots,作用域插槽,data = { scopedSlots: '_u(xxx)' } if (el.scopedSlots) { data += `${genScopedSlots(el, el.scopedSlots, state)},` } if (el.model) { data += `model:{value:${el.model.value },callback:${el.model.callback },expression:${el.model.expression }},` } if (el.inlineTemplate) { const inlineTemplate = genInlineTemplate(el, state) if (inlineTemplate) { data += `${inlineTemplate},` } } // 删掉 JSON 字符串最后的 逗号,然后加上闭合括号 } data = data.replace(/,$/, '') + '}' if (el.dynamicAttrs) { data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})` } // v-bind data wrap if (el.wrapData) { data = el.wrapData(data) } // v-on data wrap if (el.wrapListeners) { data = el.wrapListeners(data) } return data}
genDirectives() 方法
文件位置:src\compiler\codegen\index.js
function genDirectives(el: ASTElement, state: CodegenState): string | void { // 获取指令数组 const dirs = el.directives // 不存在指令,直接结束 if (!dirs) return // 指令的处理结果 let res = 'directives:[' // 用于标记指令是否需要在运行时完成的任务,比如 v-model 的 input 事件 let hasRuntime = false let i, l, dir, needRuntime // 遍历指令数组 for (i = 0, l = dirs.length; i < l; i++) { dir = dirs[i] needRuntime = true // 获取节点当前指令的处理方法,比如 web 平台的 v-html、v-text、v-model const gen: DirectiveFunction = state.directives[dir.name] if (gen) { // 执行指令的编译方法,如果指令还需要运行时完成一部分任务,则返回 true,比如 v-model needRuntime = !!gen(el, dir, state.warn) } if (needRuntime) { // 表示该指令在运行时还有任务 hasRuntime = true // res = directives:[{ name, rawName, value, arg, modifiers }, ...] res += `{name:"${dir.name}",rawName:"${dir.rawName}"${dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : '' }${dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : '' }${dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : '' }},` } } // 只有指令存在运行时任务时,才会返回 res if (hasRuntime) { return res.slice(0, -1) + ']' }}
genDirectives() 方法
文件位置:src\compiler\codegen\index.js
function genProps(props: Array<ASTAttr>): string { // 静态属性 let staticProps = `` // 动态属性 let dynamicProps = `` // 遍历属性数组 for (let i = 0; i < props.length; i++) { // 属性 const prop = props[i] // 属性值 const value = __WEEX__ ? generateValue(prop.value) : transformSpecialNewlines(prop.value) if (prop.dynamic) { // 动态属性,`dAttrName,dAttrVal,...` dynamicProps += `${prop.name},${value},` } else { // 静态属性,'attrName:attrVal,...' staticProps += `"${prop.name}":${value},` } } // 闭合静态属性字符串,并去掉静态属性最后的 ',' staticProps = `{${staticProps.slice(0, -1)}}` if (dynamicProps) { // 如果存在动态属性则返回:_d(静态属性字符串,动态属性字符串) return `_d(${staticProps},[${dynamicProps.slice(0, -1)}])` } else { // 说明属性数组中不存在动态属性,直接返回静态属性字符串 return staticProps }}
genHandlers() 方法
文件位置:src\compiler\codegen\events.js
export function genHandlers ( events: ASTElementHandlers, isNative: boolean): string { // 原生为 nativeOn,否则为 on const prefix = isNative ? 'nativeOn:' : 'on:' // 静态 let staticHandlers = `` // 动态 let dynamicHandlers = `` for (const name in events) { const handlerCode = genHandler(events[name]) if (events[name] && events[name].dynamic) { // 动态,dynamicHandles = `eventName,handleCode,...,` dynamicHandlers += `${name},${handlerCode},` } else { // staticHandlers = `eventName:handleCode,...,` staticHandlers += `"${name}":${handlerCode},` } } // 闭合静态事件处理代码字符串,去除末尾的 ',' staticHandlers = `{${staticHandlers.slice(0, -1)}}` if (dynamicHandlers) { // 动态,on_d(statickHandles, [dynamicHandlers]) return prefix + `_d(${staticHandlers},[${dynamicHandlers.slice(0, -1)}])` } else { // 静态,`on${staticHandlers}` return prefix + staticHandlers }}
genStatic() 方法
文件位置:src\compiler\codegen\index.js
function genStatic(el: ASTElement, state: CodegenState): string { // 标记当前静态节点已经被处理过了 el.staticProcessed = true const originalPreState = state.pre if (el.pre) { state.pre = el.pre } state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`) state.pre = originalPreState return `_m(${state.staticRenderFns.length - 1 }${el.staticInFor ? ',true' : '' })`}
genOnce() 方法
文件位置:src\compiler\codegen\index.js
function genOnce(el: ASTElement, state: CodegenState): string { // 标记当前节点的 v-once 指令已经被处理过了 el.onceProcessed = true if (el.if && !el.ifProcessed) { return genIf(el, state) } else if (el.staticInFor) { let key = '' let parent = el.parent while (parent) { if (parent.for) { key = parent.key break } parent = parent.parent } // key 不存在则给出提示,v-once 节点只能用于带有 key 的 v-for 节点内部 if (!key) { process.env.NODE_ENV !== 'production' && state.warn( `v-once can only be used inside v-for that is keyed. `, el.rawAttrsMap['v-once'] ) return genElement(el, state) } // 生成 `_o(_c(tag, data, children), number, key)` return `_o(${genElement(el, state)},${state.onceId++},${key})` } else { return genStatic(el, state) }}
genFor() 方法
文件位置:src\compiler\codegen\index.js
export function genFor( el: any, state: CodegenState, altGen?: Function, altHelper?: string): string { // v-for 的迭代器,比如 一个数组 const exp = el.for // 迭代时的别名 const alias = el.alias // iterator 为 v-for = "(item ,idx) in obj" 时会有,比如 iterator1 = idx const iterator1 = el.iterator1 ? `,${el.iterator1}` : '' const iterator2 = el.iterator2 ? `,${el.iterator2}` : '' // 提示,v-for 指令在组件上时必须使用 key if (process.env.NODE_ENV !== 'production' && state.maybeComponent(el) && el.tag !== 'slot' && el.tag !== 'template' && !el.key ) { state.warn( `<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` + `v-for should have explicit keys. ` + `See https://vuejs.org/guide/list.html#key for more info.`, el.rawAttrsMap['v-for'], true ) } // 标记当前节点上的 v-for 指令已经被处理过了 el.forProcessed = true // avoid recursion // 返回 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})` return `${altHelper || '_l'}((${exp}),` + `function(${alias}${iterator1}${iterator2}){` + `return ${(altGen || genElement)(el, state)}` + '})'}
genIf() 方法
文件位置:src\compiler\codegen\index.js
// 处理带有 v-if 指令的节点,最终得到一个三元表达式,condition ? render1 : render2 export function genIf( el: any, state: CodegenState, altGen?: Function, altEmpty?: string): string { // 标记当前节点的 v-if 指令已经被处理过了,避免无效的递归 el.ifProcessed = true // avoid recursion // 得到三元表达式,condition ? render1 : render2 return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)}function genIfConditions( conditions: ASTIfConditions, state: CodegenState, altGen?: Function, altEmpty?: string): string { // 长度若为空,则直接返回一个空节点渲染函数 if (!conditions.length) { return altEmpty || '_e()' } // 从 conditions 数组中拿出第一个条件对象 { exp, block } const condition = conditions.shift() // 返回结果是一个三元表达式字符串,condition ? 渲染函数1 : 渲染函数2 if (condition.exp) { return `(${condition.exp})?${genTernaryExp(condition.block) }:${genIfConditions(conditions, state, altGen, altEmpty) }` } else { return `${genTernaryExp(condition.block)}` } // v-if with v-once should generate code like (a)?_m(0):_m(1) function genTernaryExp(el) { return altGen ? altGen(el, state) : el.once ? genOnce(el, state) : genElement(el, state) }}
genSlot() 方法
文件位置:src\compiler\codegen\index.js
function genSlot(el: ASTElement, state: CodegenState): string { // 插槽名称 const slotName = el.slotName || '"default"' // 生成所有的子节点 const children = genChildren(el, state) // 结果字符串,_t(slotName, children, attrs, bind) let res = `_t(${slotName}${children ? `,function(){return ${children}}` : ''}` const attrs = el.attrs || el.dynamicAttrs ? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(attr => ({ // slot props are camelized name: camelize(attr.name), value: attr.value, dynamic: attr.dynamic }))) : null const bind = el.attrsMap['v-bind'] if ((attrs || bind) && !children) { res += `,null` } if (attrs) { res += `,${attrs}` } if (bind) { res += `${attrs ? '' : ',null'},${bind}` } return res + ')'}
genComponent() 方法
文件位置:src\compiler\codegen\index.js
function genComponent( componentName: string, el: ASTElement, state: CodegenState): string { // 所有的子节点 const children = el.inlineTemplate ? null : genChildren(el, state, true) // 返回 `_c(compName, data, children)`,compName 是 is 属性的值 return `_c(${componentName},${genData(el, state)}${children ? `,${children}` : '' })`}
总结
渲染函数的生成过程是什么?
编译器生成的渲染有两类:
render
函数,负责生成动态节点的vnode
staticRenderFns
数组中的静态渲染函数
,负责生成静态节点的vnode
渲染函数的生成过程,其实就是在遍历 AST 节点,通过递归的方式处理每个节点,最后生成格式如:_c(tag, attr, children, normalizationType)
:
tag
是标签名attr
是属性对象children
是子节点组成的数组,其中每个元素的格式都是_c(tag, attr, children, normalizationTYpe)
的形式,normalization
表示节点的规范化类型,是一个数字 0、1、2
静态节点是怎么处理的?
静态节点的处理分为两步:
将生成静态节点
vnode
函数放到staticRenderFns
数组中返回一个
_m(idx)
的可执行函数,即执行staticRenderFns
数组中下标为idx
的函数,生成静态节点的vnode
v-once、v-if、v-for、组件 等都是怎么处理的?
单纯的
v-once
节点处理方式 和静态节点
一致v-if
节点的处理结果是一个三元表达式
v-for
节点的处理结果是可执行的_l
函数,该函数负责生成v-for
节点的vnode
组件的处理结果和普通元素一样,得到的是形如
_c(compName)
的可执行代码,生成组件的vnode
感谢各位的阅读,以上就是“vue编译器如何生成渲染函数”的内容了,经过本文的学习后,相信大家对vue编译器如何生成渲染函数这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!