站長資訊網
最全最豐富的資訊網站

解析源碼,看看 vue 編譯器是怎么生成渲染函數的!

本篇文章帶大家深入解析源碼,從 vue 源碼看問題,來看看 vue 編譯器是怎么生成渲染函數的,希望對大家有所幫助。

解析源碼,看看 vue 編譯器是怎么生成渲染函數的!

前兩篇主要了解了 vue 編譯器的 解析優化

  • 將組件的 html 模版解析成 AST 對象
  • 基于 AST 語法樹 進行靜態標記,首先標記每個節點是否為 靜態節點,然后進一步標記出靜態 根節點,便于在后續更新中跳過靜態根節點的更新,從而提高性能

下面就了解一下 vue 編譯器是如何從 AST 語法樹 生成運行渲染函數.

深入源碼

createCompiler() 方法 —— 入口

文件位置:/src/compiler/index.js

其中最主要的就是 generate(ast, options) 方法,它負責從 AST 語法樹生成渲染函數.

/*   在這之前做的所有的事情,只是為了構建平臺特有的編譯選項(options),比如 web 平臺   1、將 html 模版解析成 ast   2、對 ast 樹進行靜態標記   3、將 ast 生成渲染函數      - 靜態渲染函數放到 code.staticRenderFns 數組中      - 動態渲染函數 code.render      - 在將來渲染時執行渲染函數能夠得到 vnode  */ export const createCompiler = createCompilerCreator(function baseCompile(   template: string,   options: CompilerOptions ): CompiledResult {   /*     將模版字符串解析為 AST 語法樹    每個節點的 ast 對象上都設置了元素的所有信息,如,標簽信息、屬性信息、插槽信息、父節點、子節點等   */   const ast = parse(template.trim(), options)    /*    優化,遍歷 AST,為每個節點做靜態標記      - 標記每個節點是否為靜態節點,保證在后續更新中跳過這些靜態節點      - 標記出靜態根節點,用于生成渲染函數階段,生成靜態根節點的渲染函數        優化,遍歷 AST,為每個節點做靜態標記  */   if (options.optimize !== false) {     optimize(ast, options)   }    /*     從 AST 語法樹生成渲染函數     如:code.render = "_c('div',{attrs:{"id":"app"}},_l((arr),function(item){return _c('div',{key:item},[_v(_s(item))])}),0)"   */   const code = generate(ast, options)    return {     ast,     render: code.render,     staticRenderFns: code.staticRenderFns   } })

generate() 方法

文件位置:srccompilercodegenindex.js

其中在給 code 賦值時,主要的內容是通過 genElement(ast, state) 方法進行生成的.

/*    從 AST 生成渲染函數:     - render 為字符串的代碼     - staticRenderFns 為包含多個字符串的代碼,形式為 `with(this){return xxx}` */ 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   } }

genElement() 方法

文件位置:srccompilercodegenindex.js

