render 函数是怎么来的(深入浅出 Vue 中的模板编译)

new Vue({ render: h => h(App) })

这个大家都熟悉,调用 render 就会得到传入的模板(.vue文件)对应的虚拟 DOM,那么这个 render 是哪来的呢?它是怎么把 .vue 文件转成浏览器可识别的代码的呢?
render 函数是怎么来的有两种方式
  • 第一种就是经过模板编译生成 render 函数
  • 第二种是我们自己在组件里定义了 render 函数,这种会跳过模板编译的过程
本文将为大家分别介绍这两种,以及详细的编译过程原理
认识模板编译 我们知道 这个是模板,不是真实的 HTML,浏览器是不认识模板的,所以我们需要把它编译成浏览器认识的原生的 HTML
这一块的主要流程就是
  1. 提取出模板中的原生 HTML 和非原生 HTML,比如绑定的属性、事件、指令等等
  2. 经过一些处理生成 render 函数
  3. render 函数再将模板内容生成对应的 vnode
  4. 再经过 patch 过程( Diff )得到要渲染到视图中的 vnode
  5. 最后根据 vnode 创建真实的 DOM 节点,也就是原生 HTML 插入到视图中,完成渲染
上面的 1、2、3 条就是模板编译的过程了
那它是怎么编译,最终生成 render 函数的呢?
模板编译详解——源码 baseCompile()
这就是模板编译的入口函数,它接收两个参数
  • template:就是要转换的模板字符串
  • options:就是转换时需要的参数
编译的流程,主要有三步:
  1. 模板解析:通过正则等方式提取出 模板里的标签元素、属性、变量等信息,并解析成抽象语法树 AST
  2. 优化:遍历 AST 找出其中的静态节点和静态根节点,并添加标记
  3. 代码生成:根据 AST 生成渲染函数 render
这三步分别对应三个函数,后面会一一下介绍,先看一下 baseCompile 源码中是在哪里调用的
源码地址:src/complier/index.js - 11行
export const createCompiler = createCompilerCreator(function baseCompile ( template: string, // 就是要转换的模板字符串 options: CompilerOptions //就是转换时需要的参数 ): CompiledResult { // 1. 进行模板解析,并将结果保存为 AST const ast = parse(template.trim(), options)// 没有禁用静态优化的话 if (options.optimize !== false) { // 2. 就遍历 AST,并找出静态节点并标记 optimize(ast, options) } // 3. 生成渲染函数 const code = generate(ast, options) return { ast, render: code.render, // 返回渲染函数 render staticRenderFns: code.staticRenderFns } })

就这么几行代码,三步,调用了三个方法很清晰
我们先看一下最后 return 出去的是个啥,再来深入上面这三步分别调用的方法源码,也好更清楚的知道这三步分别是要做哪些处理
编译结果
比如有这样的模板

打印一下编译后的结果,也就是上面源码 return 出去的结果,看看是啥
{ ast: { type: 1, tag: 'div', attrsList: [ { name: 'id', value: 'app' } ], attrsMap: { id: 'app' }, rawAttrsMap: {}, parent: undefined, children: [ { type: 2, expression: '_s(name)', tokens: [ { '@binding': 'name' } ], text: '{{name}}', static: false } ], plain: false, attrs: [ { name: 'id', value: '"app"', dynamic: undefined } ], static: false, staticRoot: false }, render: `with(this){return _c('div',{attrs:{"id":"app"}},[_v(_s(name))])}`, staticRenderFns: [], errors: [], tips: [] }

看不明白也没有关系,注意看上面提到的三步都干了啥
  • ast 字段,就是第一步生成的
  • static 字段,就是标记,是在第二步中根据 ast 里的 type 加上去的
  • render 字段,就是第三步生成的
有个大概的印象了,然后再来看源码
1. parse()
源码地址:src/complier/parser/index.js - 79行
就是这个方法就是解析器的主函数,就是它通过正则等方法提取出 模板字符串里所有的 tagpropschildren 信息,生成一个对应结构的 ast 对象
parse 接收两个参数
  • template :就是要转换的模板字符串
  • options:就是转换时需要的参数。它包含有四个钩子函数,就是用来把 parseHTML 解析出来的字符串提取出来,并生成对应的 AST
核心步骤是这样的:
调用 parseHTML 函数对模板字符串进行解析
  • 解析到开始标签、结束标签、文本、注释分别进行不同的处理
  • 解析过程中遇到文本信息就调用文本解析器 parseText 函数进行文本解析
  • 解析过程中遇到包含过滤器,就调用过滤器解析器 parseFilters 函数进行解析
每一步解析的结果都合并到一个对象上(就是最后的 AST)
这个地方的源码实在是太长了,有大几百行代码,我就只贴个大概吧,有兴趣的自己去看一下
export function parse ( template: string, // 要转换的模板字符串 options: CompilerOptions // 转换时需要的参数 ): ASTElement | void { parseHTML(template, { warn, expectHTML: options.expectHTML, isUnaryTag: options.isUnaryTag, canBeLeftOpenTag: options.canBeLeftOpenTag, shouldDecodeNewlines: options.shouldDecodeNewlines, shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref, shouldKeepComment: options.comments, outputSourceRange: options.outputSourceRange, // 解析到开始标签时调用,如 start (tag, attrs, unary, start, end) { // unary 是否是自闭合标签,如 render 函数是怎么来的(深入浅出 Vue 中的模板编译)
文章图片

如图可以知道,如果有 template,就不会管 el 了,所以 template 比 el 的优先级更高,比如
那我们自己写了 render 呢?
{{ name }}

这个代码执行后页面渲染出来只有 好好学习,天天向上
可以得出 render 函数的优先级更高
因为不管是 el 挂载的,还是 emplate 最后都会被编译成 render 函数,而如果已经有了 render 函数了,就跳过前面的编译了
这一点在源码里也有体现
在源码中找到答案:dist/vue.js - 11927行
Vue.prototype.$mount = function ( el, hydrating ) { el = el && query(el); var options = this.$options; // 如果没有 render if (!options.render) { var template = options.template; // 再判断,如果有 template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template); } } else if (template.nodeType) { template = template.innerHTML; } else { return this } // 再判断,如果有 el } else if (el) { template = getOuterHTML(el); } } return mount.call(this, el, hydrating) };

2. 更灵活的写法
比如说我们需要写很多 if 判断的时候

不知道你有没有写过类似上面这样的代码呢?
我们换一种方式来写出和上面一模一样的代码看看,直接写 render

搞定!就这!就这?
没错,就这!
或者下面这样,多次调用的时候就很方便

补充 如果想知道更多格式的模板编译出来是什么样的,可以这样
Vue2 的模板编译可以安装 vue-template-compiler
Vue3 的模板编译可以点这里
然后自行测试
另外在 Vue3 里模板编译部分有一些修改,可以点击下面链接,看下 深入浅出虚拟 DOM 和 Diff 算法,里面有介绍
往期精彩
  • 深入浅出虚拟 DOM 和 Diff 算法,及 Vue2 与 Vue3 中的区别
  • Vue3的7种和Vue2的12种组件通信,值得收藏
  • 最新的 Vue3.2 都更新了些什么了解一下
  • JavaScript进阶知识点
  • 前端异常监控和容灾
  • 20分钟助你拿下HTTP和HTTPS,巩固你的HTTP知识体系
结语 【render 函数是怎么来的(深入浅出 Vue 中的模板编译)】如果本文对你有一丁点帮助,点个赞支持一下吧,感谢感谢

    推荐阅读