一. 前言
本文只在为大家梳理完整的vue3
代码执行流程,从创建createApp
到渲染为真实dom
节点。
在读后续文章时,请大家参考以下流程图食用,好了废话不多说,开整!!!
(本文主要为梳理思路,不展示太多源码。)
二. Vue3 思路分析
提示:一定要按照流程图哦!!!
1. createRender(options)
我们创建vue3
时,最常见的一个api
就是createApp
,但是在执行createRender
时,createApp
还没有被创建,那该方法是干什么的呢,传入的参数有是什么呢?我们接着往下看。
options
:源码中也叫做 nodeOps
,其实就是vue3
自己重新封装的一些dom
操作,例如insert、remove、createElement等
,在pc端,其实就是利用domcument
下的一些原生方法实现
优点:那就有人要问了,为什么要重新封装
首先有这么几点好处:
- 1.封装后使用更清楚简短,方便调用
- 2.最重要的一点,那就是将
vue
的渲染工作与dom
操作完全解耦,这样更适合多端操作,只需要重入对应的nodeOps
即可,自己重新封装对应的dom
操作方法,比如移动端等
从流程图来看,调用该方法之后,返回三个Api
,其中我们只关心两个重要的也是常用的createApp、render
我们继续往下看。
2. createApp
这个方法大家应该都不陌生,该方法传入一个根组件rootComponent
,但是并没有做任何处理,在该方法中主要任务创建了一个全局对象app
。
我们来看下app中常见的几个api
:
App
use
: 用来注册插件,为vue实现扩展,如vueRouter、vuex、pinia等component
: 用来注册全局组件,可以在任意组件中使用- 注意:以上方法都应该在mount挂载之前执行
mount
: 这一步也就是最关键的一部,开始执行将组件渲染的第一步,我们继续往下看
3. app.mount(‘#app’)
从流程图中我们可以看到,挂载中主要执行了两个操作
- 1.
createVNode
- 首先将我们传入的根组件传入到
createVNode
方法中,将组件包装成vnode
对象并且返回 - 2.
render(vnode, rootContainer)
- 此处的
render
就是在前边执行createRender
时返回的方法,我们在此处继续执行它,并且传入两个参数,第一个是我们包装后的rootComponent
,第二个是挂载传入真实domrootContainer
好了,mount
执行完毕,我们进入到render
中继续向下执行
4. render(vnode, container)
在此方法中,做了一个最重要的工作就是将已经渲染的vnode
,也可以称之为旧的vnode
保存到container._vnode
上,然后调用
patch(container._vnode|| null, vnode)
这样,就形成了新旧vnode
,也就有了diff
的说法,让我们进入到patch
方法中。
5. patch(n1, n2, container)
patch
中文意思打补丁,也就描述了他的用法,对比新旧vnode
,也就是我们常说的diff
算法,将新的vnode
渲染到真实dom中。
该方法中主要分为了两大分支,当然还有其它,我们就先忽略,跟着主线走下去
- 1.
processComponent
- 当传入的n2为组件时进入这个分支
- 2.
processElement
- 当传入的n2为元素类型时走该分支
我们先进入processComponent
来对传入的rootComponent
进行处理
6. processComponent
该方法中又出现了两个分支
mountComponent
: 在第一次挂载时执行updateComponent
: 组件跟新时执行
我们接着往下看
7. mountComponent
该组件中执行了三个主要的方法,我们一个个来看
createInstance
:首先是创建实例,我们自定义的每个组件第一次渲染时都会走该分支,为每个组件定义了一个组件实例instance
,因此vue3
中就有了一个方法getCurrentInstance
来访问每个组件的实例。setupComponent
:这一步非常重要,该方法接受一个参数,也就是刚刚创建的组件实例,在这一步中,调用了我们传入的setup
,所以,我们调用的所有方法,也就是在这被执行,比如reactive,ref,生命hook,watch,computed等
,之后将setup
调用的结果保存到instance
中的setupState
属性中。setupRenderEffect
:在一切准备工作都做完之后,接下来就该进行渲染,我们继续进入到该方法中。
8. setupRenderEffect
该方法中创建了一个componentUpdateFn
组件更新函数,也就是在这,实现了真正的响应式处理,在setupComponent
中,执行了setup
,在这我们对数据进行了响应式处理,利用reactive/refs
对数据实现了处理,在get
中收集依赖,set
中更新依赖。
在创建好componentUpdateFn
方法之后,我们创建了一个新的对象
new ReactiveEffect(componentUpdateFn)
,之后我们执行返回对象的run
方法,其中执行了传入的componentUpdateFn
函数,在componentUpdateFn
中,我们首先对组件的render
或者template
进行处理,也就对我们用到了响应式数据进行了get
,由此,将ReactiveEffect
收集到对应数据的依赖中,在每次数据修改时,执行componentUpdateFn
,之后,在挂载函数中继续执行patch
,传入处理后的render
或者temeplate
,至此回到patch
中,进行递归。
9. patch
此时,传入的是根标签包装后的vnode
,所以,此次走的分支为processElement
,让我们跟着流程图继续往下看。
10. processElement mountElement
在该方法中,我们利用我们传入的nodeOpts
,也就是dom操作方法,将vnode
渲染为真实的dom,此时,终于将组件渲染到页面上,同时将转换的真实dom存放在vnode.el
上。
patchElement
该方法也就是diff
算法的核心,当组件更新之后,通过diff
算法,将新的vnode
渲染到页面上。
三. 结尾
至此,vue3从创建到渲染的所有流程执行结束,在数据更新之后,我们通过执行依赖,也就是componentUpdateFn
函数,重复之前的步骤,重新执行组件的render函数
获取根标签,传递给patch
,然后更新页面,一直重复该过程。也就实现了页面的改变。直到网页关闭。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。