在开始看vue3源码之前,我最大的一个困惑就是,vue到底是怎么把组件变成dom的?各个模块之间是如何相互关联的?
如果你也有这样的困惑,那这篇文章将会告诉你答案。
<div id="app">{{msg}}</div>
<script>
const { createApp } = Vue
const APP = {
data() {
return {
msg: 1
}
}
}
const app = createApp(APP)
app.mount('#app')
</script>
我们以这样一段代码为例。
今天来聊聊vue的createApp方法是怎么把vue组件变成真实dom的。
我们自己可以从需求出发,先分析一下。
createApp要做哪些事?
首先createApp是一个函数,接受一个对象参数。
其次createApp返回了一个对象,对象中有个mount方法。
根据这两点,我们可以很简单的写一个如下的createApp函数。
function createApp(app) {
return {
mount(el) {
const dom = document.querySelector(el)
dom.innerText = app.data().msg
}
}
}
但是这里有几个问题,一个是数据改变了,dom需要重新渲染。另一个是赋值操作,我们是写死的,这个应该根据语法判断。
所以我们至少要加上3个步骤。【伪代码】
function createApp(app) {
return {
mount(el) {
const dom = document.querySelector(el)
// 1.监听数据变化
const $proxy = new Proxy(app.data(), {
get: () => {},
set: () => {}
})
// 2.当数据变化时,更新dom
updateComponent()
// 3.语法分析,将值绑定到dom
dom.innerText = app.data().msg
}
}
}
理解到这里,我们再去看看vue是如何实现的。
vue是如何渲染dom的?
1.调用createApp方法,创建实例
跟我们自己写的demo类似,vue也是先创建一个实例,返回app对象。
只不过vue返回的app对象,上面定义了很多方法和属性。
2.调用mount
将id=app的div元素作为根节点,{{msg}}作为tempalte,创建虚拟节点,然后调用render函数渲染。
从这里可以看出,vue渲染dom是将dom先转化为虚拟dom,然后通过render函数渲染。
3.调用render函数【虚拟dom暂时先不看】
render方法,就是runtime的一个核心了。
第一次调用,直接走patch方法。
在patch中会根据虚拟节点的类型,做不同的处理。
此处,我们是组件,所以会调动processComponent处理组件。
因为是第一次执行,所以会走mountComponent方法。
4.调用mountComponent
在这个方法中做了几件事情,是比较重要的。
- 4.1.创建组件实例 组件的数据,方法都会挂载到实例上。
- 4.2.处理组件的数据,包括数据代理,props处理等
在挂载组件的过程中,会判断组件是否有render函数,如果没有,则调用compiler去解析template,并返回一个render函数。
compiler模块就是在这里与runtime模块做交互的。
- 4.3.添加监听器,当有数据改变时,重新渲染dom
组件挂载前,会创建一个监听器,当组件内部有数据变化时,会触发更新函数,重新渲染组件。更新的时候,就涉及到dom diff算法了,这个后面我们再说。
vue核心模块是如何工作的?
梳理了一遍源码逻辑,我们对vue的各个模块应该有了初步的了解。
从vue源码的目录结构,我们可以很清楚地看到,vue有几个核心模块。
从名字中,我们也能大概看出来,vue核心逻辑可以分为3大块。(SSR服务渲染,sfc单文件渲染暂时不管,只看核心逻辑)
根据vue渲染dom的逻辑,我们可以得出如下的渲染步骤。
- 1.创建虚拟dom
- 2.调用render函数挂载组件
- 3.挂载过程需要处理组件内的数据,并创建监听器
- 3.1当有数据变化时,通知监听器,调用更新组件方法
- 3.2比对新旧组件
- 4.渲染真实dom