欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

簡(jiǎn)單聊一聊Vue3組件更新過程

 更新時(shí)間:2022年04月08日 08:14:26   作者:風(fēng)度前端  
我們不光要學(xué)會(huì)Vue的組件化實(shí)現(xiàn)過程,還要懂得組件數(shù)據(jù)發(fā)生變化,更新組件的過程,這篇文章主要給大家介紹了關(guān)于Vue3組件更新過程的相關(guān)資料,需要的朋友可以參考下

前言

組件渲染的過程,本質(zhì)上就是把各種把各種類型的 vnode 渲染成真實(shí) DOM。我們也知道了組件是由模板、組件描述對(duì)象和數(shù)據(jù)構(gòu)成的,數(shù)據(jù)的變化會(huì)影響組件的變化。組件的渲染過程中創(chuàng)建了一個(gè)帶副作用的渲染函數(shù),當(dāng)數(shù)據(jù)變化的時(shí)候就會(huì)執(zhí)行這個(gè)渲染函數(shù)來觸發(fā)組件的更新。本文我們就具體分析一下組件的更新過程。

副作用渲染函數(shù)更新組件的過程

我們先來回顧一下帶副作用渲染函數(shù) setupRenderEffect 的實(shí)現(xiàn),但是這次我們要重點(diǎn)關(guān)注更新組件部分的邏輯:

const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) = >{
  instance.update = effect(function componentEffect() {
    if (!instance.isMounted) {} else {
      let {
        next,
        vnode
      } = instance
      if (next) {
        updateComponentPreRender(instance, next, optimized)
      } else {
        next = vnode
      }
      const nextTree = renderComponentRoot(instance) const prevTree = instance.subTree
      instance.subTree = nextTree 
      patch(prevTree, nextTree, hostParentNode(prevTree.el), getNextHostNode(prevTree), instance, parentSuspense, isSVG) 
      next.el = nextTree.el
      }
  },
prodEffectOptions)
}

可以看到,更新組件主要做三件事情:更新組件 vnode 節(jié)點(diǎn)、渲染新的子樹 vnode、根據(jù)新舊子樹vnode 執(zhí)行 patch 邏輯。

首先是更新組件 vnode 節(jié)點(diǎn),這里會(huì)有一個(gè)條件判斷,判斷組件實(shí)例中是否有新的組件 vnode(用next 表示),有則更新組件 vnode,沒有 next 指向之前的組件 vnode。為什么需要判斷,這其實(shí)涉及一個(gè)組件更新策略的邏輯,我們稍后會(huì)講。

接著是渲染新的子樹 vnode,因?yàn)閿?shù)據(jù)發(fā)生了變化,模板又和數(shù)據(jù)相關(guān),所以渲染生成的子樹 vnode也會(huì)發(fā)生相應(yīng)的變化。

最后就是核心的 patch 邏輯,用來找出新舊子樹 vnode 的不同,并找到一種合適的方式更新 DOM,接下來我們就來分析這個(gè)過程。

核心邏輯:patch流程

我們先來看 patch 流程的實(shí)現(xiàn)代碼:

const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) = >{
  if (n1 && !isSameVNodeType(n1, n2)) {
    anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense, true) n1 = null
  }
  const {
    type,
    shapeFlag
  } = n2
  switch (type) {
  case Text:
    break
  case Comment:
    break
  case Static:
    break
  case Fragment:
    break
  default:
    if (shapeFlag & 1) {
      processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
    } else if (shapeFlag & 6) {
      processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
    } else if (shapeFlag & 64) {} else if (shapeFlag & 128) {}
  }
}
function isSameVNodeType(n1, n2) {
  return n1.type === n2.type && n1.key === n2.key
}

在這個(gè)過程中,首先判斷新舊節(jié)點(diǎn)是否是相同的 vnode 類型,如果不同,比如一個(gè) div 更新成一個(gè)ul,那么最簡(jiǎn)單的操作就是刪除舊的 div 節(jié)點(diǎn),再去掛載新的 ul 節(jié)點(diǎn)。

