溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

vue編譯器如何生成渲染函數(shù)

發(fā)布時(shí)間:2022-01-11 09:00:23 來(lái)源:億速云 閱讀:158 作者:iii 欄目:編程語(yǔ)言

這篇文章主要講解了“vue編譯器如何生成渲染函數(shù)”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“vue編譯器如何生成渲染函數(shù)”吧!

vue編譯器如何生成渲染函數(shù)

深入源碼

createCompiler() 方法 —— 入口

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

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

/*
  在這之前做的所有的事情,只是為了構(gòu)建平臺(tái)特有的編譯選項(xiàng)(options),比如 web 平臺(tái)
  1、將 html 模版解析成 ast
  2、對(duì) ast 樹(shù)進(jìn)行靜態(tài)標(biāo)記
  3、將 ast 生成渲染函數(shù)
     - 靜態(tài)渲染函數(shù)放到 code.staticRenderFns 數(shù)組中
     - 動(dòng)態(tài)渲染函數(shù) code.render
     - 在將來(lái)渲染時(shí)執(zhí)行渲染函數(shù)能夠得到 vnode
 */
export const createCompiler = createCompilerCreator(function baseCompile(
  template: string,
  options: CompilerOptions
): CompiledResult {
  /* 
   將模版字符串解析為 AST 語(yǔ)法樹(shù)
   每個(gè)節(jié)點(diǎn)的 ast 對(duì)象上都設(shè)置了元素的所有信息,如,標(biāo)簽信息、屬性信息、插槽信息、父節(jié)點(diǎn)、子節(jié)點(diǎn)等
  */
  const ast = parse(template.trim(), options)

  /*
   優(yōu)化,遍歷 AST,為每個(gè)節(jié)點(diǎn)做靜態(tài)標(biāo)記
     - 標(biāo)記每個(gè)節(jié)點(diǎn)是否為靜態(tài)節(jié)點(diǎn),保證在后續(xù)更新中跳過(guò)這些靜態(tài)節(jié)點(diǎn)
     - 標(biāo)記出靜態(tài)根節(jié)點(diǎn),用于生成渲染函數(shù)階段,生成靜態(tài)根節(jié)點(diǎn)的渲染函數(shù)
       優(yōu)化,遍歷 AST,為每個(gè)節(jié)點(diǎn)做靜態(tài)標(biāo)記
 */
  if (options.optimize !== false) {
    optimize(ast, options)
  }

  /*
    從 AST 語(yǔ)法樹(shù)生成渲染函數(shù)
    如: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() 方法

文件位置:src\compiler\codegen\index.js

其中在給 code 賦值時(shí),主要的內(nèi)容是通過(guò) genElement(ast, state) 方法進(jìn)行生成的.

/*
   從 AST 生成渲染函數(shù):
    - render 為字符串的代碼
    - staticRenderFns 為包含多個(gè)字符串的代碼,形式為 `with(this){return xxx}`
*/
export function generate (
  ast: ASTElement | void, // ast 對(duì)象
  options: CompilerOptions // 編譯選項(xiàng)
): CodegenResult {

  /*
    實(shí)例化 CodegenState 對(duì)象,參數(shù)是編譯選項(xiàng),最終得到 state ,其中大部分屬性和 options 一樣
  */
  const state = new CodegenState(options)

  /* 
   生成字符串格式的代碼,比如:'_c(tag, data, children, normalizationType)'
    - data 為節(jié)點(diǎn)上的屬性組成 JSON 字符串,比如 '{ key: xx, ref: xx, ... }'
    - children 為所有子節(jié)點(diǎn)的字符串格式的代碼組成的字符串?dāng)?shù)組,格式:
      `['_c(tag, data, children)', ...],normalizationType`,
    - normalization 是 _c 的第四個(gè)參數(shù),表示節(jié)點(diǎn)的規(guī)范化類型(非重點(diǎn),可跳過(guò))

    注意:code 并不一定就是 _c,也有可能是其它的,比如整個(gè)組件都是靜態(tài)的,則結(jié)果就為 _m(0)
  */
  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) {

    /*
      處理靜態(tài)根節(jié)點(diǎn),生成節(jié)點(diǎn)的渲染函數(shù)
        1、將當(dāng)前靜態(tài)節(jié)點(diǎn)的渲染函數(shù)放到 staticRenderFns 數(shù)組中
        2、返回一個(gè)可執(zhí)行函數(shù) _m(idx, true or '')
    */
    return genStatic(el, state)

  } else if (el.once && !el.onceProcessed) {

    /*
      處理帶有 v-once 指令的節(jié)點(diǎn),結(jié)果會(huì)有三種:
        1、當(dāng)前節(jié)點(diǎn)存在 v-if 指令,得到一個(gè)三元表達(dá)式,`condition ? render1 : render2`
        2、當(dāng)前節(jié)點(diǎn)是一個(gè)包含在 v-for 指令內(nèi)部的靜態(tài)節(jié)點(diǎn),得到 `_o(_c(tag, data, children), number, key)`
        3、當(dāng)前節(jié)點(diǎn)就是一個(gè)單純的 v-once 節(jié)點(diǎn),得到 `_m(idx, true of '')`
     */
    return genOnce(el, state)

  } else if (el.for && !el.forProcessed) {

    /*
      處理節(jié)點(diǎn)上的 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 指令的節(jié)點(diǎn),最終得到一個(gè)三元表達(dá)式:`condition ? render1 : render2`
    */
    return genIf(el, state)

  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {

    /*
       當(dāng)前節(jié)點(diǎn)是 template 標(biāo)簽也不是 插槽 和 帶有 v-pre 指令的節(jié)點(diǎn)時(shí)走這里
       生成所有子節(jié)點(diǎn)的渲染函數(shù),返回一個(gè)數(shù)組,格式如:
        `[_c(tag, data, children, normalizationType), ...]`
    */
    return genChildren(el, state) || 'void 0'

  } else if (el.tag === 'slot') {

    /* 生成插槽的渲染函數(shù),得到: `_t(slotName, children, attrs, bind)` */
    return genSlot(el, state)

  } else {
    /*
      component or element
      處理 動(dòng)態(tài)組件 和 普通元素(自定義組件、原生標(biāo)簽、平臺(tái)保留標(biāo)簽,如 web 平臺(tái)中的每個(gè) html 標(biāo)簽)
    */

    let code
    if (el.component) {
      /*
        處理動(dòng)態(tài)組件,生成動(dòng)態(tài)組件的渲染函數(shù),得到 `_c(compName, data, children)`
      */
      code = genComponent(el.component, el, state)

    } else {
      // 處理普通元素(自定義組件、原生標(biāo)簽)

      let data
      if (!el.plain || (el.pre && state.maybeComponent(el))) {
        /* 
           非普通元素或者帶有 v-pre 指令的組件走這里,處理節(jié)點(diǎn)的所有屬性,返回一個(gè) JSON 字符串,
           比如: '{ key: xx, ref: xx, ... }'
        */
        data = genData(el, state)
      }

      /* 
        處理子節(jié)點(diǎn),得到所有子節(jié)點(diǎn)字符串格式的代碼組成的數(shù)組,格式:
        `['_c(tag, data, children)', ...],normalizationType`
        其中的 normalization 表示節(jié)點(diǎn)的規(guī)范化類型(非重點(diǎn),可跳過(guò))
      */
      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 會(huì)經(jīng)過(guò)各個(gè)模塊(module)的該方法處理,
      不過(guò)框架沒(méi)提供這個(gè)方法,不過(guò)即使處理了,最終的格式也是 _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() 方法

文件位置:src\compiler\codegen\index.js

/*
  生成所有子節(jié)點(diǎn)的渲染函數(shù),返回一個(gè)數(shù)組,格式如:
   `[_c(tag, data, children, normalizationType), ...]`
 */
export function genChildren (
  el: ASTElement,
  state: CodegenState,
  checkSkip?: boolean,
  altGenElement?: Function,
  altGenNode?: Function
): string | void {

 // 獲取所有子節(jié)點(diǎn)
  const children = el.children

  if (children.length) {
    // 第一個(gè)子節(jié)點(diǎn)
    const el: any = children[0]

    // optimize single v-for
    if (children.length === 1 &&
      el.for &&
      el.tag !== 'template' &&
      el.tag !== 'slot'
    ) {
      /* 
       優(yōu)化處理:
         - 條件:只有一個(gè)子節(jié)點(diǎn) && 子節(jié)點(diǎn)的上有 v-for 指令 && 子節(jié)點(diǎn)的標(biāo)簽不為 template 或者 slot
         - 方式:直接調(diào)用 genElement 生成該節(jié)點(diǎn)的渲染函數(shù),不需要走下面的循環(huán)然后調(diào)用 genCode 最后得到渲染函數(shù)
      */
      const normalizationType = checkSkip
        ? state.maybeComponent(el) ? `,1` : `,0`
        : ``
      return `${(altGenElement || genElement)(el, state)}${normalizationType}`
    }

    // 獲取節(jié)點(diǎn)規(guī)范化類型,返回一個(gè) number: 0、1、2(非重點(diǎn),可跳過(guò))
    const normalizationType = checkSkip
      ? getNormalizationType(children, state.maybeComponent)
      : 0

    // 是一個(gè)函數(shù),負(fù)責(zé)生成代碼的一個(gè)函數(shù)
    const gen = altGenNode || genNode

    /*
      返回一個(gè)數(shù)組,其中每個(gè)元素都是一個(gè)子節(jié)點(diǎn)的渲染函數(shù)
      格式:['_c(tag, data, children, normalizationType)', ...]
    */ 
    return `[${children.map(c => gen(c, state)).join(',')}]${
      normalizationType ? `,${normalizationType}` : ''
    }`
  }
}

genNode() 方法

文件位置:src\compiler\codegen\index.js

function genNode (node: ASTNode, state: CodegenState): string {
  // 處理普通元素節(jié)點(diǎn)
  if (node.type === 1) {
    return genElement(node, state)
  } else if (node.type === 3 && node.isComment) {
    // 處理文本注釋節(jié)點(diǎn)
    return genComment(node)
  } else {
    // 處理文本節(jié)點(diǎn)
    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

/*
  處理節(jié)點(diǎn)上的眾多屬性,最后生成這些屬性組成的 JSON 字符串,
  比如 data = { key: xx, ref: xx, ... } 
*/
export function genData(el: ASTElement, state: CodegenState): string {

  // 節(jié)點(diǎn)的屬性組成的 JSON 字符串
  let data = '{'

  /*
    首先先處理指令,因?yàn)橹噶羁赡茉谏善渌鼘傩灾案淖冞@些屬性
    執(zhí)行指令編譯方法,如 web 平臺(tái)的 v-text、v-html、v-model,然后在 el 對(duì)象上添加相應(yīng)的屬性,
    如 v-text:el.textContent = _s(value, dir)
       v-html:el.innerHTML = _s(value, dir)

    當(dāng)指令在運(yùn)行時(shí)還有任務(wù)時(shí),比如 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 屬性的節(jié)點(diǎn)在帶有 v-for 指令的節(jié)點(diǎn)的內(nèi)部,data = { refInFor: true }
  if (el.refInFor) {
    data += `refInFor:true,`
  }
  // pre,v-pre 指令,data = { pre: true }
  if (el.pre) {
    data += `pre:true,`
  }
  // 動(dòng)態(tài)組件 <component is="xxx">,data = { tag: 'component' }
  if (el.component) {
    data += `tag:"${el.tag}",`
  }
  /*
    為節(jié)點(diǎn)執(zhí)行模塊 (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: 靜態(tài)屬性字符串 } 或者 
    data = { attrs: '_d(靜態(tài)屬性字符串, 動(dòng)態(tài)屬性字符串)' }

    attributes
  */
  if (el.attrs) {
    data += `attrs:${genProps(el.attrs)},`
  }
  // DOM props,結(jié)果 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,處理內(nèi)聯(lián)模版,得到:
     data = { inlineTemplate: { render: function() { render 函數(shù) }, staticRenderFns: [ function() {}, ... ] } }
  */
  if (el.inlineTemplate) {
    const inlineTemplate = genInlineTemplate(el, state)
    if (inlineTemplate) {
      data += `${inlineTemplate},`
    }
  }
  // 刪掉 JSON 字符串最后的 逗號(hào),然后加上閉合括號(hào) }
  data = data.replace(/,$/, '') + '}'
  
  /*
    v-bind 動(dòng)態(tài)參數(shù)包裝
    必須使用相同的 v-bind 對(duì)象應(yīng)用動(dòng)態(tài)綁定參數(shù)
    合并輔助對(duì)象,以便正確處理 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() 方法

文件位置:src\compiler\codegen\index.js

/**
  運(yùn)行指令的編譯方法,如果指令存在運(yùn)行時(shí)任務(wù),則返回 
  directives: [{ name, rawName, value, arg, modifiers }, ...}] 
*/
function genDirectives(el: ASTElement, state: CodegenState): string | void {
  // 獲取指令數(shù)組
  const dirs = el.directives
  // 不存在指令,直接結(jié)束 
  if (!dirs) return

  // 指令的處理結(jié)果
  let res = 'directives:['
  // 用于標(biāo)記指令是否需要在運(yùn)行時(shí)完成的任務(wù),比如 v-model 的 input 事件
  let hasRuntime = false
  let i, l, dir, needRuntime

  // 遍歷指令數(shù)組
  for (i = 0, l = dirs.length; i < l; i++) {
    dir = dirs[i]
    needRuntime = true
    // 獲取節(jié)點(diǎn)當(dāng)前指令的處理方法,比如 web 平臺(tái)的 v-html、v-text、v-model
    const gen: DirectiveFunction = state.directives[dir.name]
    if (gen) {
      // 執(zhí)行指令的編譯方法,如果指令還需要運(yùn)行時(shí)完成一部分任務(wù),則返回 true,比如 v-model
      needRuntime = !!gen(el, dir, state.warn)
    }
    if (needRuntime) {
      // 表示該指令在運(yùn)行時(shí)還有任務(wù)
      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)}` : ''
        }},`
    }
  }

  // 只有指令存在運(yùn)行時(shí)任務(wù)時(shí),才會(huì)返回 res
  if (hasRuntime) {
    return res.slice(0, -1) + ']'
  }
}

genDirectives() 方法

文件位置:src\compiler\codegen\index.js

/*
  遍歷屬性數(shù)組 props,得到所有屬性組成的字符串
  如果不存在動(dòng)態(tài)屬性,則返回:'attrName,attrVal,...'
  如果存在動(dòng)態(tài)屬性,則返回:'_d(靜態(tài)屬性字符串, 動(dòng)態(tài)屬性字符串)' 
 */
function genProps(props: Array<ASTAttr>): string {
  // 靜態(tài)屬性
  let staticProps = ``
  // 動(dòng)態(tài)屬性
  let dynamicProps = ``

  // 遍歷屬性數(shù)組
  for (let i = 0; i < props.length; i++) {
    // 屬性
    const prop = props[i]
    // 屬性值
    const value = __WEEX__
      ? generateValue(prop.value)
      : transformSpecialNewlines(prop.value)

    if (prop.dynamic) {
       // 動(dòng)態(tài)屬性,`dAttrName,dAttrVal,...`
      dynamicProps += `${prop.name},${value},`
    } else {
      // 靜態(tài)屬性,'attrName:attrVal,...'
      staticProps += `"${prop.name}":${value},`
    }
  }
  // 閉合靜態(tài)屬性字符串,并去掉靜態(tài)屬性最后的 ','
  staticProps = `{${staticProps.slice(0, -1)}}`

  if (dynamicProps) {
    // 如果存在動(dòng)態(tài)屬性則返回:_d(靜態(tài)屬性字符串,動(dòng)態(tài)屬性字符串)
    return `_d(${staticProps},[${dynamicProps.slice(0, -1)}])`
  } else {
    // 說(shuō)明屬性數(shù)組中不存在動(dòng)態(tài)屬性,直接返回靜態(tài)屬性字符串
    return staticProps
  }
}

genHandlers() 方法

文件位置:src\compiler\codegen\events.js

/*
  生成自定義事件的代碼
  動(dòng)態(tài):'nativeOn|on_d(staticHandlers, [dynamicHandlers])'
  靜態(tài):`nativeOn|on${staticHandlers}`
 */
export function genHandlers (
  events: ASTElementHandlers,
  isNative: boolean
): string {
  // 原生為 nativeOn,否則為 on
  const prefix = isNative ? 'nativeOn:' : 'on:'
  // 靜態(tài)
  let staticHandlers = ``
  // 動(dòng)態(tài)
  let dynamicHandlers = ``
  /*
    遍歷 events 數(shù)組
    events = [{ name: { value: 回調(diào)函數(shù)名, ... } }]
  */ 
  for (const name in events) {
    const handlerCode = genHandler(events[name])
    if (events[name] && events[name].dynamic) {
      // 動(dòng)態(tài),dynamicHandles = `eventName,handleCode,...,`
      dynamicHandlers += `${name},${handlerCode},`
    } else {
      // staticHandlers = `eventName:handleCode,...,`
      staticHandlers += `"${name}":${handlerCode},`
    }
  }

  // 閉合靜態(tài)事件處理代碼字符串,去除末尾的 ','
  staticHandlers = `{${staticHandlers.slice(0, -1)}}`

  if (dynamicHandlers) {
    // 動(dòng)態(tài),on_d(statickHandles, [dynamicHandlers])
    return prefix + `_d(${staticHandlers},[${dynamicHandlers.slice(0, -1)}])`
  } else {
    // 靜態(tài),`on${staticHandlers}`
    return prefix + staticHandlers
  }
}

genStatic() 方法

文件位置:src\compiler\codegen\index.js

/*
  生成靜態(tài)節(jié)點(diǎn)的渲染函數(shù)
    1、將當(dāng)前靜態(tài)節(jié)點(diǎn)的渲染函數(shù)放到 staticRenderFns 數(shù)組中
    2、返回一個(gè)可執(zhí)行函數(shù) _m(idx, true or '') 
  
  hoist static sub-trees out
*/
function genStatic(el: ASTElement, state: CodegenState): string {
  // 標(biāo)記當(dāng)前靜態(tài)節(jié)點(diǎn)已經(jīng)被處理過(guò)了
  el.staticProcessed = true

  /*
    某些元素(模板)在 v-pre 節(jié)點(diǎn)中需要有不同的行為
    所有 pre 節(jié)點(diǎn)都是靜態(tài)根,因此可將其用作包裝狀態(tài)更改并在退出 pre 節(jié)點(diǎn)時(shí)將其重置
  */
  const originalPreState = state.pre
  if (el.pre) {
    state.pre = el.pre
  }

  /* 
    將靜態(tài)根節(jié)點(diǎn)的渲染函數(shù) push 到 staticRenderFns 數(shù)組中,
    比如:[`with(this){return _c(tag, data, children)}`]
  */
  state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)

  state.pre = originalPreState
  /* 
    返回一個(gè)可執(zhí)行函數(shù):_m(idx, true or '')
    idx = 當(dāng)前靜態(tài)節(jié)點(diǎn)的渲染函數(shù)在 staticRenderFns 數(shù)組中下標(biāo)
  */
  return `_m(${state.staticRenderFns.length - 1
    }${el.staticInFor ? ',true' : ''
    })`
}

genOnce() 方法

文件位置:src\compiler\codegen\index.js

/*
 處理帶有 v-once 指令的節(jié)點(diǎn),結(jié)果會(huì)有三種:
   1、當(dāng)前節(jié)點(diǎn)存在 v-if 指令,得到一個(gè)三元表達(dá)式,condition ? render1 : render2
   2、當(dāng)前節(jié)點(diǎn)是一個(gè)包含在 v-for 指令內(nèi)部的靜態(tài)節(jié)點(diǎn),
      得到 `_o(_c(tag, data, children), number, key)`
   3、當(dāng)前節(jié)點(diǎn)就是一個(gè)單純的 v-once 節(jié)點(diǎn),得到 `_m(idx, true of '')`
 
  v-once
 */
function genOnce(el: ASTElement, state: CodegenState): string {
  // 標(biāo)記當(dāng)前節(jié)點(diǎn)的 v-once 指令已經(jīng)被處理過(guò)了
  el.onceProcessed = true
  if (el.if && !el.ifProcessed) {
    /*
     如果含有 v-if 指令 && if 指令沒(méi)有被處理過(guò)
     則處理帶有 v-if 指令的節(jié)點(diǎn),最終得到一個(gè)三元表達(dá)式:
       condition ? render1 : render2 
    */ 
    return genIf(el, state)

  } else if (el.staticInFor) {
    /*
      說(shuō)明當(dāng)前節(jié)點(diǎn)是被包裹在還有 v-for 指令節(jié)點(diǎn)內(nèi)部的靜態(tài)節(jié)點(diǎn)
      獲取 v-for 指令的 key
    */
    let key = ''
    let parent = el.parent
    while (parent) {
      if (parent.for) {
        key = parent.key
        break
      }
      parent = parent.parent
    }

    // key 不存在則給出提示,v-once 節(jié)點(diǎn)只能用于帶有 key 的 v-for 節(jié)點(diǎn)內(nèi)部
    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 {
     /*
       上面幾種情況都不符合,說(shuō)明就是一個(gè)簡(jiǎn)單的靜態(tài)節(jié)點(diǎn),
       和處理靜態(tài)根節(jié)點(diǎn)時(shí)的操作一樣,得到 _m(idx, true or '')
     */ 
    return genStatic(el, state)
  }
}

genFor() 方法

文件位置:src\compiler\codegen\index.js

/*
  處理節(jié)點(diǎn)上 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 的迭代器,比如 一個(gè)數(shù)組
  const exp = el.for
  // 迭代時(shí)的別名
  const alias = el.alias
  // iterator 為 v-for = "(item ,idx) in obj" 時(shí)會(huì)有,比如 iterator1 = idx
  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''

  // 提示,v-for 指令在組件上時(shí)必須使用 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 */
    )
  }

  // 標(biāo)記當(dāng)前節(jié)點(diǎn)上的 v-for 指令已經(jīng)被處理過(guò)了
  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 指令的節(jié)點(diǎn),最終得到一個(gè)三元表達(dá)式,condition ? render1 : render2 
export function genIf(
  el: any,
  state: CodegenState,
  altGen?: Function,
  altEmpty?: string
): string {
  // 標(biāo)記當(dāng)前節(jié)點(diǎn)的 v-if 指令已經(jīng)被處理過(guò)了,避免無(wú)效的遞歸
  el.ifProcessed = true // avoid recursion
  // 得到三元表達(dá)式,condition ? render1 : render2
  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}

function genIfConditions(
  conditions: ASTIfConditions,
  state: CodegenState,
  altGen?: Function,
  altEmpty?: string
): string {

  // 長(zhǎng)度若為空,則直接返回一個(gè)空節(jié)點(diǎn)渲染函數(shù)
  if (!conditions.length) {
    return altEmpty || '_e()'
  }

  // 從 conditions 數(shù)組中拿出第一個(gè)條件對(duì)象 { exp, block }
  const condition = conditions.shift()
  // 返回結(jié)果是一個(gè)三元表達(dá)式字符串,condition ? 渲染函數(shù)1 : 渲染函數(shù)2
  if (condition.exp) {
    /*
     如果 condition.exp 條件成立,則得到一個(gè)三元表達(dá)式,
     如果條件不成立,則通過(guò)遞歸的方式找 conditions 數(shù)組中下一個(gè)元素,
     直到找到條件成立的元素,然后返回一個(gè)三元表達(dá)式
    */
    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

/*
  生成插槽的渲染函數(shù),得到:_t(slotName, children, attrs, bind)
 */
function genSlot(el: ASTElement, state: CodegenState): string {
   // 插槽名稱
  const slotName = el.slotName || '"default"'
  // 生成所有的子節(jié)點(diǎn)
  const children = genChildren(el, state)
  // 結(jié)果字符串,_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

/*
  生成動(dòng)態(tài)組件的渲染函數(shù),返回 `_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 {
   // 所有的子節(jié)點(diǎn)
  const children = el.inlineTemplate ? null : genChildren(el, state, true)
  // 返回 `_c(compName, data, children)`,compName 是 is 屬性的值
  return `_c(${componentName},${genData(el, state)}${children ? `,${children}` : ''
    })`
}

總結(jié)

渲染函數(shù)的生成過(guò)程是什么?

編譯器生成的渲染有兩類:

  • render 函數(shù),負(fù)責(zé)生成動(dòng)態(tài)節(jié)點(diǎn)的 vnode

  • staticRenderFns 數(shù)組中的 靜態(tài)渲染函數(shù),負(fù)責(zé)生成靜態(tài)節(jié)點(diǎn)的 vnode

渲染函數(shù)的生成過(guò)程,其實(shí)就是在遍歷 AST 節(jié)點(diǎn),通過(guò)遞歸的方式處理每個(gè)節(jié)點(diǎn),最后生成格式如:_c(tag, attr, children, normalizationType)

  • tag 是標(biāo)簽名

  • attr 是屬性對(duì)象

  • children 是子節(jié)點(diǎn)組成的數(shù)組,其中每個(gè)元素的格式都是 _c(tag, attr, children, normalizationTYpe) 的形式,

  • normalization 表示節(jié)點(diǎn)的規(guī)范化類型,是一個(gè)數(shù)字 0、1、2

靜態(tài)節(jié)點(diǎn)是怎么處理的?

靜態(tài)節(jié)點(diǎn)的處理分為兩步:

  • 將生成靜態(tài)節(jié)點(diǎn) vnode 函數(shù)放到 staticRenderFns 數(shù)組中

  • 返回一個(gè) _m(idx) 的可執(zhí)行函數(shù),即執(zhí)行 staticRenderFns 數(shù)組中下標(biāo)為 idx 的函數(shù),生成靜態(tài)節(jié)點(diǎn)的 vnode

v-once、v-if、v-for、組件 等都是怎么處理的?

  • 單純的 v-once 節(jié)點(diǎn)處理方式 和 靜態(tài)節(jié)點(diǎn) 一致

  • v-if 節(jié)點(diǎn)的處理結(jié)果是一個(gè) 三元表達(dá)式

  • v-for 節(jié)點(diǎn)的處理結(jié)果是可執(zhí)行的 _l 函數(shù),該函數(shù)負(fù)責(zé)生成 v-for 節(jié)點(diǎn)的 vnode

  • 組件的處理結(jié)果和普通元素一樣,得到的是形如 _c(compName) 的可執(zhí)行代碼,生成組件的 vnode

感謝各位的閱讀,以上就是“vue編譯器如何生成渲染函數(shù)”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)vue編譯器如何生成渲染函數(shù)這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

vue
AI