<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Vue3 相比于 Vue2 有哪些 “與眾不同”

          共 7348字,需瀏覽 15分鐘

           ·

          2021-12-16 05:10

          點(diǎn)擊上方關(guān)注?前端技術(shù)江湖一起學(xué)習(xí),天天進(jìn)步


          前言

          希望本篇文章能幫你加深對 Vue 的理解,能信誓旦旦地說自己熟練Vue2/3。除此之外,也希望路過的朋友可以幫助我查漏補(bǔ)缺??。

          內(nèi)容混雜用法?+?原理?+?使用小心得,建議收藏,慢慢看。

          區(qū)別

          生命周期的變化

          整體來看,變化不大,只是名字大部分需要 +?on,功能上類似。使用上 Vue3 組合式 API 需要先引入;Vue2 選項(xiàng) API 則可直接調(diào)用,如下所示。

          //?vue3
          <script?setup>?????
          import?{?onMounted?}?from?'vue'

          onMounted(()?=>?{
          ??...
          })
          //?可將不同的邏輯拆開成多個onMounted,依然按順序執(zhí)行,不被覆蓋
          onMounted(()?=>?{
          ??...
          })
          script>

          //?vue2
          <script>?????
          ???export?default?{?????????
          ??????mounted()?{?????????????
          ????????...?????????
          ??????},???????????
          ???}
          script>?

          復(fù)制代碼

          常用生命周期表格如下所示。

          Vue2.xVue3
          beforeCreateNot needed*
          createdNot needed*
          beforeMountonBeforeMount
          mountedonMounted
          beforeUpdateonBeforeUpdate
          updatedonUpdated
          beforeDestroyonBeforeUnmount
          destroyedonUnmounted

          Tips:?setup是圍繞beforeCreatecreated生命周期鉤子運(yùn)行的,所以不需要顯式地去定義。

          多根節(jié)點(diǎn)

          Vue3 支持了多根節(jié)點(diǎn)組件,也就是fragment

          Vue2中,編寫頁面的時(shí)候,我們需要去將組件包裹在

          中,否則報(bào)錯警告。

          <template>
          ??<div>
          ????<header>...header>
          ????<main>...main>
          ????<footer>...footer>
          ??div>
          template>
          復(fù)制代碼

          Vue3,我們可以組件包含多個根節(jié)點(diǎn),可以少寫一層,niceeee !

          <template>
          ??<header>...header>
          ??<main>...main>
          ??<footer>...footer>
          template>
          復(fù)制代碼

          異步組件

          Vue3 提供?Suspense組件,允許程序在等待異步組件時(shí)渲染兜底的內(nèi)容,如 loading ,使用戶體驗(yàn)更平滑。使用它,需在模板中聲明,并包括兩個命名插槽:defaultfallbackSuspense確保加載完異步內(nèi)容時(shí)顯示默認(rèn)插槽,并將fallback插槽用作加載狀態(tài)。

          <tempalte>
          ???<suspense>
          ?????<template?#default>
          ???????<todo-list?/>
          ?????template>
          ?????<template?#fallback>
          ???????<div>
          ?????????Loading...
          ???????div>
          ?????template>
          ???suspense>
          template>
          復(fù)制代碼

          真實(shí)的項(xiàng)目中踩過坑,若想在 setup 中調(diào)用異步請求,需在 setup 前加async關(guān)鍵字。這時(shí),會受到警告async setup() is used without a suspense boundary

          解決方案:在父頁面調(diào)用當(dāng)前組件外包裹一層Suspense組件。

          Teleport

          Vue3 提供Teleport組件可將部分DOM移動到 Vue app之外的位置。比如項(xiàng)目中常見的Dialog組件。

          <button?@click="dialogVisible?=?true">點(diǎn)擊button>
          <teleport?to="body">
          ???<div?class="dialog"?v-if="dialogVisible">
          ???div>
          teleport>
          復(fù)制代碼

          組合式API

          Vue2 是?選項(xiàng)式API(Option API),一個邏輯會散亂在文件不同位置(data、props、computed、watch、生命周期函數(shù)等),導(dǎo)致代碼的可讀性變差,需要上下來回跳轉(zhuǎn)文件位置。Vue3 組合式API(Composition API)則很好地解決了這個問題,可將同一邏輯的內(nèi)容寫到一起。

          除了增強(qiáng)了代碼的可讀性、內(nèi)聚性,組合式API 還提供了較為完美的邏輯復(fù)用性方案,舉個??,如下所示公用鼠標(biāo)坐標(biāo)案例。

          //?main.vue
          <template>
          ??<span>mouse?position?{{x}}?{{y}}span>
          template>

          <script?setup>
          import?{?ref?}?from?'vue'
          import?useMousePosition?from?'./useMousePosition'

          const?{x,?y}?=?useMousePosition()

          }
          script>
          復(fù)制代碼
          //?useMousePosition.js
          import?{?ref,?onMounted,?onUnmounted?}?from?'vue'

          function?useMousePosition()?{
          ??let?x?=?ref(0)
          ??let?y?=?ref(0)
          ??
          ??function?update(e)?{
          ????x.value?=?e.pageX
          ????y.value?=?e.pageY
          ??}
          ??
          ??onMounted(()?=>?{
          ????window.addEventListener('mousemove',?update)
          ??})
          ??
          ??onUnmounted(()?=>?{
          ????window.removeEventListener('mousemove',?update)
          ??})
          ??
          ??return?{
          ????x,
          ????y
          ??}
          }
          </script>
          復(fù)制代碼

          解決了 Vue2?Mixin的存在的命名沖突隱患,依賴關(guān)系不明確,不同組件間配置化使用不夠靈活。

          響應(yīng)式原理

          Vue2 響應(yīng)式原理基礎(chǔ)是Object.defineProperty;Vue3 響應(yīng)式原理基礎(chǔ)是?Proxy

          Object.defineProperty

          基本用法:直接在一個對象上定義新的屬性或修改現(xiàn)有的屬性,并返回對象。
          Tips:?writable?和?value?與?getter?和?setter?不共存。

          let?obj?=?{}
          let?name?=?'瑾行'
          Object.defineProperty(obj,?'name',?{
          ??enumerable:?true,?//?可枚舉(是否可通過for...in?或?Object.keys()進(jìn)行訪問)
          ??configurable:?true,?//?可配置(是否可使用delete刪除,是否可再次設(shè)置屬性)
          ??//?value:?'',?//?任意類型的值,默認(rèn)undefined
          ??//?writable:?true,?//?可重寫
          ??get:?function()?{
          ????return?name
          ??},
          ??set:?function(value)?{
          ????name?=?value
          ??}
          })
          復(fù)制代碼

          搬運(yùn) Vue2 核心源碼,略刪減。

          function?defineReactive(obj,?key,?val)?{
          ??//?一?key?一個?dep
          ??const?dep?=?new?Dep()
          ??
          ??//?獲取?key?的屬性描述符,發(fā)現(xiàn)它是不可配置對象的話直接?return
          ??const?property?=?Object.getOwnPropertyDescriptor(obj,?key)
          ??if?(property?&&?property.configurable?===?false)?{?return?}
          ??
          ??//?獲取?getter?和?setter,并獲取?val?值
          ??const?getter?=?property?&&?property.get
          ??const?setter?=?property?&&?property.set
          ??if((!getter?||?setter)?&&?arguments.length?===?2)?{?val?=?obj[key]?}
          ??
          ??//?遞歸處理,保證對象中所有?key?被觀察
          ??let?childOb?=?observe(val)
          ??
          ??Object.defineProperty(obj,?key,?{
          ????enumerable:?true,
          ????configurable:?true,
          ????//?get?劫持?obj[key]?的?進(jìn)行依賴收集
          ????get:?function?reactiveGetter()?{
          ??????const?value?=?getter???getter.call(obj)?:?val
          ??????if(Dep.target)?{
          ????????//?依賴收集
          ????????dep.depend()
          ????????if(childOb)?{
          ??????????//?針對嵌套對象,依賴收集
          ??????????childOb.dep.depend()
          ??????????//?觸發(fā)數(shù)組響應(yīng)式
          ??????????if(Array.isArray(value))?{
          ????????????dependArray(value)
          ??????????}
          ????????}
          ??????}
          ????}
          ????return?value
          ??})
          ??//?set?派發(fā)更新?obj[key]
          ??set:?function?reactiveSetter(newVal)?{
          ????...
          ????if(setter)?{
          ??????setter.call(obj,?newVal)
          ????}?else?{
          ??????val?=?newVal
          ????}
          ????//?新值設(shè)置響應(yīng)式
          ????childOb?=?observe(val)
          ????//?依賴通知更新
          ????dep.notify()
          ??}
          }
          復(fù)制代碼

          那 Vue3 為何會拋棄它呢?那肯定是有一些缺陷的。

          主要原因:無法監(jiān)聽對象或數(shù)組新增、刪除的元素。
          Vue2 方案:針對常用數(shù)組原型方法pushpopshiftunshiftsplicesortreverse進(jìn)行了hack處理;提供Vue.set監(jiān)聽對象/數(shù)組新增屬性。對象的新增/刪除響應(yīng),還可以new個新對象,新增則合并新屬性和舊對象;刪除則將刪除屬性后的對象深拷貝給新對象。

          Tips:?Object.defineOProperty是可以監(jiān)聽數(shù)組已有元素,但 Vue2 沒有提供的原因是性能問題,具體可看見參考第二篇 ~。

          Proxy

          Proxy是ES6新特性,通過第2個參數(shù)handler攔截目標(biāo)對象的行為。相較于Object.defineProperty提供語言全范圍的響應(yīng)能力,消除了局限性。但在兼容性上放棄了(IE11以下)

          局限性

          1. 對象/數(shù)組的新增、刪除。
          2. 監(jiān)測.length修改。
          3. Map、Set、WeakMap、WeakSet的支持。

          基本用法:創(chuàng)建對象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義操作。

          const?handler?=?{
          ??get:?function(obj,?prop)?{
          ????return?prop?in?obj???obj[prop]?:?''
          ??},
          ??set:?function()?{},
          ??...
          }
          復(fù)制代碼

          搬運(yùn) Vue3 的源碼 reactive.ts 文件

          function?createReactiveObject(target,?isReadOnly,?baseHandlers,?collectionHandlers,?proxyMap)?{
          ??...
          ??//?collectionHandlers:?處理Map、Set、WeakMap、WeakSet
          ??//?baseHandlers:?處理數(shù)組、對象
          ??const?proxy?=?new?Proxy(
          ????target,
          ????targetType?===?TargetType.COLLECTION???collectionHandlers?:?baseHandlers
          ??)
          ??proxyMap.set(target,?proxy)
          ??return?proxy
          }
          復(fù)制代碼

          以 baseHandlers.ts 為例,使用Reflect.get而不是target[key]的原因是receiver參數(shù)可以把this指向getter調(diào)用時(shí),而非Proxy構(gòu)造時(shí)的對象。

          //?依賴收集
          function?createGetter(isReadonly?=?false,?shallow?=?false)?{
          ??return?function?get(target:?Target,?key:?string?|?symbol,?receiver:?object)?{
          ????...
          ????//?數(shù)組類型
          ????const?targetIsArray?=?isArray(target)
          ????if?(!isReadonly?&&?targetIsArray?&&?hasOwn(arrayInstrumentations,?key))?{
          ??????return?Reflect.get(arrayInstrumentations,?key,?receiver)
          ????}
          ????//?非數(shù)組類型
          ????const?res?=?Reflect.get(target,?key,?receiver);
          ????
          ????//?對象遞歸調(diào)用
          ????if?(isObject(res))?{
          ??????return?isReadonly???readonly(res)?:?reactive(res)
          ????}

          ????return?res
          ??}
          }
          //?派發(fā)更新
          function?createSetter()?{
          ??return?function?set(target:?Target,?key:?string?|?symbol,?value:?unknown,?receiver:?Object)?{
          ????value?=?toRaw(value)
          ????oldValue?=?target[key]
          ????//?因?ref?數(shù)據(jù)在?set?value?時(shí)就已?trigger?依賴了,所以直接賦值?return?即可
          ????if?(!isArray(target)?&&?isRef(oldValue)?&&?!isRef(value))?{
          ??????oldValue.value?=?value
          ??????return?true
          ????}

          ????//?對象是否有?key?有?key?set,無?key?add
          ????const?hadKey?=?hasOwn(target,?key)
          ????const?result?=?Reflect.set(target,?key,?value,?receiver)
          ????
          ????if?(target?===?toRaw(receiver))?{
          ??????if?(!hadKey)?{
          ????????trigger(target,?TriggerOpTypes.ADD,?key,?value)
          ??????}?else?if?(hasChanged(value,?oldValue))?{
          ????????trigger(target,?TriggerOpTypes.SET,?key,?value,?oldValue)
          ??????}
          ????}
          ????return?result
          ??}
          }
          復(fù)制代碼

          虛擬DOM

          Vue3 相比于 Vue2 虛擬DOM 上增加patchFlag字段。我們借助Vue3 Template Explorer來看。

          <div?id="app">
          ??<h1>技術(shù)摸魚h1>
          ??<p>今天天氣真不錯p>
          ??<div>{{name}}div>
          div>
          復(fù)制代碼

          渲染函數(shù)如下。

          import?{?createElementVNode?as?_createElementVNode,?toDisplayString?as?_toDisplayString,?openBlock?as?_openBlock,?createElementBlock?as?_createElementBlock,?pushScopeId?as?_pushScopeId,?popScopeId?as?_popScopeId?}?from?"vue"

          const?_withScopeId?=?n?=>?(_pushScopeId("scope-id"),n=n(),_popScopeId(),n)
          const?_hoisted_1?=?{?id:?"app"?}
          const?_hoisted_2?=?/*#__PURE__*/?_withScopeId(()?=>?/*#__PURE__*/_createElementVNode("h1",?null,?"技術(shù)摸魚",?-1?/*?HOISTED?*/))
          const?_hoisted_3?=?/*#__PURE__*/?_withScopeId(()?=>?/*#__PURE__*/_createElementVNode("p",?null,?"今天天氣真不錯",?-1?/*?HOISTED?*/))

          export?function?render(_ctx,?_cache,?$props,?$setup,?$data,?$options)?{
          ??return?(_openBlock(),?_createElementBlock("div",?_hoisted_1,?[
          ????_hoisted_2,
          ????_hoisted_3,
          ????_createElementVNode("div",?null,?_toDisplayString(_ctx.name),?1?/*?TEXT?*/)
          ??]))
          }
          復(fù)制代碼

          注意第 3 個_createElementVNode的第 4 個參數(shù)即patchFlag字段類型,字段類型情況如下所示。1 代表節(jié)點(diǎn)為動態(tài)文本節(jié)點(diǎn),那在 diff 過程中,只需比對文本對容,無需關(guān)注 class、style等。除此之外,發(fā)現(xiàn)所有的靜態(tài)節(jié)點(diǎn),都保存為一個變量進(jìn)行靜態(tài)提升,可在重新渲染時(shí)直接引用,無需重新創(chuàng)建。

          export?const?enum?PatchFlags?{?
          ??TEXT?=?1,?//?動態(tài)文本內(nèi)容
          ??CLASS?=?1?<1,?//?動態(tài)類名
          ??STYLE?=?1?<2,?//?動態(tài)樣式
          ??PROPS?=?1?<3,?//?動態(tài)屬性,不包含類名和樣式
          ??FULL_PROPS?=?1?<4,?//?具有動態(tài)?key?屬性,當(dāng)?key?改變,需要進(jìn)行完整的?diff?比較
          ??HYDRATE_EVENTS?=?1?<5,?//?帶有監(jiān)聽事件的節(jié)點(diǎn)
          ??STABLE_FRAGMENT?=?1?<6,?//?不會改變子節(jié)點(diǎn)順序的?fragment
          ??KEYED_FRAGMENT?=?1?<7,?//?帶有?key?屬性的?fragment?或部分子節(jié)點(diǎn)
          ??UNKEYED_FRAGMENT?=?1?<8,??//?子節(jié)點(diǎn)沒有?key?的fragment
          ??NEED_PATCH?=?1?<9,?//?只會進(jìn)行非?props?的比較
          ??DYNAMIC_SLOTS?=?1?<10,?//?動態(tài)的插槽
          ??HOISTED?=?-1,??//?靜態(tài)節(jié)點(diǎn),diff階段忽略其子節(jié)點(diǎn)
          ??BAIL?=?-2?//?代表?diff?應(yīng)該結(jié)束
          }
          復(fù)制代碼

          事件緩存

          Vue3 的?cacheHandler可在第一次渲染后緩存我們的事件。相比于 Vue2 無需每次渲染都傳遞一個新函數(shù)。加一個click事件。

          <div?id="app">
          ??<h1>技術(shù)摸魚h1>
          ??<p>今天天氣真不錯p>
          ??<div>{{name}}div>
          ??<span?onCLick="()?=>?{}"><span>
          div>
          復(fù)制代碼

          渲染函數(shù)如下

          import?{?createElementVNode?as?_createElementVNode,?toDisplayString?as?_toDisplayString,?openBlock?as?_openBlock,?createElementBlock?as?_createElementBlock,?pushScopeId?as?_pushScopeId,?popScopeId?as?_popScopeId?}?from?"vue"

          const?_withScopeId?=?n?=>?(_pushScopeId("scope-id"),n=n(),_popScopeId(),n)
          const?_hoisted_1?=?{?id:?"app"?}
          const?_hoisted_2?=?/*#__PURE__*/?_withScopeId(()?=>?/*#__PURE__*/_createElementVNode("h1",?null,?"技術(shù)摸魚",?-1?/*?HOISTED?*/))
          const?_hoisted_3?=?/*#__PURE__*/?_withScopeId(()?=>?/*#__PURE__*/_createElementVNode("p",?null,?"今天天氣真不錯",?-1?/*?HOISTED?*/))
          const?_hoisted_4?=?/*#__PURE__*/?_withScopeId(()?=>?/*#__PURE__*/_createElementVNode("span",?{?onCLick:?"()?=>?{}"?},?[
          ??/*#__PURE__*/_createElementVNode("span")
          ],?-1?/*?HOISTED?*/))

          export?function?render(_ctx,?_cache,?$props,?$setup,?$data,?$options)?{
          ??return?(_openBlock(),?_createElementBlock("div",?_hoisted_1,?[
          ????_hoisted_2,
          ????_hoisted_3,
          ????_createElementVNode("div",?null,?_toDisplayString(_ctx.name),?1?/*?TEXT?*/),
          ????_hoisted_4
          ??]))
          }
          復(fù)制代碼

          Diff 優(yōu)化

          搬運(yùn) Vue3 patchChildren 源碼。結(jié)合上文與源碼,patchFlag幫助 diff 時(shí)區(qū)分靜態(tài)節(jié)點(diǎn),以及不同類型的動態(tài)節(jié)點(diǎn)。一定程度地減少節(jié)點(diǎn)本身及其屬性的比對。

          function?patchChildren(n1,?n2,?container,?parentAnchor,?parentComponent,?parentSuspense,?isSVG,?optimized)?{
          ??//?獲取新老孩子節(jié)點(diǎn)
          ??const?c1?=?n1?&&?n1.children
          ??const?c2?=?n2.children
          ??const?prevShapeFlag?=?n1???n1.shapeFlag?:?0
          ??const?{?patchFlag,?shapeFlag?}?=?n2
          ??
          ??//?處理?patchFlag?大于?0?
          ??if(patchFlag?>?0)?{
          ????if(patchFlag?&&?PatchFlags.KEYED_FRAGMENT)?{
          ??????//?存在?key
          ??????patchKeyedChildren()
          ??????return
          ????}?els?if(patchFlag?&&?PatchFlags.UNKEYED_FRAGMENT)?{
          ??????//?不存在?key
          ??????patchUnkeyedChildren()
          ??????return
          ????}
          ??}
          ??
          ??//?匹配是文本節(jié)點(diǎn)(靜態(tài)):移除老節(jié)點(diǎn),設(shè)置文本節(jié)點(diǎn)
          ??if(shapeFlag?&&?ShapeFlags.TEXT_CHILDREN)?{
          ????if?(prevShapeFlag?&?ShapeFlags.ARRAY_CHILDREN)?{
          ??????unmountChildren(c1?as?VNode[],?parentComponent,?parentSuspense)
          ????}
          ????if?(c2?!==?c1)?{
          ??????hostSetElementText(container,?c2?as?string)
          ????}
          ??}?else?{
          ????//?匹配新老 Vnode 是數(shù)組,則全量比較;否則移除當(dāng)前所有的節(jié)點(diǎn)
          ????if?(prevShapeFlag?&?ShapeFlags.ARRAY_CHILDREN)?{
          ??????if?(shapeFlag?&?ShapeFlags.ARRAY_CHILDREN)?{
          ????????patchKeyedChildren(c1,?c2,?container,?anchor,?parentComponent,?parentSuspense,...)
          ??????}?else?{
          ????????unmountChildren(c1?as?VNode[],?parentComponent,?parentSuspense,?true)
          ??????}
          ????}?else?{
          ??????
          ??????if(prevShapeFlag?&?ShapeFlags.TEXT_CHILDREN)?{
          ????????hostSetElementText(container,?'')
          ??????}?
          ??????if?(shapeFlag?&?ShapeFlags.ARRAY_CHILDREN)?{
          ????????mountChildren(c2?as?VNodeArrayChildren,?container,anchor,parentComponent,...)
          ??????}
          ????}
          ??}
          }
          復(fù)制代碼

          patchUnkeyedChildren 源碼如下。

          function?patchUnkeyedChildren(c1,?c2,?container,?parentAnchor,?parentComponent,?parentSuspense,?isSVG,?optimized)?{
          ??c1?=?c1?||?EMPTY_ARR
          ??c2?=?c2?||?EMPTY_ARR
          ??const?oldLength?=?c1.length
          ??const?newLength?=?c2.length
          ??const?commonLength?=?Math.min(oldLength,?newLength)
          ??let?i
          ??for(i?=?0;?i?????//?如果新?Vnode?已經(jīng)掛載,則直接?clone?一份,否則新建一個節(jié)點(diǎn)
          ????const?nextChild?=?(c2[i]?=?optimized???cloneIfMounted(c2[i]?as?Vnode))?:?normalizeVnode(c2[i])
          ????patch()
          ??}
          ??if(oldLength?>?newLength)?{
          ????//?移除多余的節(jié)點(diǎn)
          ????unmountedChildren()
          ??}?else?{
          ????//?創(chuàng)建新的節(jié)點(diǎn)
          ????mountChildren()
          ??}
          ??
          }
          復(fù)制代碼

          patchKeyedChildren源碼如下,有運(yùn)用最長遞增序列的算法思想。

          function?patchKeyedChildren(c1,?c2,?container,?parentAnchor,?parentComponent,?parentSuspense,?isSVG,?optimized)?{
          ??let?i?=?0;
          ??const?e1?=?c1.length?-?1
          ??const?e2?=?c2.length?-?1
          ??const?l2?=?c2.length
          ??
          ??//?從頭開始遍歷,若新老節(jié)點(diǎn)是同一節(jié)點(diǎn),執(zhí)行 patch 更新差異;否則,跳出循環(huán)?
          ??while(i?<=?e1?&&?i?<=?e2)?{
          ????const?n1?=?c1[i]
          ????const?n2?=?c2[i]
          ????
          ????if(isSameVnodeType)?{
          ??????patch(n1,?n2,?container,?parentAnchor,?parentComponent,?parentSuspense,?isSvg,?optimized)
          ????}?else?{
          ??????break
          ????}
          ????i++
          ??}
          ??
          ??//?從尾開始遍歷,若新老節(jié)點(diǎn)是同一節(jié)點(diǎn),執(zhí)行 patch 更新差異;否則,跳出循環(huán)?
          ??while(i?<=?e1?&&?i?<=?e2)?{
          ????const?n1?=?c1[e1]
          ????const?n2?=?c2[e2]
          ????if(isSameVnodeType)?{
          ??????patch(n1,?n2,?container,?parentAnchor,?parentComponent,?parentSuspense,?isSvg,?optimized)
          ????}?else?{
          ??????break
          ????}
          ????e1--
          ????e2--
          ??}
          ??
          ??//?僅存在需要新增的節(jié)點(diǎn)
          ??if(i?>?e1)?{????
          ????if(i?<=?e2)?{
          ??????const?nextPos?=?e2?+?1
          ??????const?anchor?=?nextPos???????while(i?<=?e2)?{
          ????????patch(null,?c2[i],?container,?parentAnchor,?parentComponent,?parentSuspense,?isSvg,?optimized)
          ??????}
          ????}
          ??}
          ??
          ??//?僅存在需要刪除的節(jié)點(diǎn)
          ??else?if(i?>?e2)?{
          ????while(i?<=?e1)?{
          ??????unmount(c1[i],?parentComponent,?parentSuspense,?true)????
          ????}
          ??}
          ??
          ??//?新舊節(jié)點(diǎn)均未遍歷完
          ??//?[i?...?e1?+?1]:?a?b?[c?d?e]?f?g
          ??//?[i?...?e2?+?1]:?a?b?[e?d?c?h]?f?g
          ??//?i?=?2,?e1?=?4,?e2?=?5
          ??else?{
          ????const?s1?=?i
          ????const?s2?=?i
          ????//?緩存新?Vnode?剩余節(jié)點(diǎn)?上例即{e:?2,?d:?3,?c:?4,?h:?5}
          ????const?keyToNewIndexMap?=?new?Map()
          ????for?(i?=?s2;?i?<=?e2;?i++)?{
          ??????const?nextChild?=?(c2[i]?=?optimized
          ????????????cloneIfMounted(c2[i]?as?VNode)
          ??????????:?normalizeVNode(c2[i]))
          ??????
          ??????if?(nextChild.key?!=?null)?{
          ????????if?(__DEV__?&&?keyToNewIndexMap.has(nextChild.key))?{
          ??????????warn(
          ????????????`Duplicate?keys?found?during?update:`,
          ?????????????JSON.stringify(nextChild.key),
          ????????????`Make?sure?keys?are?unique.`
          ??????????)
          ????????}
          ????????keyToNewIndexMap.set(nextChild.key,?i)
          ??????}
          ????}
          ??}
          ??
          ??let?j?=?0
          ??//?記錄即將?patch?的?新?Vnode?數(shù)量
          ??let?patched?=?0
          ??//?新?Vnode?剩余節(jié)點(diǎn)長度
          ??const?toBePatched?=?e2?-?s2?+?1
          ??//?是否移動標(biāo)識
          ??let?moved?=?false
          ??let?maxNewindexSoFar?=?0
          ??
          ??//?初始化?新老節(jié)點(diǎn)的對應(yīng)關(guān)系(用于后續(xù)最大遞增序列算法)
          ??const?newIndexToOldIndexMap?=?new?Array(toBePatched)
          ??for?(i?=?0;?i?0
          ??
          ??//?遍歷老?Vnode?剩余節(jié)點(diǎn)
          ??for?(i?=?s1;?i?<=?e1;?i++)?{
          ????const?prevChild?=?c1[i]
          ????
          ????//?代表當(dāng)前新?Vnode?都已patch,剩余舊?Vnode?移除即可
          ????if?(patched?>=?toBePatched)?{
          ??????unmount(prevChild,?parentComponent,?parentSuspense,?true)
          ??????continue
          ????}
          ????
          ????let?newIndex
          ????//?舊?Vnode?存在?key,則從?keyToNewIndexMap?獲取
          ????if?(prevChild.key?!=?null)?{
          ??????newIndex?=?keyToNewIndexMap.get(prevChild.key)
          ????//?舊?Vnode?不存在?key,則遍歷新?Vnode?獲取
          ????}?else?{
          ??????for?(j?=?s2;?j?<=?e2;?j++)?{
          ????????if?(newIndexToOldIndexMap[j?-?s2]?===?0?&&?isSameVNodeType(prevChild,?c2[j]?as?VNode)){
          ???????????newIndex?=?j
          ???????????break
          ????????}
          ??????}???????????
          ???}
          ???
          ???//?刪除、更新節(jié)點(diǎn)
          ???//?新?Vnode?沒有?當(dāng)前節(jié)點(diǎn),移除
          ???if?(newIndex?===?undefined)?{
          ?????unmount(prevChild,?parentComponent,?parentSuspense,?true)
          ???}?else?{
          ?????//?舊?Vnode?的下標(biāo)位置?+?1,存儲到對應(yīng)?新?Vnode?的?Map?中
          ?????//?+?1?處理是為了防止數(shù)組首位下標(biāo)是?0?的情況,因?yàn)檫@里的?0?代表需創(chuàng)建新節(jié)點(diǎn)
          ?????newIndexToOldIndexMap[newIndex?-?s2]?=?i?+?1
          ?????
          ?????//?若不是連續(xù)遞增,則代表需要移動
          ?????if?(newIndex?>=?maxNewIndexSoFar)?{
          ???????maxNewIndexSoFar?=?newIndex
          ?????}?else?{
          ???????moved?=?true
          ?????}
          ?????
          ?????patch(prevChild,c2[newIndex],...)
          ?????patched++
          ???}
          ??}
          ??
          ??//?遍歷結(jié)束,newIndexToOldIndexMap?=?{0:5,?1:4,?2:3,?3:0}
          ??//?新建、移動節(jié)點(diǎn)
          ??const?increasingNewIndexSequence?=?moved
          ??//?獲取最長遞增序列
          ????getSequence(newIndexToOldIndexMap)
          ??:?EMPTY_ARR
          ??
          ??j?=?increasingNewIndexSequence.length?-?1

          ??for?(i?=?toBePatched?-?1;?i?>=?0;?i--)?{
          ????const?nextIndex?=?s2?+?i
          ????const?nextChild?=?c2[nextIndex]?as?VNode
          ????const?anchor?=?extIndex?+?1?1]?as?VNode).el?:?parentAnchor
          ????//?0?新建?Vnode
          ????if?(newIndexToOldIndexMap[i]?===?0)?{
          ??????patch(null,nextChild,...)
          ????}?else?if?(moved)?{
          ??????//?移動節(jié)點(diǎn)
          ??????if?(j?0?||?i?!==?increasingNewIndexSequence[j])?{
          ????????move(nextChild,?container,?anchor,?MoveType.REORDER)
          ??????}?else?{
          ????????j--
          ??????}
          ????}
          ??}
          }
          復(fù)制代碼

          打包優(yōu)化

          tree-shaking:模塊打包webpackrollup等中的概念。移除 JavaScript 上下文中未引用的代碼。主要依賴于importexport語句,用來檢測代碼模塊是否被導(dǎo)出、導(dǎo)入,且被 JavaScript 文件使用。

          nextTick為例子,在 Vue2 中,全局 API 暴露在 Vue 實(shí)例上,即使未使用,也無法通過tree-shaking進(jìn)行消除。

          import?Vue?from?'vue'

          Vue.nextTick(()?=>?{
          ??//?一些和DOM有關(guān)的東西
          })
          復(fù)制代碼

          Vue3 中針對全局 和內(nèi)部的API進(jìn)行了重構(gòu),并考慮到tree-shaking的支持。因此,全局 API 現(xiàn)在只能作為ES模塊構(gòu)建的命名導(dǎo)出進(jìn)行訪問。

          import?{?nextTick?}?from?'vue'

          nextTick(()?=>?{
          ??//?一些和DOM有關(guān)的東西
          })
          復(fù)制代碼

          通過這一更改,只要模塊綁定器支持tree-shaking,則 Vue 應(yīng)用程序中未使用的api將從最終的捆綁包中消除,獲得最佳文件大小。受此更改影響的全局API有如下。

          • Vue.nextTick
          • Vue.observable?(用?Vue.reactive 替換)
          • Vue.version
          • Vue.compile (僅全構(gòu)建)
          • Vue.set (僅兼容構(gòu)建)
          • Vue.delete (僅兼容構(gòu)建)

          內(nèi)部 API 也有諸如 transition、v-model等標(biāo)簽或者指令被命名導(dǎo)出。只有在程序真正使用才會被捆綁打包。

          根據(jù) 尤大 直播可以知道如今 Vue3 將所有運(yùn)行功能打包也只有22.5kb,比 Vue2 輕量很多。

          自定義渲染API

          Vue3 提供的createApp默認(rèn)是將 template 映射成 html。但若想生成canvas時(shí),就需要使用custom renderer api自定義render生成函數(shù)。

          //?自定義runtime-render函數(shù)
          import?{?createApp?}?from?'./runtime-render'
          import?App?from?'./src/App'

          createApp(App).mount('#app')
          復(fù)制代碼

          TypeScript 支持

          Vue3 由TS重寫,相對于 Vue2 有更好地TypeScript支持。

          • Vue2?Option API中 option 是個簡單對象,而TS是一種類型系統(tǒng),面向?qū)ο蟮恼Z法,不是特別匹配。
          • Vue2 需要vue-class-component強(qiáng)化vue原生組件,也需要vue-property-decorator增加更多結(jié)合Vue特性的裝飾器,寫法比較繁瑣。

          周邊

          列舉一些 Vue3 配套產(chǎn)物,具體Composition API新語法可見官方遷移文檔,參考中有鏈接~ 。

          • vue-cli 4.5.0
          • Vue Router 4.0
          • Vuex 4.0
          • Element plus
          • Vite

          參考

          Vue3.0性能優(yōu)化之重寫虛擬Dom[1]
          記一次思否問答的問題思考:Vue為什么不能檢測數(shù)組變動[2]
          Vue3 源碼解析系列 \- 響應(yīng)式原理(reactive 篇)[3]
          Vue 源碼解讀(3)—— 響應(yīng)式原理[4]
          Vue 3 Virtual Dom Diff源碼閱讀[5]
          Vue 2 遷移[6]

          參考資料

          [1]

          https://blog.csdn.net/summer_zhh/article/details/108080930:?https://link.juejin.cn?target=https%3A%2F%2Fblog.csdn.net%2Fsummer_zhh%2Farticle%2Fdetails%2F108080930

          [2]

          https://segmentfault.com/a/1190000015783546:?https://link.juejin.cn?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000015783546

          [3]

          https://zhuanlan.zhihu.com/p/87899787:?https://link.juejin.cn?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F87899787

          [4]

          https://juejin.cn/post/6950826293923414047#heading-12:?https://juejin.cn/post/6950826293923414047#heading-12

          [5]

          https://segmentfault.com/a/1190000038654183:?https://link.juejin.cn?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000038654183

          [6]

          https://v3.vuejs.org/guide/migration/migration-build.html#overview:?https://link.juejin.cn?target=https%3A%2F%2Fv3.vuejs.org%2Fguide%2Fmigration%2Fmigration-build.html%23overview


          關(guān)于本文

          作者:花哨

          https://juejin.cn/post/7011372376969445413

          The End

          歡迎自薦投稿到《前端技術(shù)江湖》,如果你覺得這篇內(nèi)容對你挺有啟發(fā),記得點(diǎn)個?「在看」

          點(diǎn)個『在看』支持下?

          瀏覽 82
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  嘛豆三级片 | 在线伊人91 | 大香蕉第四色 | 水蜜桃视频网站在线观看 | 囯产精品久久久久久久久久乐趣播 |