VueStudyNote icon indicating copy to clipboard operation
VueStudyNote copied to clipboard

03 生成render函数

Open xwjie opened this issue 6 years ago • 0 comments

取得了语法树之后,分三步。

  1. 生成render函数字符串
  2. 生成render函数
  3. 如何调用

生成函数字符串

我们使用 snabbdom 来产生和处理我们的虚拟dom,所以我们需要把AST生成类似下面的render函数字符串。

  产生类似这样的snabbdom函数字符串
  h('div#container.two.classes', { on: { click: someFn } }, [
      h('span', { style: { fontWeight: 'bold' } }, 'This is bold'),
      h('h1', strVar),
      'this is string',
      h('a', { props: { href: '/foo' } }, 'I\'ll take you places!')
  ])

这里只有一个h函数,实际上VUE有多个函数,分别为

  • _c 是 createElement(创建元素),
  • _m 是 renderStatic(渲染静态节点),
  • _v 是 createTextVNode(创建文本dom),
  • _s 是 toString (转换为字符串)
  • _f :filter函数,如 message | capitalize | wrap('-') 会编译成 _f("wrap")(_f("capitalize")(message),'-')

这里的h相当于vue的 _c。

export function installRenderHelpers (target: any) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
}

现在还没有处理class和attribute,所以代码很简单,一个递归即可。

处理字符串类型的时候,需要把它解析为变量。就是

变量1:{{message}}。

需要处理为

"变量1:"+this.message+"。"

最后的this我们提取到外面 with(this), 调用的时候call 绑定this到我们自己的实例上

使用的 正则表达式。代码来自vue

export function parseText(
  text: string,
  re: string
) {
  if (!re.test(text)) {
    return
  }
  const tokens = []
  let lastIndex = re.lastIndex = 0
  let match, index, tokenValue
  while ((match = re.exec(text))) {
    index = match.index

    // push text token
    if (index > lastIndex) {
      tokenValue = text.slice(lastIndex, index)
      tokens.push(JSON.stringify(tokenValue))
    }

    // tag token
    var exp = match[1].trim()
    tokens.push(exp)

    lastIndex = index + match[0].length
  }

  if (lastIndex < text.length) {
    tokenValue = text.slice(lastIndex)
    tokens.push(JSON.stringify(tokenValue))
  }

  return {
    expression: tokens.join('+'),
  }
}

所以根据AST语法树生成render函数字符串的 递归 处理代码如下:


function createRenderStr(ast: ASTNode): string {
  let str: string = ""

  if (ast.type == 1) {
    str = createRenderStrElemnet(ast)
  } else if (ast.type == 3) {
    str = createRenderStrText(ast)
  } else {
    warn(`wrong type:${ast.type}`)
  }

  return str
}

function createRenderStrElemnet(node: any): string {
  log('createRenderStrElemnet', node)

  let str: string = 'h(' + JSON.stringify(node.tag)

  let attrs = node.attrsMap

  if (attrs) {
    str += ',{'

    // why not use for..in, see eslint `no-restricted-syntax`
    Object.keys(attrs).every(attrname => {
      // str += JSON.stringify(attrname) + '=' + JSON.stringify(attrs[attrname]) + ' '
    })

    str += '}'
  }

  if (node.children) {
    str += ',['

    node.children.forEach(child => {
      str += createRenderStr(child) + ','
    })

    str += ']'
  }

  str += ')'

  return str
}

function createRenderStrText(node: any): string {
  return node.text
}


相当简单清晰,有没有??

生成函数

使用 new Function,参考 这里

function renderToFunctions(renderStr: string): Function {
  return new Function(`with(this){return ${renderStr}}`)
}

生成之后,保存到实例的 $render

const { render } = compileToFunctions(this.$options.template)

// save to this.$render
this.$render = render

如何调用

大家注意到,我们生成的函数是没有指定参数的。调用的时候使用call绑定当前的实例到this上,这就是为什么渲染函数前面有个 with(this)

  // 新的虚拟节点
  let vnode = vm.$render.call(proxy)

vue里面处理传入当前实例,还会把 $createElement 函数传入,一开始没有太明白,后面想应该是自定义渲染函数render函数的时候需要用到的。这个自定义render函数我们后面再支持,到时候再加,目前就一个当前实例即可。

// \vue\src\core\instance\render.js 的 _render 函数
//xiaowenjie 第二个参数,应该是给自定义render函数用的。
vnode = render.call(vm._renderProxy, vm.$createElement)

最终效果

测试代码 Xiao/example/helloworld.html

模板

template: '<h1>变量1:{{message}}。<br/>变量2:{{message2}}。</h1>',

生成的渲染函数字符串

h("h1",{},["变量1:"+message+"。",h("br",{},[]),"变量2:"+message2+"。",])

生成的render函数

render ƒ anonymous() {
with(this){return h("h1",{},["变量1:"+message+"。",h("br",{},[]),"变量2:"+message2+"。",])}
}

xwjie avatar Jan 08 '18 13:01 xwjie