如果是相同的 vnode 類型,就需要走 diff 更新流程了,接著會(huì)根據(jù)不同的 vnode 類型執(zhí)行不同的處理邏輯,這里我們?nèi)匀恢环治銎胀ㄔ仡愋秃徒M件類型的處理過程。

1.處理組件

如何處理組件的呢?舉個(gè)例子,我們?cè)诟附M件 App 中里引入了 Hello 組件:

<template>
  <div>
    <p>This is an app.</p>
    <hello :msg="msg"></hello>
    <button @click="toggle">Toggle msg</button>
  </div>
</template> 
<script>
export default {
  data () {
    return { msg: 'Vue' }
  },
  methods: {
    toggle () {
      this.msg = this.msg === 'Vue' ? 'World' : 'Vue'
    }
  }
}
</script>

Hello 組件中是 <div>包裹著一個(gè) <p> 標(biāo)簽, 如下所示:

<template>
  <div>
    <p>Hello, {{ msg }}</p>
  </div>
</template> 
<script> 
export default {
  props: { msg: String }
}
</script>

點(diǎn)擊 App 組件中的按鈕執(zhí)行 toggle 函數(shù),就會(huì)修改 data 中的 msg,并且會(huì)觸發(fā) App 組件的重新渲染。

結(jié)合前面對(duì)渲染函數(shù)的流程分析,這里 App 組件的根節(jié)點(diǎn)是 div 標(biāo)簽,重新渲染的子樹 vnode 節(jié)點(diǎn)是一個(gè)普通元素的 vnode,應(yīng)該先走 processElement 邏輯。組件的更新最終還是要轉(zhuǎn)換成內(nèi)部真實(shí)DOM 的更新,而實(shí)際上普通元素的處理流程才是真正做 DOM 的更新,由于稍后我們會(huì)詳細(xì)分析普通元素的處理流程,所以我們先跳過這里,繼續(xù)往下看。

和渲染過程類似,更新過程也是一個(gè)樹的深度優(yōu)先遍歷過程,更新完當(dāng)前節(jié)點(diǎn)后,就會(huì)遍歷更新它的子節(jié)點(diǎn),因此在遍歷的過程中會(huì)遇到 hello 這個(gè)組件 vnode 節(jié)點(diǎn),就會(huì)執(zhí)行到 processComponent 處理邏輯中,我們?cè)賮砜匆幌滤膶?shí)現(xiàn),我們重點(diǎn)關(guān)注一下組件更新的相關(guān)邏輯:

const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = >{
  if (n1 == null) {} else {
    updateComponent(n1, n2, parentComponent, optimized)
  }
}
const updateComponent = (n1, n2, parentComponent, optimized) = >{
  const instance = (n2.component = n1.component) if (shouldUpdateComponent(n1, n2, parentComponent, optimized)) {
    instance.next = n2 invalidateJob(instance.update) instance.update()
  } else {
    n2.component = n1.component n2.el = n1.el
  }
}

可以看到,processComponent 主要通過執(zhí)行 updateComponent 函數(shù)來更新子組件,updateComponent 函數(shù)在更新子組件的時(shí)候,會(huì)先執(zhí)行 shouldUpdateComponent 函數(shù),根據(jù)新舊子組件 vnode 來判斷是否需要更新子組件。這里你只需要知道,在 shouldUpdateComponent 函數(shù)的內(nèi)部,主要是通過檢測(cè)和對(duì)比組件 vnode 中的 props、chidren、dirs、transiton 等屬性,來決定子組件是否需要更新。

這是很好理解的,因?yàn)樵谝粋€(gè)組件的子組件是否需要更新,我們主要依據(jù)子組件 vnode 是否存在一些會(huì)影響組件更新的屬性變化進(jìn)行判斷,如果存在就會(huì)更新子組件。

雖然 Vue.js 的更新粒度是組件級(jí)別的,組件的數(shù)據(jù)變化只會(huì)影響當(dāng)前組件的更新,但是在組件更新的過程中,也會(huì)對(duì)子組件做一定的檢查,判斷子組件是否也要更新,并通過某種機(jī)制避免子組件重復(fù)更新。

