Maison  >  Article  >  interface Web  >  Analysez le code source et voyez comment le compilateur vue génère la fonction de rendu !

Analysez le code source et voyez comment le compilateur vue génère la fonction de rendu !

青灯夜游
青灯夜游avant
2022-01-10 19:00:362142parcourir

Cet article vous amènera à analyser en profondeur le code source, à examiner le problème à partir du code source de vue et à voir comment le compilateur vue génère la fonction de rendu. J'espère que cela sera utile à tout le monde.

Analysez le code source et voyez comment le compilateur vue génère la fonction de rendu !

Les deux premiers articles comprennent principalement le parsing et optimisation du compilateur vue :

  • analyse le modèle html du composant en un objet ASTbasé sur le
  • Syntaxe AST tree
  • procédez au marquage statique, marquez d'abord si chaque nœud est un nœud statique, puis marquez ensuite le nœud racine statique, afin que la mise à jour du nœud racine statique puisse être ignorée dans les mises à jour ultérieures, améliorant ainsi les performances
  • Apprenons cela ci-dessous
vue

Comment le compilateur génère la fonction de rendu en cours d'exécution à partir de l'arbre de syntaxe AST En profondeur dans le code source

méthode createCompiler() - entrée

Emplacement du fichier : /src/compiler/index.js

/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 !== &#39;template&#39; &&
      el.tag !== &#39;slot&#39;
    ) {
      /* 
       优化处理:
         - 条件:只有一个子节点 && 子节点的上有 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

    /*
      返回一个数组,其中每个元素都是一个子节点的渲染函数
      格式:[&#39;_c(tag, data, children, normalizationType)&#39;, ...]
    */ 
    return `[${children.map(c => gen(c, state)).join(&#39;,&#39;)}]${
      normalizationType ? `,${normalizationType}` : &#39;&#39;
    }`
  }
}

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 = &#39;{&#39;

  /*
    首先先处理指令,因为指令可能在生成其它属性之前改变这些属性
    执行指令编译方法,如 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 + &#39;,&#39;

  // 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: &#39;component&#39; }
  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: &#39;_d(静态属性字符串, 动态属性字符串)&#39; }

    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: &#39;_u(xxx)&#39; }
  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(/,$/, &#39;&#39;) + &#39;}&#39;
  
  /*
    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 = &#39;directives:[&#39;
  // 用于标记指令是否需要在运行时完成的任务,比如 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)}` : &#39;&#39;
        }${dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : &#39;&#39;
        }${dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : &#39;&#39;
        }},`
    }
  }

  // 只有指令存在运行时任务时,才会返回 res
  if (hasRuntime) {
    return res.slice(0, -1) + &#39;]&#39;
  }
}

genDirectives() 方法

文件位置:srccompilercodegenindex.js

/*
  遍历属性数组 props,得到所有属性组成的字符串
  如果不存在动态属性,则返回:&#39;attrName,attrVal,...&#39;
  如果存在动态属性,则返回:&#39;_d(静态属性字符串, 动态属性字符串)&#39; 
 */
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 {
      // 静态属性,&#39;attrName:attrVal,...&#39;
      staticProps += `"${prop.name}":${value},`
    }
  }
  // 闭合静态属性字符串,并去掉静态属性最后的 &#39;,&#39;
  staticProps = `{${staticProps.slice(0, -1)}}`

  if (dynamicProps) {
    // 如果存在动态属性则返回:_d(静态属性字符串,动态属性字符串)
    return `_d(${staticProps},[${dynamicProps.slice(0, -1)}])`
  } else {
    // 说明属性数组中不存在动态属性,直接返回静态属性字符串
    return staticProps
  }
}

genHandlers() 方法

文件位置:srccompilercodegenevents.js

/*
  生成自定义事件的代码
  动态:&#39;nativeOn|on_d(staticHandlers, [dynamicHandlers])&#39;
  静态:`nativeOn|on${staticHandlers}`
 */
