Vue 源码解读(10)—— 编译器 之 生成渲染函数

本文深入解析 Vue 编译器如何将 HTML 模板转化为渲染函数,涵盖 AST 语法树、genElement、genChildren 等关键步骤,助你理解 Vue 渲染函数的生成过程。
摘要由CSDN通过智能技术生成

Python微信订餐小程序课程视频

https://edu.csdn.net/course/detail/36074

Python实战量化交易理财系统

https://edu.csdn.net/course/detail/35475

前言

这篇文章是 Vue 编译器的最后一部分,前两部分分别是:Vue 源码解读(8)—— 编译器 之 解析Vue 源码解读(9)—— 编译器 之 优化

从 HTML 模版字符串开始,解析所有标签以及标签上的各个属性,得到 AST 语法树,然后基于 AST 语法树进行静态标记,首先标记每个节点是否为静态静态,然后进一步标记出静态根节点。这样在后续的更新中就可以跳过这些静态根节点的更新,从而提高性能。

这最后一部分讲的是如何从 AST 生成渲染函数。

目标

深入理解渲染函数的生成过程,理解编译器是如何将 AST 变成运行时的代码,也就是我们写的类 html 模版最终变成了什么?

源码解读

入口

/src/compiler/index.js

/**
 * 在这之前做的所有的事情,只有一个目的,就是为了构建平台特有的编译选项(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 对象上都设置了元素的所有信息,比如,标签信息、属性信息、插槽信息、父节点、子节点等。
  // 具体有那些属性,查看 options.start 和 options.end 这两个处理开始和结束标签的方法
  const ast = parse(template.trim(), options)
  // 优化,遍历 AST,为每个节点做静态标记
  // 标记每个节点是否为静态节点,然后进一步标记出静态根节点
  // 这样在后续更新中就可以跳过这些静态节点了
  // 标记静态根,用于生成渲染函数阶段,生成静态根节点的渲染函数
  if (options.optimize !== false) {
    optimize(ast, options)
  }
  // 代码生成,将 ast 转换成可执行的 render 函数的字符串形式
  // code = {
  // render: `with(this){return ${\_c(tag, data, children, normalizationType)}}`,
  // staticRenderFns: [\_c(tag, data, children, normalizationType), ...]
  // }
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})


generate

/src/compiler/codegen/index.js

/**
 * 从 AST 生成渲染函数
 * @returns {
 * render: `with(this){return \_c(tag, data, children)}`,
 * staticRenderFns: state.staticRenderFns
 * } 
 */
export function generate(
 ast: ASTElement | void,
 options: CompilerOptions
): CodegenResult {
  // 实例化 CodegenState 对象,生成代码的时候需要用到其中的一些东西
  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 ? genElement(ast, state) : '\_c("div")'
  return {
    render: `with(this){return ${code}}`,
    staticRenderFns: state.staticRenderFns
  }
}


genElement

/src/compiler/codegen/index.js

阅读建议

先读最后的 else 模块生成 code 的语句部分,即处理自定义组件和原生标签的 else 分支,理解最终生成的数据格式是什么样的;然后再回头阅读 genChildrengenData,先读 genChildren,代码量少,彻底理解最终生成的数据结构,最后再从上到下去阅读其它的分支。

在阅读以下代码时,请把 Vue 源码解读(8)—— 编译器 之 解析(下) 最后得到的 AST 对象放旁边辅助阅读,因为生成渲染函数的过程就是在处理该对象上众多的属性的过程。

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
    // 处理动态组件和普通元素(自定义组件、原生标签)
    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)
      }

      // 处理子节点,得到所有子节点字符串格式的代码组成的数组,格式ÿ
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值