我們接著看 updateComponent 函數(shù),如果 shouldUpdateComponent 返回 true ,那么在它的最后,先執(zhí)行 invalidateJob(instance.update)避免子組件由于自身數(shù)據(jù)變化導(dǎo)致的重復(fù)更新,然后又執(zhí)行了子組件的副作用渲染函數(shù) instance.update 來主動(dòng)觸發(fā)子組件的更新。

再回到副作用渲染函數(shù)中,有了前面的講解,我們?cè)倏唇M件更新的這部分代碼,就能很好地理解它的邏輯了:

let {
  next,
  vnode
} = instance
if (next) {
  updateComponentPreRender(instance, next, optimized)
} else {
  next = vnode
}
const updateComponentPreRender = (instance, nextVNode, optimized) = >{
  nextVNode.component = instance 
  const prevProps = instance.vnode.props 
  instance.vnode = nextVNode 
  instance.next = null 
  updateProps(instance, nextVNode.props, prevProps, optimized) 		  	    updateSlots(instance, nextVNode.children)
}

結(jié)合上面的代碼,我們?cè)诟陆M件的 DOM 前,需要先更新組件 vnode 節(jié)點(diǎn)信息,包括更改組件實(shí)例的 vnode 指針、更新 props 和更新插槽等一系列操作,因?yàn)榻M件在稍后執(zhí)行 renderComponentRoot時(shí)會(huì)重新渲染新的子樹 vnode ,它依賴了更新后的組件 vnode 中的 props 和 slots 等數(shù)據(jù)。

所以我們現(xiàn)在知道了一個(gè)組件重新渲染可能會(huì)有兩種場(chǎng)景,一種是組件本身的數(shù)據(jù)變化,這種情況下next 是 null;另一種是父組件在更新的過程中,遇到子組件節(jié)點(diǎn),先判斷子組件是否需要更新,如果需要?jiǎng)t主動(dòng)執(zhí)行子組件的重新渲染方法,這種情況下 next 就是新的子組件 vnode。

你可能還會(huì)有疑問,這個(gè)子組件對(duì)應(yīng)的新的組件 vnode 是什么時(shí)候創(chuàng)建的呢?答案很簡(jiǎn)單,它是在父組件重新渲染的過程中,通過 renderComponentRoot 渲染子樹 vnode 的時(shí)候生成,因?yàn)樽訕?vnode是個(gè)樹形結(jié)構(gòu),通過遍歷它的子節(jié)點(diǎn)就可以訪問到其對(duì)應(yīng)的組件 vnode。再拿我們前面舉的例子說,當(dāng)App 組件重新渲染的時(shí)候,在執(zhí)行 renderComponentRoot 生成子樹 vnode 的過程中,也生成了hello 組件對(duì)應(yīng)的新的組件 vnode。

所以 processComponent 處理組件 vnode,本質(zhì)上就是去判斷子組件是否需要更新,如果需要?jiǎng)t遞歸執(zhí)行子組件的副作用渲染函數(shù)來更新,否則僅僅更新一些 vnode 的屬性,并讓子組件實(shí)例保留對(duì)組件vnode 的引用,用于子組件自身數(shù)據(jù)變化引起組件重新渲染的時(shí)候,在渲染函數(shù)內(nèi)部可以拿到新的組件vnode。

前面也說過,組件是抽象的,組件的更新最終還是會(huì)落到對(duì)普通 DOM 元素的更新。所以接下來我們?cè)敿?xì)分析一下組件更新中對(duì)普通元素的處理流程。

2.處理普通元素

我們?cè)賮砜慈绾翁幚砥胀ㄔ?,我把之前的示例稍加修改,將其中?Hello 組件刪掉,如下所示:

<template>
  <div>
    <p>This is {{msg}}</p>
    <button @click="toggle">Toggle msg</button>
  </div>