export function genHandlers (
  events: ASTElementHandlers,
  isNative: boolean
): string {
  // 原生为 nativeOn,否则为 on
  const prefix = isNative ? &#39;nativeOn:&#39; : &#39;on:&#39;
  // 静态
  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},`
    }
  }

  // 闭合静态事件处理代码字符串,去除末尾的 &#39;,&#39;
  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 &#39;&#39;) 
  
  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 &#39;&#39;)
    idx = 当前静态节点的渲染函数在 staticRenderFns 数组中下标
  */
  return `_m(${state.staticRenderFns.length - 1
    }${el.staticInFor ? &#39;,true&#39; : &#39;&#39;
    })`
}

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 &#39;&#39;)`
 
  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 = &#39;&#39;
    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 !== &#39;production&#39; && state.warn(
        `v-once can only be used inside v-for that is keyed. `,
        el.rawAttrsMap[&#39;v-once&#39;]
      )

      return genElement(el, state)
    }

    // 生成 `_o(_c(tag, data, children), number, key)`
    return `_o(${genElement(el, state)},${state.onceId++},${key})`
  } else {
     /*
       上面几种情况都不符合,说明就是一个简单的静态节点,
       和处理静态根节点时的操作一样,得到 _m(idx, true or &#39;&#39;)
     */ 
    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}` : &#39;&#39;
  const iterator2 = el.iterator2 ? `,${el.iterator2}` : &#39;&#39;

  // 提示,v-for 指令在组件上时必须使用 key
  if (process.env.NODE_ENV !== &#39;production&#39; &&
    state.maybeComponent(el) &&
    el.tag !== &#39;slot&#39; &&
    el.tag !== &#39;template&#39; &&
    !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[&#39;v-for&#39;],
      true /* tip */
    )
  }

  // 标记当前节点上的 v-for 指令已经被处理过了
  el.forProcessed = true // avoid recursion

  // 返回 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})`
  return `${altHelper || &#39;_l&#39;}((${exp}),` +
    `function(${alias}${iterator1}${iterator2}){` +
    `return ${(altGen || genElement)(el, state)}` +
    &#39;})&#39;
}

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 || &#39;_e()&#39;
  }

  // 从 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 || &#39;"default"&#39;
  // 生成所有的子节点
  const children = genChildren(el, state)
  // 结果字符串,_t(slotName, children, attrs, bind)
  let res = `_t(${slotName}${children ? `,function(){return ${children}}` : &#39;&#39;}`
  
  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[&#39;v-bind&#39;]
  
  if ((attrs || bind) && !children) {
    res += `,null`
  }
  if (attrs) {
    res += `,${attrs}`
  }
  if (bind) {
    res += `${attrs ? &#39;&#39; : &#39;,null&#39;},${bind}`
  }
  return res + &#39;)&#39;
}

genComponent() 方法

文件位置:srccompilercodegenindex.js