export function genElement (el: ASTElement, state: CodegenState): string {   if (el.parent) {     el.pre = el.pre || el.parent.pre   }    if (el.staticRoot && !el.staticProcessed) {      /*       處理靜態根節點,生成節點的渲染函數         1、將當前靜態節點的渲染函數放到 staticRenderFns 數組中         2、返回一個可執行函數 _m(idx, true or '')     */     return genStatic(el, state)    } else if (el.once && !el.onceProcessed) {      /*       處理帶有 v-once 指令的節點,結果會有三種:         1、當前節點存在 v-if 指令,得到一個三元表達式,`condition ? render1 : render2`         2、當前節點是一個包含在 v-for 指令內部的靜態節點,得到 `_o(_c(tag, data, children), number, key)`         3、當前節點就是一個單純的 v-once 節點,得到 `_m(idx, true of '')`      */     return genOnce(el, state)    } else if (el.for && !el.forProcessed) {      /*       處理節點上的 v-for 指令,得到:         `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})`     */      return genFor(el, state)    } else if (el.if && !el.ifProcessed) {      /*       處理帶有 v-if 指令的節點,最終得到一個三元表達式:`condition ? render1 : render2`     */     return genIf(el, state)    } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {      /*        當前節點是 template 標簽也不是 插槽 和 帶有 v-pre 指令的節點時走這里        生成所有子節點的渲染函數,返回一個數組,格式如:         `[_c(tag, data, children, normalizationType), ...]`     */     return genChildren(el, state) || 'void 0'    } else if (el.tag === 'slot') {      /* 生成插槽的渲染函數,得到: `_t(slotName, children, attrs, bind)` */     return genSlot(el, state)    } else {     /*       component or element       處理 動態組件 和 普通元素(自定義組件、原生標簽、平臺保留標簽,如 web 平臺中的每個 html 標簽)     */      let code     if (el.component) {       /*         處理動態組件,生成動態組件的渲染函數,得到 `_c(compName, data, children)`       */       code = genComponent(el.component, el, state)      } else {       // 處理普通元素(自定義組件、原生標簽)        let data       if (!el.plain || (el.pre && state.maybeComponent(el))) {         /*             非普通元素或者帶有 v-pre 指令的組件走這里,處理節點的所有屬性,返回一個 JSON 字符串,            比如: '{ key: xx, ref: xx, ... }'         */         data = genData(el, state)       }        /*          處理子節點,得到所有子節點字符串格式的代碼組成的數組,格式:         `['_c(tag, data, children)', ...],normalizationType`         其中的 normalization 表示節點的規范化類型(非重點,可跳過)       */       const children = el.inlineTemplate ? null : genChildren(el, state, true)        /*         得到最終的字符串格式的代碼,格式:_c(tag, data, children, normalizationType)       */        code = `_c('${el.tag}'${         data ? `,${data}` : '' // data       }${         children ? `,${children}` : '' // children       })`      }      /*       如果提供了 transformCode 方法,則最終的 code 會經過各個模塊(module)的該方法處理,       不過框架沒提供這個方法,不過即使處理了,最終的格式也是 _c(tag, data, children)        module transforms     */     for (let i = 0; i < state.transforms.length; i++) {       code = state.transforms[i](el, code)     }      // 返回 code     return code   } }

genChildren() 方法

文件位置:srccompilercodegenindex.js

/*   生成所有子節點的渲染函數,返回一個數組,格式如:    `[_c(tag, data, children, normalizationType), ...]`  */ 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'     ) {       /*         優化處理:          - 條件:只有一個子節點 && 子節點的上有 v-for 指令 && 子節點的標簽不為 template 或者 slot          - 方式:直接調用 genElement 生成該節點的渲染函數,不需要走下面的循環然后調用 genCode 最后得到渲染函數       */       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      /*       返回一個數組,其中每個元素都是一個子節點的渲染函數       格式:['_c(tag, data, children, normalizationType)', ...]     */      return `[${children.map(c => gen(c, state)).join(',')}]${       normalizationType ? `,${normalizationType}` : ''     }`   } }

genNode() 方法

文件位置:srccompilercodegenindex.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() 方法

文件位置:srccompilercodegenindex.js

// 得到返回值,格式為:`_e(xxxx)` export function genComment (comment: ASTText): string {   return `_e(${JSON.stringify(comment.text)})` }

genText() 方法

文件位置:srccompilercodegenindex.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() 方法

文件位置:srccompilercodegenindex.js

/*   處理節點上的眾多屬性,最后生成這些屬性組成的 JSON 字符串,   比如 data = { key: xx, ref: xx, ... }  */ export function genData(el: ASTElement, state: CodegenState): string {    // 節點的屬性組成的 JSON 字符串   let data = '{'    /*     首先先處理指令,因為指令可能在生成其它屬性之前改變這些屬性     執行指令編譯方法,如 web 平臺的 v-text、v-html、v-model,然后在 el 對象上添加相應的屬性,     如 v-text:el.textContent = _s(value, dir)        v-html:el.innerHTML = _s(value, dir)      當指令在運行時還有任務時,比如 v-model,     則返回 directives: [{ name, rawName, value, arg, modifiers }, ...}]    */   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}",`   }   /*     為節點執行模塊 (class、style) 的 genData 方法,     得到 data = { staticClass: xx, class: xx, staticStyle: xx, style: xx }      module data generation functions   */   for (let i = 0; i < state.dataGenFns.length; i++) {     data += state.dataGenFns[i](el)   }   /*     其它屬性,得到 data = { attrs: 靜態屬性字符串 } 或者      data = { attrs: '_d(靜態屬性字符串, 動態屬性字符串)' }      attributes   */   if (el.attrs) {     data += `attrs:${genProps(el.attrs)},`   }   // DOM props,結果 el.attrs 相同   if (el.props) {     data += `domProps:${genProps(el.props)},`   }   /*     自定義事件      - data = { `on${eventName}:handleCode` }               或者       - { `on_d(${eventName}:handleCode`, `${eventName},handleCode`) }        event handlers   */   if (el.events) {     data += `${genHandlers(el.events, false)},`   }   /*      帶 .native 修飾符的事件,      - data = { `nativeOn${eventName}:handleCode` }                或者       - { `nativeOn_d(${eventName}:handleCode`, `${eventName},handleCode`)   */   if (el.nativeEvents) {     data += `${genHandlers(el.nativeEvents, true)},`   }   /*    非作用域插槽,得到 data = { slot: slotName }     slot target    only for non-scoped slots   */   if (el.slotTarget && !el.slotScope) {     data += `slot:${el.slotTarget},`   }   // scoped slots,作用域插槽,data = { scopedSlots: '_u(xxx)' }   if (el.scopedSlots) {     data += `${genScopedSlots(el, el.scopedSlots, state)},`   }   /*     處理 v-model 屬性,得到     data = { model: { value, callback, expression } }      component v-model   */   if (el.model) {     data += `model:{value:${el.model.value       },callback:${el.model.callback       },expression:${el.model.expression       }},`   }   /*      inline-template,處理內聯模版,得到:      data = { inlineTemplate: { render: function() { render 函數 }, staticRenderFns: [ function() {}, ... ] } }   */   if (el.inlineTemplate) {     const inlineTemplate = genInlineTemplate(el, state)     if (inlineTemplate) {       data += `${inlineTemplate},`     }   }   // 刪掉 JSON 字符串最后的 逗號,然后加上閉合括號 }   data = data.replace(/,$/, '') + '}'      /*     v-bind 動態參數包裝     必須使用相同的 v-bind 對象應用動態綁定參數     合并輔助對象,以便正確處理 class/style/mustUseProp 屬性。   */   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() 方法

文件位置:srccompilercodegenindex.js

/**   運行指令的編譯方法,如果指令存在運行時任務,則返回    directives: [{ name, rawName, value, arg, modifiers }, ...}]  */ 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() 方法

文件位置:srccompilercodegenindex.js

/*   遍歷屬性數組 props,得到所有屬性組成的字符串   如果不存在動態屬性,則返回:'attrName,attrVal,...'   如果存在動態屬性,則返回:'_d(靜態屬性字符串, 動態屬性字符串)'   */ 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() 方法

文件位置:srccompilercodegenevents.js

/*   生成自定義事件的代碼   動態:'nativeOn|on_d(staticHandlers, [dynamicHandlers])'   靜態:`nativeOn|on${staticHandlers}`  */ export function genHandlers (   events: ASTElementHandlers,   isNative: boolean ): string {   // 原生為 nativeOn,否則為 on   const prefix = isNative ? 'nativeOn:' : 'on:'   // 靜態   let staticHandlers = ``   // 動態   let dynamicHandlers = ``   /*     遍歷 events 數組     events = [{ name: { value: 回調函數名, ... } }]   */    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() 方法

文件位置:srccompilercodegenindex.js

/*   生成靜態節點的渲染函數     1、將當前靜態節點的渲染函數放到 staticRenderFns 數組中     2、返回一個可執行函數 _m(idx, true or '')       hoist static sub-trees out */ function genStatic(el: ASTElement, state: CodegenState): string {   // 標記當前靜態節點已經被處理過了   el.staticProcessed = true    /*     某些元素(模板)在 v-pre 節點中需要有不同的行為     所有 pre 節點都是靜態根,因此可將其用作包裝狀態更改并在退出 pre 節點時將其重置   */   const originalPreState = state.pre   if (el.pre) {     state.pre = el.pre   }    /*      將靜態根節點的渲染函數 push 到 staticRenderFns 數組中,     比如:[`with(this){return _c(tag, data, children)}`]   */   state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)    state.pre = originalPreState   /*      返回一個可執行函數:_m(idx, true or '')     idx = 當前靜態節點的渲染函數在 staticRenderFns 數組中下標   */   return `_m(${state.staticRenderFns.length - 1     }${el.staticInFor ? ',true' : ''     })` }

genOnce() 方法

文件位置:srccompilercodegenindex.js

/*  處理帶有 v-once 指令的節點,結果會有三種:    1、當前節點存在 v-if 指令,得到一個三元表達式,condition ? render1 : render2    2、當前節點是一個包含在 v-for 指令內部的靜態節點,       得到 `_o(_c(tag, data, children), number, key)`    3、當前節點就是一個單純的 v-once 節點,得到 `_m(idx, true of '')`     v-once  */ function genOnce(el: ASTElement, state: CodegenState): string {   // 標記當前節點的 v-once 指令已經被處理過了   el.onceProcessed = true   if (el.if && !el.ifProcessed) {     /*      如果含有 v-if 指令 && if 指令沒有被處理過      則處理帶有 v-if 指令的節點,最終得到一個三元表達式:        condition ? render1 : render2      */      return genIf(el, state)    } else if (el.staticInFor) {     /*       說明當前節點是被包裹在還有 v-for 指令節點內部的靜態節點       獲取 v-for 指令的 key     */     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 {      /*        上面幾種情況都不符合,說明就是一個簡單的靜態節點,        和處理靜態根節點時的操作一樣,得到 _m(idx, true or '')      */      return genStatic(el, state)   } }

genFor() 方法

文件位置:srccompilercodegenindex.js

/*   處理節點上 v-for 指令     得到 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})` */ 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 /* tip */     )   }    // 標記當前節點上的 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() 方法

文件位置:srccompilercodegenindex.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) {     /*      如果 condition.exp 條件成立,則得到一個三元表達式,      如果條件不成立,則通過遞歸的方式找 conditions 數組中下一個元素,      直到找到條件成立的元素,然后返回一個三元表達式     */     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() 方法

文件位置:srccompilercodegenindex.js

/*   生成插槽的渲染函數,得到:_t(slotName, children, attrs, bind)  */ 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() 方法

文件位置:srccompilercodegenindex.js

/*   生成動態組件的渲染函數,返回 `_c(compName, data, children)`    componentName is el.component, take it as argument to shun flow's pessimistic refinement */ 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

贊(0)
分享到: 更多 (0)
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
91精品久久久久久久久中文字幕| 亚洲国产精品免费视频| 久久精品熟女亚洲av麻豆| 国产一区二区精品| 日韩精品成人亚洲专区| 在线观看精品一区| 久久丫精品国产亚洲av| 国产自啪精品视频网站丝袜| 精品国际久久久久999波多野| 亚洲日韩区在线电影| 国产成人精品日本亚洲专| 国产精品视频一区二区三区四| 国产精品视频免费一区二区三区| 国内精品久久久久| chinese精品男同志浪小辉| 国产成人综合久久精品下载| 中日韩精品无码一区二区三区| 99re这里只有精品国产精品| 亚洲av日韩av永久无码电影| 国产麻豆剧传媒精品网站| 在线精品国产成人综合| 久久精品国产亚洲av高清漫画| 国产精品玖玖美女张开腿让男人桶爽免费看 | 国美女福利视频午夜精品| 无码日韩精品一区二区三区免费 | 日韩大片免费观看视频播放| 91精品国产免费| 亚洲国产精品无码成人片久久| 日韩免费一区二区三区在线 | 日韩亚洲翔田千里在线 | 国产女精品视频在ktv| 8090成人午夜精品| 乱精品一区字幕二区| 国产亚洲精品a在线观看app| 国内精品免费久久影院| 国产69精品久久久久99尤物| 日韩福利视频一区| 亚州日韩精品专区久久久| 国产真实乱人偷精品| 国产成人高清精品免费软件| 99久久这里只精品国产免费|