</template> 
<script>
export default {
  data () {
    return { msg: 'Vue' }
  },
  methods: {
    toggle () {
      this.msg === 'Vue' ? 'World' : 'Vue'
    }
  }
}

</script>

當(dāng)我們點(diǎn)擊 App 組件中的按鈕會(huì)執(zhí)行 toggle 函數(shù),然后修改 data 中的 msg,這就觸發(fā)了 App 組件的重新渲染。

App 組件的根節(jié)點(diǎn)是 div 標(biāo)簽,重新渲染的子樹 vnode 節(jié)點(diǎn)是一個(gè)普通元素的 vnode,所以應(yīng)該先走processElement 邏輯,我們來看這個(gè)函數(shù)的實(shí)現(xiàn):

const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = >{
  isSVG = isSVG || n2.type === 'svg'
  if (n1 == null) {} else {
    patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
  }
}
const patchElement = (n1, n2, parentComponent, parentSuspense, isSVG, optimized) = >{
  const el = (n2.el = n1.el) const oldProps = (n1 && n1.props) || EMPTY_OBJ 	 	const newProps = n2.props || EMPTY_OBJ 
  patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG) const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
  patchChildren(n1, n2, el, null, parentComponent, parentSuspense, areChildrenSVG)
}

可以看到,更新元素的過程主要做兩件事情:更新 props 和更新子節(jié)點(diǎn)。其實(shí)這是很好理解的,因?yàn)橐粋€(gè) DOM 節(jié)點(diǎn)元素就是由它自身的一些屬性和子節(jié)點(diǎn)構(gòu)成的。

首先是更新 props,這里的 patchProps 函數(shù)就是在更新 DOM 節(jié)點(diǎn)的 class、style、event 以及其它的一些 DOM 屬性。

其次是更新子節(jié)點(diǎn),我們來看一下這里的 patchChildren 函數(shù)的實(shí)現(xiàn):

const patchChildren = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized = false) = >{
  const c1 = n1 && n1.children const prevShapeFlag = n1 ? n1.shapeFlag: 0 const c2 = n2.children const {
    shapeFlag
  } = n2
  if (shapeFlag & 8) {
    if (prevShapeFlag & 16) {
      unmountChildren(c1, parentComponent, parentSuspense)
    }
    if (c2 !== c1) {
      hostSetElementText(container, c2)
    }
  } else {
    if (prevShapeFlag & 16) {
      if (shapeFlag & 16) {
        patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
      } else {
        unmountChildren(c1, parentComponent, parentSuspense, true)
      }
    } else {
      if (prevShapeFlag & 8) {
        hostSetElementText(container, '')
      }
      if (shapeFlag & 16) {
        mountChildren(c2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)
      }
    }
  }
}

對(duì)于一個(gè)元素的子節(jié)點(diǎn) vnode 可能會(huì)有三種情況:純文本、vnode 數(shù)組和空。那么根據(jù)排列組合對(duì)于新舊子節(jié)點(diǎn)來說就有九種情況,我們可以通過三張圖來表示。

首先來看一下舊子節(jié)點(diǎn)是純文本的情況:

  • 如果新子節(jié)點(diǎn)也是純文本,那么做簡(jiǎn)單地文本替換即可;
  • 如果新子節(jié)點(diǎn)是空,那么刪除舊子節(jié)點(diǎn)即可;
  • 如果新子節(jié)點(diǎn)是 vnode 數(shù)組,那么先把舊子節(jié)點(diǎn)的文本清空,再去舊子節(jié)點(diǎn)的父容器下添加多個(gè)新子節(jié)點(diǎn)。

接下來看一下舊子節(jié)點(diǎn)是空的情況:

  • 如果新子節(jié)點(diǎn)是純文本,那么在舊子節(jié)點(diǎn)的父容器下添加新文本節(jié)點(diǎn)即可;
  • 如果新子節(jié)點(diǎn)也是空,那么什么都不需要做;
  • 如果新子節(jié)點(diǎn)是 vnode 數(shù)組,那么直接去舊子節(jié)點(diǎn)的父容器下添加多個(gè)新子節(jié)點(diǎn)即可。

