共计 5873 个字符,预计需要花费 15 分钟才能阅读完成。
在上一篇中,咱们晓得 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 的相干学习分享😁!