render函数实现原理

Posted 前端精髓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了render函数实现原理相关的知识,希望对你有一定的参考价值。

我们先使用vue的模板编译库vue-template-compiler验证一下输出结果。

const compiler = require('vue-template-compiler')
let str = `<div v-if="msg">999</div>`

console.log(compiler.compile(str))

发现包含ast和render这两个属性。

{
  ast: {
    type: 1,
    tag: 'div',
    attrsList: [],
    attrsMap: { 'v-if': 'msg' },
    rawAttrsMap: {},
    parent: undefined,
    children: [ [Object] ],
    if: 'msg',
    ifConditions: [ [Object] ],
    plain: true,
    static: false,
    staticRoot: false,
    ifProcessed: true
  },
  render: `with(this){return (msg)?_c('div',[_v("999")]):_e()}`,
  staticRenderFns: [],
  errors: [],
  tips: []
}

render属性的内容是根据ast属性生成的,之前我有文章已经讲解了ast是如何生成的,今天主要分析render函数的生成过程。

// <div v-if="msg">999</div>
with(this){return (msg)?_c('div',[_v("999")]):_e()}

每个指令的生成过程都不一样,if 指令会使用三元运算符,for 指令会生成一个新的函数,并且会使用一些辅助函数 _c 和 _v 等等。

// <div><span v-for="item in list">6</span></div>
with(this){return _c('div',_l((list),function(item){return _c('span',[_v("6")])}),0)}

开始了解如何生成render函数

function generate (
  ast,
  options
) {
  var code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, options)) : '_c("div")';
  return {
    render: ("with(this){return " + code + "}")
  }
}

我们暂时先了解 genElement 函数,看看如何生成元素的。

function genElement (el, state) {
  if (el.parent) {
    el.pre = el.pre || el.parent.pre;
  }

  if (el.staticRoot && !el.staticProcessed) {
    // return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
    // return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {
    // return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {
    // return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
    // return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    // return genSlot(el, state)
  } else {
    // component or element
    var code;
    if (el.component) {
      // code = genComponent(el.component, el, state);
    } else {
      var data;

      var children = el.inlineTemplate ? null : genChildren(el, state, true);
      code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
    }
    // module transforms
    //for (var i = 0; i < state.transforms.length; i++) {
    //  code = state.transforms[i](el, code);
    //}
    return code
  }
}

在生成元素的过程中,会先判断元素上面的各种指令进行分别的处理,指令的生成我们暂时不讨论,我们先看看 genChildren 的处理。

function genChildren (
  el,
  state,
  checkSkip,
  altGenElement,
  altGenNode
) {
  var children = el.children;
  if (children.length) {
    var gen = altGenNode || genNode;
    return ("[" + (children.map(function (c) { return gen(c, state); }).join(',')) + "]")
  }
}

// genNode 函数是根据不同的 type 生成不同的内容
function genNode (node, state) {
  if (node.type === 1) {
    // 如果子元素还是元素会递归的处理
    return genElement(node, state)
  } else if (node.type === 3 && node.isComment) {
    // return genComment(node)
  } else {
    // 生成文本
    return genText(node)
  }
}

function genText (text) {
  return ("_v(" + (text.type === 2
    ? text.expression // no need for () because already wrapped in _s()
    : JSON.stringify(text.text)) + ")")
}

最后执行我们的代码,验证是否会成功:

template: '<div><span>11</span></div>'
render: "with(this){return _c('div',[_c('span',[_v(11)])])}"

以上是关于render函数实现原理的主要内容,如果未能解决你的问题,请参考以下文章

Render函数

vue中渲染函数Render原理解析

vue渲染虚拟dom树原理

带有片段参数的 Grails render()

Vue数据绑定原理及简单实现

render函数里面实现佛如循环的写法