/*
  生成动态组件的渲染函数,返回 `_c(compName, data, children)`

  componentName is el.component, take it as argument to shun flow&#39;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}` : &#39;&#39;
    })`
}

总结

渲染函数的生成过程是什么?

编译器生成的渲染有两类:

  • render 函数,负责生成动态节点的 vnode
  • staticRenderFns 数组中的 静态渲染函数,负责生成静态节点的 vnode

渲染函数的生成过程,其实就是在遍历 AST 节点,通过递归的方式处理每个节点,最后生成格式如:_c(tag, attr, children, normalizationType)La plus importante est la méthode

generate(ast, options)🎜, qui est responsable de la génération des fonctions de rendu à partir du 🎜AST🎜 arbre de syntaxe.🎜rrreee 🎜🎜🎜méthode Generate()🎜🎜🎜🎜🎜Emplacement du fichier : srccompilercodegenindex.js🎜🎜🎜Lors de l'attribution d'une valeur à 🎜code🎜, le contenu principal passe par la méthode 🎜 genElement(ast, state)🎜 pour générer.🎜rrreee🎜🎜🎜genElement() méthode🎜🎜🎜🎜🎜Emplacement du fichier : srccompilercodegenindex.js🎜🎜rrreee🎜 🎜🎜méthode genChildren() 🎜🎜🎜🎜🎜Emplacement du fichier : srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genNode() méthode 🎜🎜 🎜🎜🎜Emplacement du fichier : srccompilercodegenindex.js code>🎜🎜rrreee🎜🎜🎜méthode genComment()🎜🎜🎜🎜🎜Emplacement du fichier : <code>srccompilercodegenindex.js🎜🎜rrreee🎜 🎜🎜 Méthode genText()🎜🎜🎜🎜🎜 Emplacement du fichier : srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜Méthode genData()🎜🎜🎜🎜🎜Emplacement du fichier : srccompilercodegenindex.js 🎜🎜rrreee🎜🎜🎜genDirectives() méthode🎜🎜 🎜🎜 🎜Emplacement du fichier : srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genDirectives() méthode 🎜🎜🎜🎜🎜 Emplacement du fichier : srccompilercodegenindex .js🎜🎜rrreee🎜🎜🎜genHandlers ( ) Méthode 🎜🎜🎜🎜🎜Emplacement du fichier : srccompilercodegenevents.js🎜🎜rrreee🎜🎜🎜genStatic() méthode 🎜🎜 🎜🎜🎜Fichier emplacement : srccompilercodegenindex.js🎜🎜 méthode rrreee🎜🎜🎜genOnce() 🎜🎜🎜🎜🎜Emplacement du fichier : srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genFor() méthode 🎜🎜🎜🎜🎜Emplacement du fichier : srccompilercode genindex.js 🎜🎜rrreee🎜🎜🎜méthode genIf() 🎜🎜🎜🎜🎜Emplacement du fichier : srccompilercodegenindex.js🎜🎜r rreee 🎜🎜🎜Méthode genSlot() 🎜🎜🎜🎜🎜 Emplacement du fichier : srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜Méthode genComponent() 🎜🎜🎜🎜🎜Emplacement du fichier : srccompilercode genindex.js🎜🎜rrreee🎜Résumé🎜<blockquote>🎜🎜rendu Quel est le processus de génération de fonction ? 🎜🎜</blockquote>🎜Il existe deux types de rendus générés par le compilateur : 🎜🎜🎜<code>render fonction, qui est responsable de la génération du vnode des nœuds dynamiques🎜 🎜staticRenderFns La fonction de rendu statique dans le tableau est responsable de la génération du vnode de nœuds statiques. Le processus de génération de la fonction de rendu traverse en fait le nœud AST, traite chaque nœud de manière récursive et génère enfin un format tel que : _c(tag, attr, children, normalizationType)  : 🎜
  • tag est le nom de la balise 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
  • attr est l'objet attribut

children est un tableau composé de nœuds enfants, où le format de chaque élément est Il se présente sous la forme de _c(tag, attr, children, normalizationTYpe), normalization représente le type de normalisation du nœud, qui est un nombre 0 , 1, 2

🎜
🎜Comment traiter les nœuds statiques ?🎜
🎜Le traitement des nœuds statiques est divisé en deux étapes : 🎜🎜🎜Mettre la fonction de génération de nœuds statiques vnode into staticRenderFns renvoie une fonction exécutable de _m(idx) dans le tableau, c'est-à-dire qu'il exécute staticRenderFns. L'indice dans le tableau est la fonction idx pour générer des nœuds statiques vnode🎜🎜
🎜Comment sont v-once, v-if, v-for, composants, etc. traités ?🎜
🎜🎜La méthode simple de traitement des nœuds v-once est cohérente avec le nœud statique🎜🎜v- si le résultat du traitement du nœud est un Le résultat du traitement du nœud expression ternaire🎜🎜v-for est la fonction exécutable _l, qui est responsable de la génération de v- Le résultat du traitement du composant <code>vnode🎜🎜 du nœud for est le même que celui d'un élément ordinaire. Ce qui est obtenu est un code exécutable. sous la forme de _c(compName), et le vnode du composant🎜🎜🎜[Recommandations associées : 🎜tutoriel vue.js🎜]🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer