Vue渲染原理讲解
本文的主要内容是详细地介绍Vue的内部渲染原理,从而帮助大家深入掌握关于Vue Options、生命周期等概念。为了帮助Vue使用经验较少的同学快速理解Vue,我们先从Vue的简介开始,第二部分再详细介绍Vue渲染原理。
Vue官方网站。
为了简单,我们先以一个cdn版本的Vue为例:
<script type="text/javascript" src="https://unpkg.com/vue"></script>
="app"></div>
<script>
let app = new Vue({
el: '#app',
data: { title: '标题' },
template: '{{title}}
',
methods: {
changeTitle (title) { this.title = title; }
}
});
setTimeout(function () {
app.changeTitle('新标题');
}, 1000);
</script>
执行完script脚本对应的框架代码后,window上会新增一个构造函数Vue,用于构建Vue实例。我们向new Vue传入了一个配置对象,这个对象包含如el、data、template、methods等属性,用于为Vue实例添加属性和方法。Vue会根据这些配置,生成一个可以自动生成视图的响应式的Vue组件,它不仅负责管理视图层和业务层,还负责两者的同步。
我们来简单看一下一些常用配置的作用:
- el
根元素,该参数只能由根节点声明,表示当前Vue应用需要被挂载到页面的哪个DOM节点上。如上面的例子指定了根元素为#app,那么该Vue实例生成的DOM就会直接替换id为app的元素。
- name
组件的名字,主要用于全局注册组件,如:
import MyComponent from 'MyComponent';
Vue.component(MyComponent.name, MyComponent);
- components
声明当前组件的外部依赖,相当于局部注册组件,在编写单组件时,如果需要用到其他的项目内组件通常会提供该参数。
- props
来自父级组件的数据依赖,这个依赖是响应式的。
- data
业务数据,这个参数是model层的核心,相关的业务逻辑都是围绕data展开的。
- computed
计算属性,定义一组变量,这组变量的值是基于一个或多个props、data计算而来,computed内变量的值会根据这些依赖的值变化而自动更新,并且会自动缓存上次的计算结果。
- watch
手动监控props、data或者computed的变化,定义变化时的回调函数。
- 生命周期方法
定义Vue组件在各个生命周期需要执行的回调函数,Vue在执行到对应的阶段时会调用它们。生命周期与Vue组件创建的细节是第二部分渲染原理的重点。
- methods
组件的工具方法集。methods定义了一组工具方法,可以在computed、watch、生命周期方法或者其他工具方法中调用。
有了这些基本知识的铺垫,下面我们就开始详细介绍Vue的渲染过程。
Vue源码笔记之响应式系统。
Vue源码笔记之虚拟DOM,里面有详细的patch过程和图解。
也就是说,调用updateComponent时,如果组件尚未渲染,则依据vnode渲染组件(该过程主要就是用document.createElement创建真实DOM标签,然后用appendChild添加到页面上);如果组件已经存在,则比对vnode,产生高效更新算法,用原生的DOM方法去操作真实DOM,完整视图更新。
显然,定义这个函数是为了在数据变化时自动调用以更新视图,也就是说它必须接入到响应式系统才有意义。接下来的代码就是将其接入响应式系统:
function mountCompnent () {
...
// 上述函数
const updateComponent = () => {
vm._update(vm._render());
}
// 将updateComponent接入响应式系统
new Watcher(vm, updateComponent, noop, {
before () {
callhook('beforeUpdate');
}
})
}
还记得watcher的作用吗,它是数据对象的订阅者,负责在数据变化时执行某些操作。上面的代码为当前组件实例构造了一个watcher,初始化watcher的过程中会触发data属性的get方法,因此这个watcher就会被Dep收集起来,传入的回调函数正是它的updateComponent方法。当数据变化时,Observer会通知Dep,Dep依次调用订阅者watcher的run方法,run里面会执行上述回调函数(即updateComponent),于是视图得到更新。这样就实现了修改数据之后自动更新视图。再次看一下此时data的结构:
this._data = {
message: ‘’,
__ob__: {
dep: {
subs: []
}
},
get message (): {} // 调用get时,依赖会被收集
set message (): { // 内部包含对该属性的观察者对象
// 这里包含组件内对message的订阅者(watcher)
}
}
注意,_data内的__ob__.dep.subs保存的并不是当前组件内的watcher,而是外部watcher。如果当前模板中这样绑定了data内的message:{{message}},那么组件对应的watcher就会以闭包的形式保存在set message () { ... }内。当message值变化时,set方法就会调用,它会继而触发依赖收集者dep的notify方法通知订阅者,该方法会依次调用subs中每个watcher的run方法,依次调用它们提供的回调函数。对于用于更新组件的watcher来说,这个回调函数就是它的updateComponent方法。
而每当updateComponent被调用前,Vue都会调用callHook('beforeUpdate'),执行该生命周期钩子函数,因为视图即将被更新。当然,当updateComponent执行完毕后,Vue又会调用callHook('updated'),执行更新完毕的生命周期钩子函数。
最后是组件的销毁过程。当手动调用this.$destroy(),或由于v-if属性等原因导致组件必须被销毁时,Vue主要执行了以下过程:

当触发$destroy方法时,首先是调用beforeDestroy生命周期钩子函数。接着主要是清除组件的依赖关系,以及销毁watcher等。此时组件已经失去了响应能力,相当于它的状态被销毁了,因此Vue会调用destroyed生命周期钩子函数。最后注销组件的事件监听,清除一些附属参数,组件彻底被销毁(对于Vue组件来说,一旦状态被销毁,它就被认为是销毁了,所以destroyed是在事件被销毁前调用的)。
最后附赠本文的示例代码和完整的console输出供大家学习:
<!DOCTYPE html>
>
>
<script src="vue-2.6.10-learning.js"></script>
</head>
>
="app"></div>
<script>
var app = new Vue({
el: '#app',
template: "标题
{{ message }}
",
data () { return { message: 1 } },
mounted () {
console.log(this.$data);
setTimeout(() => { this.message = 2; }, 1000);
setTimeout(() => { this.$destroy(); }, 5000);
}
})
</script>
</body>
</html>

总结
本文主要讲解了Vue组件的完整渲染过程,如果能结合源码看本文,效果会更好。通过本文,我希望能够帮助读者对Vue的渲染过程有一个全局的了解,从而能够更深入地思考实际项目中出现的一些问题。
相关