Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

03 生成render函数 #5

Open
xwjie opened this issue Jan 8, 2018 · 0 comments
Open

03 生成render函数 #5

xwjie opened this issue Jan 8, 2018 · 0 comments

Comments

@xwjie
Copy link
Owner

xwjie commented Jan 8, 2018

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

  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+"。",])}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant