>  기사  >  웹 프론트엔드  >  소스 코드를 구문 분석하고 vue 컴파일러가 렌더링 기능을 생성하는 방법을 확인하세요!

소스 코드를 구문 분석하고 vue 컴파일러가 렌더링 기능을 생성하는 방법을 확인하세요!

青灯夜游
青灯夜游앞으로
2022-01-10 19:00:362161검색

이 기사에서는 소스 코드를 심층적으로 분석하고, vue 소스 코드의 문제를 살펴보고, vue 컴파일러가 렌더링 기능을 생성하는 방법을 살펴보는 것이 모든 사람에게 도움이 되기를 바랍니다.

소스 코드를 구문 분석하고 vue 컴파일러가 렌더링 기능을 생성하는 방법을 확인하세요!

처음 두 기사는 주로 vue 컴파일러의 parsingoptimization을 이해합니다.

  • 구성 요소의 html 템플릿을 AST 구문 tree 정적 표시를 진행합니다. 먼저 각 노드가
  • 정적 노드
  • 인지 여부를 표시한 다음 정적 루트 노드를 추가로 표시하여 후속 업데이트에서 정적 루트 노드의 업데이트를 건너뛸 수 있도록 하여 성능을 향상시킵니다 아래에서 자세히 알아보세요vue
  • 컴파일러가
AST 구문 트리

에서 실행 중인 렌더링 함수를 생성하는 방법 소스 코드 자세히

createCompiler() 메서드 - 항목

파일 위치: /src/compiler/index.js

가장 중요한 것은 generate(ast, options)/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) 메서드입니다. 이 메서드는

AST🎜 구문 트리.🎜rrreee 🎜🎜🎜generate() 메서드🎜🎜🎜🎜🎜파일 위치: srccompilercodegenindex.js🎜🎜🎜값을 🎜code🎜, 주요 콘텐츠는 🎜 genElement(ast, state)🎜 메서드를 통해 생성됩니다.🎜rrreee🎜🎜🎜genElement() 메서드🎜🎜🎜🎜🎜파일 위치: srccompilercodegenindex.js🎜🎜rrreee🎜 🎜🎜genChildren() 메서드 🎜🎜🎜🎜🎜파일 위치: srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genNode() 메서드 🎜 🎜 🎜🎜🎜파일 위치: srccompilercodegenindex.j s 파일 위치: <code>srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genData() 메서드🎜🎜🎜🎜🎜파일 위치: srccompilercodegenindex. js🎜🎜rrreee🎜🎜🎜genDirectives() 메서드🎜🎜 🎜🎜 🎜파일 위치: srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genDirectives() 메서드 🎜🎜🎜🎜 🎜파일 위치 : srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genHandlers ( ) 메서드 🎜🎜🎜🎜🎜파일 위치: srccompilercodegenevents.js🎜🎜rrreee🎜🎜🎜genStatic() 메서드 🎜 File 위치 : srccompilercodegenindex.js 🎜🎜 rrreee🎜🎜🎜genonce () 메소드 🎜🎜🎜🎜🎜file 위치 : srccompilercodegenindex.js 🎜🎜rrreee🎜🎜 🎜genFor() 메서드 🎜🎜🎜🎜🎜파일 위치: srccompilercode genindex.js 🎜🎜rrreee🎜🎜🎜genIf() 메서드 🎜🎜🎜🎜🎜파일 위치: srccompilercodegenindex.js code>🎜🎜rrreee🎜🎜🎜genSlot() 메서드 🎜🎜🎜🎜🎜 파일 위치: <code>srccompilercodegenindex.js🎜🎜rrreee🎜🎜🎜genComponent() 메서드 🎜🎜🎜🎜🎜파일 위치: srccompilercodegenindex.js🎜🎜rrreee🎜Summary🎜
🎜🎜rendering 함수 생성 프로세스는 무엇인가요? 🎜🎜
🎜컴파일러에서 생성되는 렌더링에는 두 가지 유형이 있습니다. 🎜🎜🎜render 함수, 동적 노드의 vnode 생성을 담당합니다🎜 🎜staticRenderFns 배열의 정적 렌더링 함수가 담당합니다. 정적 노드의 vnode를 생성하기 위한 렌더링 함수의 생성 프로세스는 실제로 AST 노드를 순회하며 각 노드는 재귀적으로 처리되며 최종 생성된 형식은 다음과 같습니다. _c(tag, attr , 어린이, 정규화 유형): 🎜
  • tag는 태그 이름입니다. 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는 속성 개체입니다.

children은 하위 노드로 구성된 배열입니다. 여기서 형식은 각 요소는 _c(tag, attr, children, NormalizationTYPE) 형식이며, normalization은 노드의 정규화 유형을 나타내며 숫자 0입니다. , 1, 2

🎜
🎜정적 노드는 어떻게 처리되나요?🎜
🎜정적 노드 처리는 두 단계로 나누어집니다. 🎜🎜🎜정적 노드 생성 기능을 넣습니다. vnodestaticRenderFns로 변환하면 _m(idx)의 실행 가능한 함수가 배열로 반환됩니다. 즉, staticRenderFns가 실행됩니다. 배열의 첨자는 정적 노드 vnode🎜🎜
🎜를 생성하는 idx 함수입니다. v-once, v-if, v-for, 구성 요소 등을 처리하나요?🎜
🎜🎜간단한 v-once 노드 처리 방법은 정적 노드🎜🎜v와 동일합니다. -if 노드 처리 결과는 삼항식🎜🎜v-for 노드의 처리 결과가 실행 가능한 _l 함수이고, v를 생성하는 역할을 담당합니다. for 노드의 vnode🎜🎜 구성 요소의 처리 결과는 일반 요소의 처리 결과와 동일합니다. _c(compName) 형태의 코드 및 구성 요소의 vnode🎜🎜🎜[관련 권장 사항: 🎜vue.js tutorial🎜]🎜

위 내용은 소스 코드를 구문 분석하고 vue 컴파일러가 렌더링 기능을 생성하는 방법을 확인하세요!의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제