- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
vue3的架构
vue3整体分为三个部分:程序运行时模块、响应式模块、程序编译模块,咱们今天就只先看程序运行时模块,看一下vue3在初始化时的一个流程。
\
克隆vue-next代码
git clone https://github.com/vuejs/vue-next.git
安装依赖:(node >= 12.0.0)
yarn --ignore-script
修改package.json生成sourcemap
"dev": "node scripts/dev.js --sourcemap",
启动
yarn dev
createApp
vue3初始化项目不是new Vue了,而是使用createApp().mount()这样的方式,所以要了解初始化流程 必然得先去找到createApp这个函数;
找到packages/runtime-dom/src/index.ts文件:
其中app就是应用程序的实例,是由ensureRenderer()方法创建的,还提供了一个mount方法使用。
接着去看ensureRenderer()方法:
ensureRenderer方法会看这个renderer有没有创建,如果创建直接返回,没有的话就调用createRenderer这个方法去创建renderer。这个renderer就是实例真正的创建者。所以又要去renderer.ts看createRenderer()这个方法了
createRenderer里面又返回了一个baseCreateRenderer这个方法,这个方法是一个工厂函数,总共1800多行代码,里面的东西都是渲染的核心代码,从平台特性 options 取出相关 API,实现了 patch、处理节点、处理组件、更新组件、安装组件实例等等方法,最终返回了一个对象,这里咱们重点关注他return了什么
看到这大概就清除了,其实createAppAPI才是应用程序创建的地方。
这里面有个app就是那个应用程序实例,是个对象里面有些属性。里面有些熟悉的面孔,比如use,mixin,component,directive。这里就体现了跟vue2的区别:
-
- 之前vue2都是静态方法,容易被全局配置给污染,现在实例方法就不会了。
- vue2没用的一些方法也会打包进去,vue3就不会了。
-
- 逻辑上来说创建了一个实例去调用实例里的方法是符合逻辑的。还可以链式调用。
\
现在进到createAppAPI方法里面看看mount方法:
这个render函数的作用其实就是将虚拟dom转变为真实dom。这个render方法是createAppAPI这个方法的参数,所以又要回去renderer.ts找render方法了
这里又调用了一个patch方法,当我们没挂载过时第一个参数会传null,再传我们的vnode和container进去
这个方法判断了我们传进去的类型。那我怎么判断我初始化它会走哪个类型呢。它判断我们传进来的是component。所以就跟着流程走下去,看setupRenderEffect这个方法
这里计算出当前dom树对应的vnode,然后传进patch函数,这个patch函数就是转换虚拟dom的函数。
到此,整个初始化流程就结束了。
简版的初始化代码
首先在vue/examples里面创建一个1.html
<!--
* @Author: xie bin
* @Date: 2021-05-25 17:55:42
* @LastEditTime: 2021-05-25 18:06:56
* @LastEditors: xie bin
* @Description:
* @FilePath: /vue3/vue-next/packages/vue/examples/1.html
-->
<div id="app">
<div>{{text}}</div>
</div>
<script>
Vue.createApp({
data() {
return {
text: 'hello world !'
}
}
}).mount('#app')
</script>
第一步,我们需要个vue,里面有个createApp方法,这个方法根据看的源码其实就是返回renderer这个实例里的createApp方法.
// 1.创建一个vue,里面有一个createApp方法
const Vue = {
createApp(options) {
return renderer || renderer.createApp(options)
}
}
第二步,声明render,这里可以看出不止可以操作dom,它可以干任何事,取决于你传啥进去。其实可以自定义操作的
// 2.声明renderer
const renderer = createRenderer({})
第三步,定义createRenderer函数
// 3.定义createRenderer函数
const createRenderer = options => {
const render = (vnode, container) => {
}
return {
render,
createApp: createAppAPI(render)
}
}
第四步, 定义createAppAPI方法
// 4.定义createAppAPI方法
const createAppAPI = render => {
return function createApp(root) {
}
}
现在整体的一个结构已经完了。
在createApp方法内真正创建app的是renderer。然后renderer是由createRenderer这个方法创建的。它返回了一个render和createApp。这个createApp呢又是createAppApi这个方法创建的。这个方法接受到render之后,创建一个app,定义mount方法。mount将来会调用render函数。将vnode转换为真实dom。
完整代码
<!--
* @Author: xie bin
* @Date: 2021-05-25 17:55:42
* @LastEditTime: 2021-05-26 11:41:45
* @LastEditors: xie bin
* @Description:
* @FilePath: /vue3/vue-next/packages/vue/examples/1.html
-->
<div id="app">
</div>
<script>
// 4.定义createAppAPI方法
const createAppAPI = render => {
return function createApp(root) {
const app = {
mount(container) {
// 实际上vnode应该是跟组件render函数的返回值
const vnode = {
tag: 'h2',
props: null,
children: root.data().text
}
render(vnode, document.querySelector(container));
}
};
return app
}
}
// 3.定义createRenderer函数
const createRenderer = options => {
const render = (vnode, container) => {
// 解析vnode => dom
// 常见vnode对应真实dom
const child = options.createElement(vnode.tag);
if (typeof vnode.children === 'string') {
child.textContent = vnode.children;
} else {
// 递归,省略了多个子元素
}
// 插入节点
options.insert(child, container);
}
return {
render,
createApp: createAppAPI(render)
}
}
// 2.声明renderer
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
insert(child, container) {
container.appendChild(child);
}
})
// 1.创建一个vue,里面有一个createApp方法
const Vue = {
createApp(options) {
return renderer.createApp(options)
}
}
Vue.createApp({
data() {
return {
text: 'hello world !'
}
}
}).mount('#app')
</script>