vue3 组件开发,vue3改进

  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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: