• 技术文章 >web前端 >Vue.js

    解析源码,看看 vue 编译器是怎么生成渲染函数的!

    青灯夜游青灯夜游2022-01-10 19:00:36转载63
    本篇文章带大家深入解析源码,从 vue 源码看问题,来看看 vue 编译器是怎么生成渲染函数的,希望对大家有所帮助。

    前两篇主要了解了 vue 编译器的 解析优化

    下面就了解一下 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() 方法

    文件位置:src\compiler\codegen\index.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() 方法

    文件位置: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) {
    
        /*
          处理静态根节点,生成节点的渲染函数
            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() 方法

    文件位置:src\compiler\codegen\index.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() 方法

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

    function genNode (node: ASTNode, state: CodegenState): string {
      // 处理普通元素节点
      if (node.type === 1) {
        return genElement(node, state)
      } else if (node.type === 3 && node.isComment) {
        // 处理文本注释节点
        return genComment(node)
      } else {
        // 处理文本节点
        return genText(node)
      }
    }

    genComment() 方法

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

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

    genText() 方法

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

    // 得到返回值,格式为:`_v(xxxxx)`
    export function genText (text: ASTText | ASTExpression): string {
      return `_v(${text.type === 2
        ? text.expression // no need for () because already wrapped in _s()
        : transformSpecialNewlines(JSON.stringify(text.text))
      })`
    }

    genData() 方法

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

    /*
      处理节点上的众多属性,最后生成这些属性组成的 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() 方法

    文件位置:src\compiler\codegen\index.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() 方法

    文件位置:src\compiler\codegen\index.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() 方法

    文件位置:src\compiler\codegen\events.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() 方法

    文件位置:src\compiler\codegen\index.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() 方法

    文件位置:src\compiler\codegen\index.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() 方法

    文件位置:src\compiler\codegen\index.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() 方法

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

    // 处理带有 v-if 指令的节点,最终得到一个三元表达式,condition ? render1 : render2 
    export function genIf(
      el: any,
      state: CodegenState,
      altGen?: Function,
      altEmpty?: string
    ): string {
      // 标记当前节点的 v-if 指令已经被处理过了,避免无效的递归
      el.ifProcessed = true // avoid recursion
      // 得到三元表达式,condition ? render1 : render2
      return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
    }
    
    function genIfConditions(
      conditions: ASTIfConditions,
      state: CodegenState,
      altGen?: Function,
      altEmpty?: string
    ): string {
    
      // 长度若为空,则直接返回一个空节点渲染函数
      if (!conditions.length) {
        return altEmpty || '_e()'
      }
    
      // 从 conditions 数组中拿出第一个条件对象 { exp, block }
      const condition = conditions.shift()
      // 返回结果是一个三元表达式字符串,condition ? 渲染函数1 : 渲染函数2
      if (condition.exp) {
        /*
         如果 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() 方法

    文件位置:src\compiler\codegen\index.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() 方法

    文件位置:src\compiler\codegen\index.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}` : ''
        })`
    }

    总结

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

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

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

    静态节点是怎么处理的?

    静态节点的处理分为两步:

    v-once、v-if、v-for、组件 等都是怎么处理的?

    【相关推荐:vue.js教程

    以上就是解析源码,看看 vue 编译器是怎么生成渲染函数的!的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    上一篇:Vue实例要怎么挂载?聊聊实例挂载的过程 下一篇:深入聊聊Vue中的一些常用指令

    相关文章推荐

    • vue中v-bind和v-model的区别是什么• vue怎样解决axios请求出现前端跨域问题(实例详解)• Vue实例要怎么挂载?聊聊实例挂载的过程• vue中用什么来发送请求• vue里data为什么要用return返回数据

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网