vue3 组件开发,vue3改进
我们不仅要学习Vue的组件实现过程,还要知道组件数据变更和组件更新的过程。本文主要介绍Vue3的组件更新过程的相关信息,有需要的朋友可以参考一下。
目录
前言副作用渲染功能更新组件核心逻辑:补丁流程1。处理组件2。处理公共元素汇总
前言
组件渲染的过程,本质上就是把各种vnode渲染成真正的DOM。我们也知道构件是由模板、构件描述对象和数据组成的,数据的变化会影响构件的变化。在组件渲染的过程中,会创建一个带有副作用的渲染函数。当数据发生变化时,将执行这个渲染函数来触发组件的更新。本文将具体分析组件的更新过程。
副作用渲染函数更新组件的过程
让我们首先回顾一下带有副作用的渲染函数setupRenderEffect的实现,但这次我们将重点关注更新组件部分的逻辑:
const setupRenderEffect=(实例,初始节点,容器,锚,父节点,isSVG,优化)={
instance . update=effect(function component effect(){
如果(!instance.isMounted) {} else {
让{
接下来,
虚拟节点
}=实例
如果(下一步){
updateComponentPreRender(实例,下一个,优化)
}否则{
next=vnode
}
const next tree=rendercomponentrout(instance)const prev tree=instance . subtree
instance.subTree=nextTree
patch(prevTree,nextTree,hostParentNode(prevTree.el),getNextHostNode(prevTree),instance,patch悬念,isSVG)
next.el=nextTree.el
}
},
prodEffectOptions)
}
如你所见,更新组件主要做三件事:更新组件 vnode 节点、渲染新的子树 vnode、根据新旧子树vnode 执行 patch 逻辑。
首先,更新组件vnode节点。这里会有一个条件判断,判断组件实例中是否有新的组件vnode(用next表示)。如果有,更新组件vnode,没有下一个指向前一个组件vnode。为什么需要判断?这其实涉及到一个组件更新策略的逻辑,我们后面会讲到。
然后,呈现新的子树vnode。因为数据发生了变化,模板与数据相关,所以呈现的子树vnode也会相应变化。
最后是核心的补丁逻辑,用来找出新子树vnode和旧子树VNode的区别,找到合适的方式来更新DOM。接下来,我们将分析这个过程。
核心逻辑:patch流程
我们先来看一下补丁流程的实现代码:
const patch=(n1,n2,container,anchor=null,patchComponent=null,parent悬念=null,isSVG=false,optimized=false)={
如果(n1!isSameVNodeType(n1,n2)) {
anchor=getNextHostNode(n1)unmount(n1,parentComponent,parent悬念,true) n1=null
}
常数{
类型,
形状标志
}=n2
开关(类型){
案例文本:
破裂
案例评论:
破裂
案例静态:
破裂
案例片段:
破裂
默认值:
if (shapeFlag 1) {
processElement(n1,n2,容器,定位点,父组件,父悬念,isSVG,优化)
} else if (shapeFlag 6) {
processComponent(n1,n2,容器,定位点,父组件,父悬念,isSVG,优化)
} else if(shape flag 64){ } else if(shape flag 128){ }
}
}
函数isSameVNodeType(n1,n2) {
返回n1 . type===N2 . type n1 . key===N2 . key
}
在这个过程中,首先判断新旧节点是否是同一种vnode类型。如果它们不同,比如将div更新为ul,那么最简单的操作就是删除旧的div节点,然后挂载新的ul节点。
如果是同一个vnode类型,需要经过diff更新过程,然后会根据不同的vnode类型执行不同的处理逻辑。这里还是只分析常见元素类型和组件类型的处理。
1.处理组件
组件怎么处理?例如,我们在父组件应用程序中引入了Hello组件:
模板
差异
这是一个应用程序。/p
hello :msg=msg/hello
按钮@click=toggle 切换消息/按钮
/div
/模板
脚本
导出默认值{
data () {
return { msg: Vue }
},
方法:{
切换(){
this.msg=this.msg===Vue ?世界: Vue
}
}
}
/脚本
在Hello组件中,一个div包装了一个p标记,如下所示:
模板
差异
pHello,{{ msg }}/p
/div
/模板
脚本
导出默认值{
道具:{ msg: String }
}
/脚本
单击App组件中的按钮执行切换功能,这将修改数据中的消息并触发App组件的重新呈现。
结合渲染函数的进程分析,这里App组件的根节点是div标签,重新渲染的子树的vnode节点是一个公共元素vnode,所以应该先遵循processElement逻辑。组件的更新最终会转化为内部真实DOM的更新,但实际上普通元素的处理流程才是DOM的真实更新。由于后面会详细分析普通元素的处理流程,这里就略过,继续往下看。
与渲染过程类似,更新过程也是树的深度优先遍历过程。更新当前节点后,它将遍历并更新其子节点。所以在遍历过程中会遇到组件vnode节点hello,这个节点会在processComponent的处理逻辑中执行。我们来看看它的实现。我们将关注组件更新的相关逻辑:
const processComponent=(n1,n2,容器,定位点,父组件,父悬念,isSVG,优化)={
if (n1==null) {} else {
updateComponent(n1,n2,parentComponent,优化)
}
}
const updateComponent=(n1,n2,parentComponent,optimized)={
const instance=(n2 . component=n1 . component)if(should updatecomponent(n1,N2,parentComponent,optimized)) {
instance . next=N2 invalidate job(instance . update)instance . update()
}否则{
n2 .组件=n1 .组件n2.el=n1.el
}
}
如您所见,processComponent主要通过执行updateComponent函数来更新其子组件。updateComponent函数在更新其子组件时,会先执行shouldUpdateComponent函数,根据新旧子组件vnode判断是否需要更新子组件。这里需要知道的是,在shouldUpdateComponent函数内部,主要是通过检测和比较props、chidren、dirs、transiton等的属性。在组件vnode中决定子组件是否需要更新。
这个很好理解,因为一个组件的一个子组件是否需要更新,我们主要是判断子组件vnode中是否有一些属性变化会影响组件更新,如果有,就更新子组件。
虽然Vue.js的更新粒度在组件级别,但是组件的数据变化只会影响当前组件的更新。然而,在组件更新的过程中,将对子组件进行某些检查,以确定子组件是否也应该更新,并且将使用某种机制来避免子组件的重复更新。
接下来,我们来看看updateComponent函数。如果shouldUpdateComponent返回true,那么在它的末尾,首先执行invalidateJob(instance.update)以避免子组件因自身数据变化而重复更新,然后执行子组件的副作用渲染函数instance.update以主动触发子组件的更新。
回到副作用渲染函数,有了前面的解释,看看组件更新的这部分代码就能很好的理解它的逻辑了:
让{
接下来,
虚拟节点
}=实例
如果(下一步){
updateComponentPreRender(实例,下一个,优化)
}否则{
next=vnode
}
const updatecomponentprender=(instance,nextVNode,optimized)={
nextVNode.component=instance
const prev props=instance . vnode . props
instance.vnode=nextVNode
instance.next=null
updateProps(instance,nextVNode.props,prevProps,optimized) updateSlots(instance,nextVNode.children)
}
结合上面的代码,在更新组件的DOM之前,我们需要更新组件的vnode节点信息,包括改变组件实例的vnode指针、更新props、更新slots等一系列操作,因为组件在后面执行renderComponentRoot时会重新渲染新的子树vnode,并且依赖于更新后的组件vnode中的props、slots等数据。
所以现在我们知道,当一个组件被重新渲染时,可能有两种情况。一种是组件本身的数据变化,这种情况下next为null;另一种是当父组件在更新过程中遇到子组件节点时,首先判断子组件是否需要更新,如果需要,则主动执行子组件的重渲染方法。在这种情况下,接下来是新的子组件vnode。
您可能还想知道,这个子组件对应的新组件vnode是什么时候创建的?答案很简单。它是在父组件重新渲染过程中renderComponentRoot渲染子树vnode时生成的。因为子树vnode是一个树形结构,所以可以通过遍历其子节点来访问其对应的组件vnode。就拿前面的例子来说吧。当重新渲染App组件时,在执行renderComponentRoot生成子树vnode的过程中,还会生成hello组件对应的新组件vnode。
所以processComponent处理组件vnode,本质上是判断子组件是否需要更新。如果需要,它递归地执行子组件的副作用渲染函数来更新,否则,它只更新一些vnode属性,并让子组件保留对组件vnode的引用,当子组件本身的数据更改导致组件被重新渲染时,它用于在渲染函数内获取新组件vnode。
如前所述,组件是抽象的,组件的更新最终会陷入普通DOM元素的更新。那么我们就来详细分析一下组件更新中常见元素的处理流程。
2.处理普通元素
让我们看看如何处理常见元素。我稍微修改了前面的示例,删除了Hello组件,如下所示:
模板
差异
p这是{{msg}}/p
按钮@click=toggle 切换消息/按钮
/div
/模板
脚本
导出默认值{
data () {
return { msg: Vue }
},
方法:{
切换(){
this.msg===Vue ?世界: Vue
}
}
}
/脚本
当我们单击App组件中的按钮时,将执行切换功能,然后是修改 data 中的 msg,这就触发了 App 组件的重新渲染。
App的根节点是div标签,重新渲染的子树vnode节点是一个常用元素vnode,我们先取processElement逻辑。让我们来看看这个函数的实现:
const processElement=(n1,n2,容器,定位点,父组件,父悬念,isSVG,优化)={
isSVG=isSVG n2.type===svg
if (n1==null) {} else {
patchElement(n1,n2,parentComponent,parent悬念,isSVG,优化)
}
}
const patchElement=(n1,n2,parentComponent,parent悬念,isSVG,优化)={
const El=(N2 . El=n1 . El)const old props=(n1 n1 . props) EMPTY _ OBJ const new props=N2 . props EMPTY _ OBJ
patchProps(el,n2,oldProps,newProps,parentComponent,parent悬念,is SVG)const are childrensvg=is SVG N2 . type!==foreignObject
patchChildren(n1,n2,el,null,parentComponent,parent悬念,areChildrenSVG)
}
可以看到,更新元素的过程主要做两件事:更新道具和更新子节点。实际上,这很容易理解,因为DOM节点元素是由它自己的属性和子节点组成的。
第一步,更新道具。这里的patchProps函数是更新DOM节点的类、样式、事件等DOM属性。
其次,更新子节点。让我们在这里看一下patchChildren函数的实现:
const patchChildren=(n1,n2,container,anchor,parentComponent,parent suspension,isSVG,optimized=false)={
const C1=n1 n1 . children const prev shape flag=n1?n1.shapeFlag: 0常量c2=n2.children常量{
形状标志
}=n2
if (shapeFlag 8) {
if (prevShapeFlag 16) {
unmountChildren(c1,parentComponent,parent悬念)
}
如果(c2!==c1) {
hostSetElementText(容器,c2)
}
}否则{
if (prevShapeFlag 16) {
if (shapeFlag 16) {
patchKeyedChildren(c1,c2,容器,定位点,父组件,父悬念,isSVG,优化)
}否则{
unmountChildren(c1,parentComponent,parent悬念,true)
}
}否则{
if (prevShapeFlag 8) {
hostSetElementText(容器,)
}
if (shapeFlag 16) {
mountChildren(c2,容器,定位点,父组件,父悬念,isSVG,优化)
}
}
}
}
元素的子节点vnode可能有三种情况:纯文本、vnode数组和null。然后根据排列组合,新旧子节点有九种情况,可以用三个图形来表示。
我们先来看看旧的子节点是纯文本的情况:
如果新子节点也是纯文本,则为;只需替换文本。如果新子节点为空,则删除旧的子节点。如果新子节点是一个vnode数组,首先清空旧子节点的文本,然后在旧子节点的父容器下添加多个新子节点。
让我们看一下旧的子节点为空的情况:
如果新的子节点是纯文本,则在旧的子节点的父容器下添加一个新的文本节点。如果新的子节点也是空的,那么不需要做任何事情;如果新的子节点是一个vnode数组,只需转到旧的子节点的父容器并添加更多新的子节点。
最后,让我们看一下旧的子节点是vnode阵列的情况:
如果新的子节点是纯文本,则先删除旧的子节点,然后在旧的子节点的父容器下添加新的文本节点;如果新子节点为空,则删除旧的子节点。如果新的子节点也是vnode数组,那么就需要做一个完整的diff的新旧子节点,这是最复杂的情况,内部使用的是核心的diff算法。
总结
这就是这篇关于Vue3组件更新过程的文章。有关Vue3组件更新过程的更多信息,请搜索我们以前的文章或继续浏览下面的相关文章。希望大家以后能多多支持我们!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。