最后來看一下舊子節(jié)點(diǎn)是 vnode 數(shù)組的情況:

  • 如果新子節(jié)點(diǎn)是純文本,那么先刪除舊子節(jié)點(diǎn),再去舊子節(jié)點(diǎn)的父容器下添加新文本節(jié)點(diǎn);
  • 如果新子節(jié)點(diǎn)是空,那么刪除舊子節(jié)點(diǎn)即可;
  • 如果新子節(jié)點(diǎn)也是 vnode 數(shù)組,那么就需要做完整的 diff 新舊子節(jié)點(diǎn)了,這是最復(fù)雜的情況,內(nèi)部運(yùn)用了核心 diff 算法。

總結(jié)

到此這篇關(guān)于Vue3組件更新過程的文章就介紹到這了,更多相關(guān)Vue3組件更新過程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue computed無法得到this的屬性或方法的解決

    vue computed無法得到this的屬性或方法的解決

    這篇文章主要介紹了vue computed無法得到this的屬性或方法的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • 詳解Vue2 SSR 緩存 Api 數(shù)據(jù)

    詳解Vue2 SSR 緩存 Api 數(shù)據(jù)

    本篇文章主要介紹了Vue2 SSR 緩存 Api 數(shù)據(jù),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-11-11
  • 通過命令行生成vue項(xiàng)目框架的方法

    通過命令行生成vue項(xiàng)目框架的方法

    本篇文章主要介紹了通過命令行生成vue項(xiàng)目框架的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-07-07
  • Iview Table組件中各種組件擴(kuò)展的使用

    Iview Table組件中各種組件擴(kuò)展的使用

    這篇文章主要介紹了Iview Table組件中各種組件擴(kuò)展的使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-10-10
  • 詳解Vue中的Props與Data細(xì)微差別

    詳解Vue中的Props與Data細(xì)微差別

    這篇文章主要介紹了詳解Vue中的Props與Data細(xì)微差別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • 使用Vue.set()方法實(shí)現(xiàn)響應(yīng)式修改數(shù)組數(shù)據(jù)步驟

    使用Vue.set()方法實(shí)現(xiàn)響應(yīng)式修改數(shù)組數(shù)據(jù)步驟

    今天小編就為大家分享一篇使用Vue.set()方法實(shí)現(xiàn)響應(yīng)式修改數(shù)組數(shù)據(jù)步驟,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2019-11-11
  • vue指令?v-bind的使用和注意需要注意的點(diǎn)

    vue指令?v-bind的使用和注意需要注意的點(diǎn)

    這篇文章主要給大家分享了?v-bind的使用和注意需要注意的點(diǎn),下面文章圍繞?v-bind指令的相關(guān)資料展開內(nèi)容且附上詳細(xì)代碼?需要的小伙伴可以參考一下,希望對(duì)大家有所幫助
    2021-11-11
  • vue3?證件識(shí)別上傳組件封裝功能

    vue3?證件識(shí)別上傳組件封裝功能

    證件圖片識(shí)別上傳根據(jù)業(yè)務(wù)需要,經(jīng)常涉及到證件上傳,例如身份證上傳、銀行卡、營業(yè)執(zhí)照等信息,根據(jù)設(shè)計(jì)師的設(shè)計(jì),單獨(dú)封裝了一個(gè)上傳組件,這篇文章主要介紹了vue3?證件識(shí)別上傳組件封裝,需要的朋友可以參考下
    2023-05-05
  • vue2.0使用md-edit編輯器的過程

    vue2.0使用md-edit編輯器的過程

    這篇文章主要介紹了vue2.0+使用md-edit編輯器的解決方案,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-02-02
  • 深入淺析vue-cli@3.0 使用及配置說明

    深入淺析vue-cli@3.0 使用及配置說明

    這篇文章主要介紹了vue-cli@3.0 使用及配置說明,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-05-05

最新評(píng)論