在上一篇中,咱们晓得render
函数最终是通过baseCreaterenderer
创立的。
当通过createApp
API 创立的组件实例调用mount
办法挂载组件的时候,其实mount
办法也是通过调用render
办法。
实现组件的渲染工作。
这篇文章次要剖析是对baseCreateRender
函数源码进行剖析。
baseCreateRenderer
函数的整体代码大略有一千多行。
蕴含的信息相当丰盛。
纵向扩大,能够学习到Vnode
的patch
过程、虚构DOM
的diff
形式、指令的调用形式。
深度扩大,能够学完template
的解析、转换与生成,任务调度器的执行过程、甚至响应式零碎。
还有就是写完,能够补上后面好多留的坑😂。
这次咱们先纵向学习,理解该函数在Vue
中次要做了什么。前面在逐渐深刻。
前文回顾
上篇文章中,咱们晓得app实例是通过createApp
API创立的,createApp
将createRenderer
函数返回的对象中的app
属性做了一些解决之后。再返回给用户。
而createRenderer
其实调用的是baseCreateRenderer
函数,并给baseCreateRenderer
函数传递了一个用于配置渲染器的options
对象。
这个options
对象中蕴含了DOM
的解决办法 & 属性的patch
办法。
而baseCreateRenderer
函数返回的对象中,蕴含render
渲染函数、hydrate
用于服务端渲染的注水函数、createApp
函数。
Vue3
顺带的将render
办法设定为API
,不便高阶玩家自由发挥。
当咱们调用app
实例上的mount
办法时。
会依据挂载的组件创立对应的Vnode
。
将Vnode
、挂载元素el
传给render
函数。
最终通过render
函数实现组件的渲染工作。
解构配置项
为了不便外部patch
函数的应用,baseCreateRenderer
函数首先对options
进行了解构.
options
次要蕴含的办法是对DOM
的创立、插入、挪动、设置、获取父节点、克隆节点、patch
属性等办法。
这里咱们须要先简略相熟下:
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
forcePatchProp: hostForcePatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
cloneNode: hostCloneNode,
insertStaticContent: hostInsertStaticContent
渲染逻辑
在组件生命周期中,首次挂载会触发mounted
钩子。
后续如果状态产生变换,会触发beforeUpdate
、updated
钩子。
这其实与渲染函数render
无关。
render
函数首先会判断Vnode
是否存在。
如果不存在阐明须要执行进行卸载,执行unmount
操作。
如果存在须要进行patch
操作。
patch
的过程就蕴含了组件了创立到挂载,变动到更新。
const render = (vnode, container, isSVG) => {
if (vnode == null) {
// 如果没有Vnode,则卸载原来的Vnode
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
// 存在则对新旧Vnode进行patch
// patch是一个递归的过程
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
// patch完结后,开始冲刷任务调度器中的工作
flushPostFlushCbs()
// 更新vnode
container._vnode = vnode
}
从代码来看,render
函数的逻辑并不简单。
render
函数的设计思维,根本就代表了vue
解决各种类型节点的形式;
- 首先会判断
Vnode
是否存在,如果不存在,则调用unmount
函数,进行组件的卸载 - 否则调用
patch
函数,对组件进行patch
patch
完结后,会调用flushPostFlushCbs
函数冲刷工作池- 最初更新容器上的
Vnode
patch Vnode
Vnode
有不同的类型,在这里我将其分为:
- 简略类型:文本、正文、
Static
。 - 简单类型:组件、
Fragment
、Component
、Teleport
、Suspense
。
patch
思路,能够看作一个深度优先遍历。与深度克隆的逻辑十分类似。
简略类型就相当于JS中的原始数据类型:字符串、数字、布尔。
简单类型就相当于JS中的援用类型:对象、数组、Map、Set。
不同的节点类型,须要采取不同的patch
形式。
而patch
函数的主要职责就是去判断Vnode
的节点类型,而后调用对应类型的Vnode
解决形式,进行更粗疏的patch
。
上面咱们看下patch
函数是如何解决的。
为了升高patch函数的了解难度,上面的流程图体现的是patch处理过程中的次要逻辑,并没有将所有细节记录在图中。
Text类型
- 匹配到
Text
类型Vnode
。 - 会调用
ProcessText
函数对节点进行解决。 ProcessText
函数首先会判断n1
是否存在。- 不存在,阐明是第一次执行,间接进行文本插入。
- 新旧,新旧文本不同,会设置新的
Text
。
Comment类型
- 匹配到
Comment
类型Vnode
- 调用
processCommentNode
函数 - 如果
n1
不存在,则执行插入工作 - 否则间接新的笼罩旧的,因为正文节点并不需要在页面中进行展现,不用做多余的渲染工作
Static类型
- 咱们晓得
Vue3
的性能晋升,有局部起因就是得益于对动态节点的解决。 patch
过程中,匹配到Static
类型节点。- 如果
n1
不存在,会调用mountStaticNode
,对动态节点进行挂载操作。 - 如果是
dev
环境,会调用patchStaticNode
函数,patch
节点。 - 为什么仅在
dev
环境中进行patch
呢,因为dev
环境下波及到HMR
。 - 另外动态节点不存在对
state
的依赖,不会触发track
、trigger
。且放弃不变,在生产环境下,不用进行patch
。以升高性能开销。
Fragment类型
- 匹配到
Fragment
类型节点。 - 会调用
processFragment
函数,进行解决。 Fragment
节点,Fragment
是Vue3
中新增的Fragment
组件,能够包裹多个子节点,然而并不会渲染Fragment
节点。- 所以在渲染过程中次要解决的是
Fragmemt
包裹的子节点。 -
如果
n1
不存在,会执行mountChildren
,对子节点进行挂载。mountChildren
会对子节点进行遍历操作,递归调用patch
函数。
-
如果n1存在,会对子节点再进行进一步的判断
- 如果
patchFlag
存在 && 存在动静节点 - 则会调用
patchBlockChildren
,对子节点进行patch
, patchBlockChildren
会遍历子节点,递归调用patch
函数- 否则会调用
patchChildren
函数,对子节点进行patch
patchChildren
在执行的过程中波及到了DOM
的diff
过程,这里临时不开展剖析,前面会出独自进行剖析
- 如果
Element类型
- 匹配到
Element
类型 - 会调用
processElement
函数 -
n1
不存在,会执行mountElement
函数,对Vnode
进行挂载mountElement
在挂载Vnode
过程中,会通过mountChildren
,对子节点进行递归挂载解决。- 并会对
Vnode
的prop
进行patch
。 - 并调用
queuePostRenderEffect
函数,向任务调度池中的后置执行阶段push
生命周期钩子mounted
。
-
否则会执行
patchElement
函数,对element
进行patch
,patchElement
函数次要会执行以下工作:- 调用
hostPatchProp
对节点的class
、style
进行patch
-
遍历
props
对节点的新旧props
进行patch
- 调用
patchBlockChildren
或者patchChildren
进行patch
操作 - 并调用
queuePostRenderEffect
函数,向任务调度池中的后置执行阶段push
生命周期钩子updated
。 - 这里须要对子节点解决的起因是因为
Element
的子节点中,也可能还有组件或者其余类型的节点
- 调用
- 调用
Component类型
- 通常状况下,咱们都会给
createApp
传递一个组件 - 故当
render
函数执行patch
时,首先会匹配到组件类型的节点 - 如果是组件类型,会调用
processComponent
函数进行解决 - 首先会判断
n1
是否存在 -
如果存在会进一步判断
- 该组件是否是被
Keep-Alive
包裹的组件 - 如果是,则会执行组件的
activate
钩子 - 否则会调用
mountComponent
函数,对组件进行挂载 -
mountComponent
函数波及的层级较深,这里先不开展说,然而要晓得以下几点:- 会实现组件实例的创立
- 实现
Props
、Slots
的初始化 - 执行
setup
函数,获取响应式状态 - 实现组件模板的解析、编译与转换
- 调用
setupRenderEffect
创立一个渲染级别的effect
- 用于负责组件的更新,这里我临时将其称为
updateEffect
。
- 该组件是否是被
-
否则会执行
updateComponent
函数,判断组件是否须要进行更细- 次要会对组件的新旧
Props
、子节点进行判断 - 如果发生变化,会调用
mountComponent
阶段创立的updateEffect
,触发响应式零碎 - 否则间接原有的间接进行笼罩
- 次要会对组件的新旧
Teleport类型 & Suspense类型
Teleport
与Suspense
是Vue3
新增的两个内置组件- 如果匹配到以上两种,会调用组件实例上的
process
办法 porcess
办法的次要逻辑与后面的雷同- 首先会判断原有
Vnode
是否存在,不存在则mount
,存在则patch
- 这两种类型的具体解决形式,咱们会在剖析这两个组件的源码的时候会进行剖析
卸载组件
- 如果调用
render
函数时没有传Vnode
,则会调用unmount
函数对组件进行卸载 - 卸载过程中,如果存在
ref
,会首先重置ref
- 如果组件是通过
Keep-Alive
缓存的组件,会通过deactivate
对组件进行卸载 -
如果是组件类型
Vnode
,会通过unmountComponent
函数对组件进行卸载- 在卸载组件过程中会执行
beforeMount
生命周期钩子 - 通过
stop
API来卸载组件的所有相干effect
- 如果存在
updateEffect
,会卸载updateEffect
,并递归调用unmount
函数,对组件进行卸载 - 最初会执行
unmount
生命周期钩子 - 并通过
queuePostRenderEffect
向任务调度器中的后置工作池中,push
一个用于标记组件已实现卸载的函数 - 至此,就实现了组件的卸载工作
- 在卸载组件过程中会执行
-
如果不是组件类型的
Vnode
,会有以下几种状况:- 如果是
Suspense
类型,会通过Suspense
实例上的unmount
办法实现Vnode
的卸载工作 - 如果是
Teleport
类型,会通过Teleport
实例上的remove
办法实现Vnode
的卸载工作 - 如果存在子组件,会通过
unmountChildren
实现子组件的卸载工作 - 最初会调用
remove
函数实现Fragment
、Static
、Element
类型的卸载工作
- 如果是
从下面整个过程能够看出,卸载组件过程根本与patch
形似,也是对各种类型的Vnode
有不同的解决办法,并会通过递归调用unmount
实现组件的卸载工作,卸载过程中,会卸载组件相干的effect
、updateEffect
,触发卸载相干的生命周期钩子 & 指令相干的钩子。
总结
通过下面的梳理剖析,能够晓得,对于所有类型的组件,patch
过程十分类似。
首先会判断原有的vnode
是否存在。
如果不存在,则会进行mount
操作。
如果存在则会对新旧Vnode
进行patch操作。
不同的是对于简单类型的Vnode
,因为其外部可能蕴含有其余类型的Vnode
,比方Component
类型。其中会波及到:
- 组件实例的创立
- 模板的编译工作
- 子组件的递归
patch
工作等等
在unmount
过程中,同样的会对不同的组件类型进行解决,并卸载组件的所有相干effect
,递归卸载子组件。
不过没有提及的是下面的两个过程中,都会向任务调度器中push
工作。
在render函数执行的最初阶段,会通过flushPostFlushCbs
冲刷任务调度器,对于任务调度器是如何运作的,能够移步这里👉任务调度器源码剖析
其实写到这里还有两个问题没有说:
第一个问题:还有哪些没有说?
baseCreateRenderer蕴含的内容切实是太多了,要想一篇就剖析完并输入,对于读者和我都是一种考验。所以本文只是纵向的对baseCreateRenderer进行了剖析,并没有深究细节。
比拟重要的有几点:
- 子节点的diff过程
- 组件类型编译过程、响应式转换过程
- updateEffect做了什么
- 如何进行指令的生命周期钩子调用
- 生命周期的执行过程
- 往任务调度器中都push了哪些工作
- 如何设计的性能监控零碎
等等细节之处都是咱们还没有说的。不过前面咱们会持续。
第二个问题:baseCreateRenderer这么简单,我或者你费了这么大劲读了有什么用?
上面我说说我的感触:
- 在工作中,很大概率下没啥用。因为Vue通过createRenderer曾经做了很全面的配置,创立的render函数,曾经能满足工作需要。
- 如果是高阶玩家,比如说我想用createRenderer做个Vnode渲染引擎,可能有帮忙。只须要通过配置Options。就能够创立一个定制化的渲染器。并且这个渲染器曾经蕴含了Vnode的diff零碎、编译系统。
最初非常感谢各位的浏览,如果文章有疏漏之处,还望批评指正。
如果有所播种,能够帮我点个关注,我会继续更新Vue的相干学习分享😁!
发表回复