先以两个简单的vue3代码片段作为引子,分别是main.js文件和app.vue文件。
// main.js文件
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App);
app.mount('#app')
以上代码作为vue3项目的入口文件,const app = createApp(App)和app.mount('#app')两行代码底层原理是什么?
// app.vue
<template>
<p>{{ reactiveObj.count }}</p>
<button @click="changeCount">修改数据</button>
</template>
<script setup>
import { reactive } from "vue";
// 定义响应式对象
const reactiveObj = reactive({
count: 0,
});
// 定义修改数据的方法
const changeCount = function () {
reactiveObj.count += 1;
};
</script>
以上代码是一个响应式的简单案例,首次渲染和点击按钮后的二次渲染底层实现流程是什么?
使用vue3的过程中,会有很多经典而又好用的功能,比如组件渲染、响应式、计算属性、侦听器、自定义指令、插槽等等。
每一个功能也许我们都能运用得炉火纯青。
甚至某个知识点的原理我们也能娓娓道来。
那么,我们可否能将所有的功能的底层逻辑绘制成一个体系树?
下面就是我所绘制的vue3底层原理脉络图。
接下来先简单介绍图中的大题逻辑。
1、框架入口
例子中可以看出,通过import { createApp } from 'vue'的方式从vue中引入了方法createApp,并将App作为参数传入其中,最后,通过app.mount('#app')的方式将app挂载到#app上去。
图中可以看出app是通过const app = ensureRenderer().createApp(...args)获得,并且在缓存mount后又对其进行了重写,最后返回app。
在调用app.mount(#app)时,实际是先标准化处理容器container,再去执行被缓存的mount。
详情可查看vue3组件渲染:首次渲染前半部分。
2、组件渲染
图中从mount分支开始就是组件渲染的开始,首先我们需要通过createVNode (rootComponent, rootProps)创建虚拟dom-vnode。
然后,执行渲染逻辑render(vnode, rootContainer, namespace) --> patch根据type和shapeFlag执行函数 --> processComponent --> mountComponent,到这里需要创建组件实例instance,执行setupComponent(instance)和setupRenderEffect。
首次渲染相关的可查看vue3组件渲染:首次渲染。
二次渲染相关的可查看vue3数据更新:diff算法。
3、响应式。
响应式就是在执行setupComponent(instance)分支逻辑时,setup中reactive或ref对应的响应式数据中都有get和set函数。
在渲染逻辑时,会执行到render函数,这里会访问到get函数,执行当前视图更新effect的依赖收集的逻辑。
当数据改变时,又会触发set函数,进而触发数据更新后的视图重新渲染,即为数据的响应式。
有三种主流的响应式响应式数据、计算属性和侦听器。
响应式体系建立详情可查看vue3响应式原理:ref、computed、watch和render的关系
响应式数据详情可查看vue3响应式原理:reactive。
计算属性详情可查看vue3响应式原理:可被收集/也可被收集的computed。
侦听器详情可查看vue3响应式原理:被监听的watch。
4、编译原理
渲染过程中的render又是哪里产生的?
如果使用了脚手架,脚手架已经帮我们进行了处理。
如果想在运行时,再去进行render的生成也可以,在setupComponent(instance)执行时,会执行到Component.render = compile的逻辑,这里又分为ast的生成、ast的转换和code的生成。
详情可通过编译入口进行断点调试。
这是一个大概的轮廓图,所有的知识点理论上都可以填充到体系图中的某个分支中。
本文如有用,请点赞+关注+收藏,后续会持续更新重要的底层原理分析。
如果对vue2源码比较感兴趣,可以点击 vue2核心面试题汇总【查缺补漏】。