1、模板编译
- 模板编译的主要目的是将模板 (template) 转换为渲染函数 (render)
<div>
<h1@click="handler">title</h1>
<p>some content</p>
</div>
- 渲染函数 render
render (h) {
return h('div', [
h('h1', { on: { click: this.handler} }, 'title'),
h('p', 'some content')
])
}
- 模板编译的作用
- Vue 2.x 使用 VNode 描述视图以及各种交互,用户自己编写 VNode 比较复杂
- 用户只需要编写类似 HTML 的代码 - Vue 模板,通过编译器将模板转换为返回 VNode 的render 函数
- .vue 文件会被 webpack 在构建的过程中转换成 render 函数
体验模板编译的结果
- 带编译器版本的 Vue.js 中,使用 template 或 el 的方式设置模板
<div id="app">
<h1>Vue<span>模板编译过程</span></h1>
<p>{{ msg }}</p>
<comp@myclick="handler"></comp>
</div>
<scriptsrc="../../dist/vue.js"></script><script>
Vue.component('comp', {
template: '<div>I am a comp</div>'
})
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello compiler'
},
methods: {
handler () {
console.log('test')
}
}
})
console.log(vm.$options.render)
</script>
- 编译后 render 输出的结果
(function anonymous() {
with (this) {
return_c(
"div",
{ attrs: { id: "app" } },
[
_m(0),
_v(" "),
_c("p", [_v(_s(msg))]),
_v(" "),
_c("comp", { on: { myclick: handler} }),
],
1
);
}
});
-
_c 是 createElement() 方法,定义的位置 instance/render.js 中
-
相关的渲染函数(_开头的方法定义),在 instance/render-helps/index.js 中
-
把 template 转换成 render 的入口 src\platforms\web\entry-runtime-with-compiler.js
Vue Template Explorer
- vue-template-explorer
- Vue 2.6 把模板编译成 render 函数的工具
- vue-next-template-explorer
- Vue 3.0 beta 把模板编译成 render 函数的工具模板
模板编译过程
- 解析、优化、生成
编译的入口
- src\platforms\web\entry-runtime-with-compiler.js
Vue.prototype.$mount=function (
......
// 把 template 转换成 render 函数
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV!=='production',
shouldDecodeNewlines
,shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments }, this)
options.render=render
options.staticRenderFns=staticRenderFns
......
)
- 调试 compileToFunctions() 执行过程,生成渲染函数的过程
- compileToFunctions: src\compiler\to-function.js
- complie(template, options):src\compiler\create-compiler.js
- baseCompile(template.trim(), finalOptions):src\compiler\index.js
解析 - parse
- 解析器将模板解析为抽象语树 AST,只有将模板解析成 AST 后,才能基于它做优化或者生成代码字符串。
- src\compiler\index.js
const ast = parse(template.trim(), options) //src\compiler\parser\index.js
parse()
- 查看得到的 AST tree
https://astexplorer.net/#/gist/30f2bd28c9bbe0d37c2408e87cabdfcc/1cd0d49beed22d3fc8e2ade0177bb22bbe4b907c - 结构化指令的处理
- v-if 最终生成单元表达式
v-if/v-for 结构化指令只能在编译阶段处理,如果我们要在 render 函数处理条件或循环只能使用js 中的 if 和 for
- v-if 最终生成单元表达式
Vue.component('comp', {
data: () {
return {
msg: 'my comp'
}
},
render (h) {
if (this.msg) {
return h('div', this.msg)
}
return h('div', 'bar')
}
})
优化 - optimize
- 优化抽象语法树,检测子节点中是否是纯静态节点
- 一旦检测到纯静态节点,例如:
hello整体是静态节点
永远不会更改的节点- 提升为常量,重新渲染的时候不在重新创建节点
- 在 patch 的时候直接跳过静态子树
生成 - generate
组件化机制
- 组件化可以让我们方便的把页面拆分成多个可重用的组件
- 组件是独立的,系统内可重用,组件之间可以嵌套
- 有了组件可以像搭积木一样开发网页
- 下面我们将从源码的角度来分析 Vue 组件内部如何工作
- 组件实例的创建过程是从上而下
- 组件实例的挂载过程是从下而上
组件声明
- 复习全局组件的定义方式
Vue.component('comp', {
template: '<h1>hello</h1>'
})
- Vue.component() 入口
- 创建组件的构造函数,挂载到 Vue 实例的 vm.options.component.componentName =Ctor
- 组件构造函数的创建
- 调试 Vue.component() 调用的过程
<div id="app">
</div>
<scriptsrc="../../dist/vue.js"></script>
<script>
const Comp = Vue.component('comp', {
template: '<h2>I am a comp</h2>'
})
const vm = new Vue({
el: '#app',
render (h) {
return h(Comp)
}
})
</script>
组件创建和挂载
组件 VNode 的创建过程
- 创建根组件,首次 _render() 时,会得到整棵树的 VNode 结构
- 整体流程:new Vue() --> $mount() --> vm._render() --> createElement() --> createComponent()
- 创建组件的 VNode,初始化组件的 hook 钩子函数
组件实例的创建和挂载过程
- Vue._update() --> patch() --> createElm() --> createComponent()