亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

vue3中虛擬dom的介紹與使用詳解

 更新時間:2024年01月14日 08:36:50   作者:CSU_XZY  
Vue?是如何將一份模板轉換為真實的?DOM?節(jié)點的,又是如何高效地更新這些節(jié)點的呢,這些都離不開虛擬dom這個概念,下面我們就來了解下虛擬dom這個概念以及它是什么吧

Vue 是如何將一份模板轉換為真實的 DOM 節(jié)點的,又是如何高效地更新這些節(jié)點的呢?這些都離不開虛擬dom這個概念,接下來我們就先了解下虛擬dom這個概念以及它是什么。

什么是虛擬dom

在使用前端框架的時候,我們經常會聽到虛擬dom這個概念。那么到底什么是虛擬dom呢?

虛擬 DOM (Virtual DOM,簡稱 VDOM) 是一種編程概念,意為將目標所需的 UI 通過數據結構“虛擬”地表示出來,保存在內存中,然后將真實的 DOM 與之保持同步。這個概念是由 React 率先開拓,隨后被許多不同的框架采用,當然也包括 Vue。簡單來說,虛擬 DOM 就是一個用 JavaScript 對象表示真實 DOM 的概念。

vue3中,虛擬dom,也就是代碼里面的vnode,他就是一個對象,定義位于 packages/runtime-core/src/vnode.ts 文件中。VNode 類的結構如下,具體字段是怎么使用的我們后續(xù)章節(jié)一個個講解。

export interface VNode<
  HostNode = RendererNode,
  HostElement = RendererElement,
  ExtraProps = { [key: string]: any }
> {
  __v_isVNode: true
  [ReactiveFlags.SKIP]: true
  type: VNodeTypes
  props: (VNodeProps & ExtraProps) | null
  key: string | number | symbol | null
  ref: VNodeNormalizedRef | null
  scopeId: string | null
  slotScopeIds: string[] | null
  children: VNodeNormalizedChildren
  component: ComponentInternalInstance | null
  dirs: DirectiveBinding[] | null
  transition: TransitionHooks<HostElement> | null
  // DOM
  el: HostNode | null
  anchor: HostNode | null // fragment anchor
  target: HostElement | null // teleport target
  targetAnchor: HostNode | null // teleport target anchor
  staticCount: number
  suspense: SuspenseBoundary | null
  ssContent: VNode | null
  ssFallback: VNode | null
  shapeFlag: number
  patchFlag: number
  dynamicProps: string[] | null
  dynamicChildren: VNode[] | null
  appContext: AppContext | null
  ctx: ComponentInternalInstance | null
  memo?: any[]
  isCompatRoot?: true
  ce?: (instance: ComponentInternalInstance) => void
}

與其說虛擬 DOM 是一種具體的技術,不如說是一種模式,所以并沒有一個標準的實現。我們可以用一個簡單的例子來說明:

const vnode = {
  type: 'div',
  props: {
    id: 'hello'
  },
  children: [
    /* 更多 vnode */
  ]
}

這里所說的 vnode 即一個純 JavaScript 的對象 (一個“虛擬節(jié)點”),它代表著一個 <div> 元素。它包含我們創(chuàng)建實際元素所需的所有信息。它還包含更多的子節(jié)點,這使它成為虛擬 DOM 樹的根節(jié)點。

一個運行時渲染器將會遍歷整個虛擬 DOM 樹,并據此構建真實的 DOM 樹。這個過程被稱為掛載 (mount)。

如果我們有兩份虛擬 DOM 樹,渲染器將會有比較地遍歷它們,找出它們之間的區(qū)別,并應用這其中的變化到真實的 DOM 上。這個過程被稱為更新 (patch),又被稱為“比對”(diffing) 或“協調”(reconciliation)。

虛擬 DOM 帶來的主要收益是它讓開發(fā)者能夠靈活、聲明式地創(chuàng)建、檢查和組合所需 UI 的結構,同時只需把具體的 DOM 操作留給渲染器去處理。

為什么使用虛擬dom

在前端開發(fā)中,頻繁地操作 DOM 是非常耗時的,因為每次操作都會引發(fā)瀏覽器的重排和重繪。為了解決這個問題,虛擬 DOM 應運而生。通過在內存中模擬 DOM,我們可以批量地計算出 DOM 的變化,從而減少對真實 DOM 的操作次數。這樣可以提高應用的性能,特別是在大型應用中。

例如當一次操作中有100次更新DOM的動作時,虛擬DOM不會立即操作DOM,而是和原本的DOM進行對比,將這100次更新的變化部分內容保存到內存中,最終一次性地應用在DOM樹上,再進行后續(xù)操作,避免大量無謂的計算量。

虛擬DOM實際上就是采用JavaScript對象來存儲DOM節(jié)點的信息,將DOM的更新變成對象的修改,并且這些修改計算在內存中發(fā)生,當修改完成后,再將JavaScript轉換成真實的DOM節(jié)點,交給瀏覽器,從而達到性能的提升。

虛擬dom的好處

使用虛擬 DOM 的好處主要有以下幾點:

  • 性能優(yōu)化:通過減少直接操作真實 DOM 的次數,可以降低瀏覽器的重繪和回流成本,從而提高頁面性能。
  • 跨平臺:虛擬 DOM 不依賴于瀏覽器環(huán)境,可以方便地在不同平臺(如服務器端渲染、移動端應用等)使用。
  • 易于測試:因為虛擬 DOM 是 JavaScript 對象,我們可以直接對其進行操作和斷言,而不需要依賴瀏覽器環(huán)境。

同時vue3的設計也是這樣的,我們可以看到源碼的組織結構將各個目錄拆開來,runtime-core的實現就是與平臺無關的。在瀏覽器環(huán)境下使用runtime-domdom操作,在其他平臺使用各個平臺的dom操作,實現了跨平臺。

vue中vnode是如何運行的

從高層面的視角看,Vue 組件掛載時會發(fā)生如下幾件事:

  • 編譯:Vue 模板被編譯為渲染函數:即用來返回虛擬 DOM 樹的函數。這一步驟可以通過構建步驟提前完成,也可以通過使用運行時編譯器即時完成。
  • 掛載:運行時渲染器調用渲染函數,遍歷返回的虛擬 DOM 樹,并基于它創(chuàng)建實際的 DOM 節(jié)點。這一步會作為響應式副作用執(zhí)行,因此它會追蹤其中所用到的所有響應式依賴。
  • 更新:當一個依賴發(fā)生變化后,副作用會重新運行,這時候會創(chuàng)建一個更新后的虛擬 DOM 樹。運行時渲染器遍歷這棵新樹,將它與舊樹進行比較,然后將必要的更新應用到真實 DOM 上去。

可以看到我們一切操作都是針對于虛擬DOM的,只有在最后提交的時候才會將虛擬DOM掛載到瀏覽器上面

怎么創(chuàng)建虛擬dom

vue3中,我們在template模版中編寫的代碼最終都會通過compiler模塊進行編譯,編譯之后會返回一個render字符串,后面在應用運行的時候會調用這個render生成render函數。在函數里面會調用不同的方法進行虛擬dom的創(chuàng)建。

例如下面的模版:

可以去template-explorer.vuejs.org/ 網站體驗模版編譯結果

<template>
  <hello-world :msg="msg" :info="info"></hello-world>
  <div>
    <button @click="addAge">Add age</button>
    <button @click="toggleMsg">Toggle Msg</button>
  </div>
</template>

經過編譯之后會生成如下函數:

import { resolveComponent as _resolveComponent, createVNode as _createVNode, createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
?
const _hoisted_1 = ["onClick"]
const _hoisted_2 = ["onClick"]
?
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  const _component_hello_world = _resolveComponent("hello-world")
?
  return (_openBlock(), _createElementBlock(_Fragment, null, [
    _createVNode(_component_hello_world, {
      msg: _ctx.msg,
      info: _ctx.info
    }, null, 8 /* PROPS */, ["msg", "info"]),
    _createElementVNode("div", null, [
      _createElementVNode("button", { onClick: _ctx.addAge }, "Add age", 8 /* PROPS */, _hoisted_1),
      _createElementVNode("button", { onClick: _ctx.toggleMsg }, "Toggle Msg", 8 /* PROPS */, _hoisted_2)
    ])
  ], 64 /* STABLE_FRAGMENT */))
}

現在我們不用理解這個代碼的所有內容,我們只用關注里面創(chuàng)建節(jié)點的代碼,例如createVNode,createElementVNode這些就是用來創(chuàng)建虛擬dom的。調用完之后會生成虛擬dom樹。由于樹結構過大, 我們這里不做展示,整個結構和我們上面定義的虛擬dom結構一致。

接下來我們可以看下創(chuàng)建虛擬dom的具體實現,我們只挑常用的創(chuàng)建虛擬dom的方法。所有和虛擬dom有關的內容都位于源碼packages/runtime-core/src/vnode.ts

createVNode

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, // 如果是組件,type就是組件內容。如果是普通類型就是一個字符串,比方說div, HelloWorld之類的
  props: (Data & VNodeProps) | null = null,
  children: unknown = null, //子組件
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
  if (!type || type === NULL_DYNAMIC_COMPONENT) {
    type = Comment
  }
?
  if (isVNode(type)) { // 是Vnode的處理,例如用戶通過h函數編寫的代碼
    const cloned = cloneVNode(type, props, true /* mergeRef: true */)
    if (children) {
      normalizeChildren(cloned, children) // 標準化孩子
    }
    if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {
      if (cloned.shapeFlag & ShapeFlags.COMPONENT) {
        currentBlock[currentBlock.indexOf(type)] = cloned
      } else {
        currentBlock.push(cloned)
      }
    }
    cloned.patchFlag |= PatchFlags.BAIL
    return cloned
  }
?
  // 標準化用class寫的組件
  if (isClassComponent(type)) { 
    type = type.__vccOpts
  }
?
  // 標準化異步組件和函數式組件
  if (__COMPAT__) { 
    type = convertLegacyComponent(type, currentRenderingInstance)
  }
?
  // 標準化class props style
  //對于代理過的對象,我們需要克隆來使用他們
  //因為直接修改會導致觸發(fā)響應式
  if (props) {
    props = guardReactiveProps(props)! // 防止props是響應式的
    let { class: klass, style } = props
    if (klass && !isString(klass)) {
      props.class = normalizeClass(klass) // 標準話class
    }
    if (isObject(style)) { // 標準化style
      if (isProxy(style) && !isArray(style)) {
        style = extend({}, style)
      }
      props.style = normalizeStyle(style)
    }
  }
?
  // encode the vnode type information into a bitmap
  const shapeFlag = isString(type) // 根據type不同生成不同的shapeFlag,方便后面對不同的類型做處理
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
    ? ShapeFlags.SUSPENSE
    : isTeleport(type)
    ? ShapeFlags.TELEPORT
    : isObject(type)
    ? ShapeFlags.STATEFUL_COMPONENT // 有狀態(tài)組件
    : isFunction(type)
    ? ShapeFlags.FUNCTIONAL_COMPONENT // 函數組件
    : 0
?
  return createBaseVNode(
    type,
    props,
    children,
    patchFlag, // 更新類型
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  )
}

createVNode主要是對傳遞的type做出判斷,通過賦值shapeFlag來標明當前的虛擬節(jié)點的類型。

如果props含有style或者class要進行標準化。

例如<div :style="['background:red',{color:'red'}]"></div>其中第一個是cssText形式、第二個是對象形式,他們應該被轉化為對象類型所以轉化后應該為<div style={color:'red',background:'red'}></div>。當然對于class也需要標準化:class={hello:true,world:false} => :class="hello"。但是這里處理的其實是用戶自己寫了render函數,而對于使用了Vue自帶的編譯系統(tǒng)之后,是不需要做這一層處理的。

最后會調用createBaseVNode進行虛擬dom的創(chuàng)建

function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag = 0,
  dynamicProps: string[] | null = null, // 動態(tài)屬性
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false
) {
  const vnode = { // 創(chuàng)建一個vnode
    __v_isVNode: true, // 是不是vnode
    __v_skip: true,
    type, // type對于組件來說就是組件內容
    props,
    key: props && normalizeKey(props), // 標準化key
    ref: props && normalizeRef(props), // 標準化 props
    scopeId: currentScopeId,
    slotScopeIds: null,
    children, // 孩子的vnode
    component: null, // 如果是一個組件的話就會有組件的實例,組件實例的subtree上面掛載了這個組件下面所有的vnode
    suspense: null,
    ssContent: null,
    ssFallback: null,
    dirs: null, // 指令相關
    transition: null, // transition相關
    el: null, // 真實dom
    anchor: null, // 插入的位置
    target: null,
    targetAnchor: null,
    staticCount: 0,
    shapeFlag, // 組件類型
    patchFlag, // 靶向更新標記
    dynamicProps, // 動態(tài)props
    dynamicChildren: null, // 動態(tài)孩子
    appContext: null, // 應用根context
    ctx: currentRenderingInstance
  } as VNode
?
  if (needFullChildrenNormalization) {
    normalizeChildren(vnode, children) // 標準化子組件。例如插槽啥的。添加children并且更改vnode的shapFlag
    if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
      ;(type as typeof SuspenseImpl).normalize(vnode) // 標準化異步組件
    }
  } else if (children) {
    vnode.shapeFlag |= isString(children) // 判斷ShapeFlags,可以是多個類型的組合
      ? ShapeFlags.TEXT_CHILDREN
      : ShapeFlags.ARRAY_CHILDREN
  }
?
  // 編譯優(yōu)化相關?,F在不必了解
  if (
    isBlockTreeEnabled > 0 &&
    // avoid a block node from tracking itself
    !isBlockNode &&
    // has current parent block
    currentBlock &&
    // presence of a patch flag indicates this node needs patching on updates. // 補丁標志的存在表明此節(jié)點需要在更新時進行補丁。
    // component nodes also should always be patched, because even if the // 組件節(jié)點應該是中有patchFlag
    // component doesn't need to update, it needs to persist the instance on to // 組件不需要更新,它需要將實例持久化到
    // the next vnode so that it can be properly unmounted later. 下一個vnode,以便以后可以正確地卸載它。
    (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) && // 有patchFlag說明是動態(tài)節(jié)點
    vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
  ) {
    currentBlock.push(vnode) // 放入到父親的區(qū)塊中去,說明現在的是一個動態(tài)節(jié)點
  }
?
  if (__COMPAT__) {
    convertLegacyVModelProps(vnode)
    defineLegacyVNodeProperties(vnode)
  }
?
  return vnode
}

可以看到我們創(chuàng)建了一個VNode類型的對象,也就是一個虛擬dom。同時對key、ref、chidren(needFullChildrenNormalization為true)進行標準化。

至此我們就知道vue3中虛擬dom究竟是怎么產生的啦。

createElementVNode

用于創(chuàng)建普通tag的虛擬節(jié)點如<div></div>

export { createBaseVNode as createElementVNode }

可以看到createElementVNode其實就是createBaseVNode,用來創(chuàng)建虛擬dom。

項目中使用

上面我們提到,Vue 模板會被預編譯成虛擬 DOM 渲染函數。Vue 也提供了 API(h函數) 使我們可以不使用模板編譯,直接手寫渲染函數。在處理高度動態(tài)的邏輯時,渲染函數相比于模板更加靈活,因為你可以完全地使用 JavaScript 來構造你想要的 vnode。

但是官方推薦的是使用模版而不是渲染函數。那么為什么 Vue 默認推薦使用模板呢?有以下幾點原因:

  • 模板更貼近實際的 HTML。這使得我們能夠更方便地重用一些已有的 HTML 代碼片段,能夠帶來更好的可訪問性體驗、能更方便地使用 CSS 應用樣式,并且更容易使設計師理解和修改。
  • 由于其確定的語法,更容易對模板做靜態(tài)分析。這使得 Vue 的模板編譯器能夠應用許多編譯時優(yōu)化來提升虛擬 DOM 的性能表現(例如靜態(tài)提升,靶向更新等等)

在絕大多數情況下,Vue 推薦使用模板語法來創(chuàng)建應用。然而在某些使用場景下,我們真的需要用到 JavaScript 完全的編程能力。這時渲染函數就派上用場了。Vue 提供了一個 h() 函數用于創(chuàng)建 vnodes。下面我們來看看h函數的用法:

import { h } from 'vue'
?
const vnode = h(
  'div', // type
  { id: 'foo', class: 'bar' }, // props
  [
    /* children */
  ]
)

h()hyperscript 的簡稱——意思是“能生成 HTML (超文本標記語言) 的 JavaScript”。這個名字來源于許多虛擬 DOM 實現默認形成的約定。一個更準確的名稱應該是 createVnode(),但當你需要多次使用渲染函數時,一個簡短的名字會更省力。

我們可以在組件中使用h函數來執(zhí)行渲染而使用模版

import { h } from 'vue'

export default {
  setup() { // 需要確保返回的是一個函數而不是一個值,這個函數最后會被用作渲染函數render,這個render在后面會被重復調用的
    // 使用數組返回多個根節(jié)點
    return () => [
      h('div'),
      h('div'),
      h('div')
    ]
  }
}

以上代碼等價于

<template>
	<div></div>
  <div></div>
  <div></div>
</template>

export default {
  setup() {}
}

然后我們可以看下h函數的源碼:

export function h(type: any, propsOrChildren?: any, children?: any): VNode {
  const l = arguments.length
  if (l === 2) { // 只有兩個參數,要么是只有孩子,要么是只有props
    if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
      if (isVNode(propsOrChildren)) { // 如果第二個參數是一個vnode說明是子節(jié)點
        return createVNode(type, null, [propsOrChildren])
      }
      return createVNode(type, propsOrChildren) // 沒有孩子節(jié)點的情況
    } else {
      // 如果propsOrChildren是數組那么一定是孩子節(jié)點
      return createVNode(type, null, propsOrChildren)
    }
  } else { // prop和孩子節(jié)點都有的情況
    if (l > 3) { // 第三個參數之后都是孩子
      children = Array.prototype.slice.call(arguments, 2)
    } else if (l === 3 && isVNode(children)) {
      children = [children]
    }
    return createVNode(type, propsOrChildren, children)
  }
}

可以看到h函數有三個參數,第一個參數是類型,第二個是props參數或者孩子節(jié)點(如果有第三個參數就是props,沒有第三個參數就要判斷下),三個參數是孩子節(jié)點。除了類型必填以外,其他的參數都是可選的。h函數只是對參數做了一下判斷,然后底層還是調用的createVNode進行虛擬DOM的創(chuàng)建。

具體判斷邏輯代碼中都已經注釋,大家可以結合著看。

總結

現在相信大家已經了解了虛擬dom這個東東,后續(xù)我們所有的操作都是通過操作虛擬dom來實現的,最后掛載的時候通過原生的dom操作來將虛擬dom掛載到頁面上。

以上就是vue3中虛擬dom的介紹與使用詳解的詳細內容,更多關于vue3虛擬dom的資料請關注腳本之家其它相關文章!

相關文章

  • Vue數據雙向綁定底層實現原理

    Vue數據雙向綁定底層實現原理

    這篇文章主要為大家詳細介紹了Vue數據雙向綁定底層實現原理,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • vue項目是如何運行起來的

    vue項目是如何運行起來的

    這篇文章主要介紹了vue項目是如何運行起來的,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • vue里如何主動銷毀keep-alive緩存的組件

    vue里如何主動銷毀keep-alive緩存的組件

    這篇文章主要介紹了vue里如何主動銷毀keep-alive緩存的組件,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-03-03
  • electron實現打印功能支持靜默打印、無感打印

    electron實現打印功能支持靜默打印、無感打印

    使用electron開發(fā)應用遇到了打印小票的功能,實現途中還是幾經波折,下面這篇文章主要給大家介紹了關于electron實現打印功能支持靜默打印、無感打印的相關資料,需要的朋友可以參考下
    2023-12-12
  • vue獲取參數的幾種方式總結

    vue獲取參數的幾種方式總結

    這篇文章主要介紹了vue獲取參數的幾種方式總結,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • vue-cli腳手架的.babelrc文件用法說明

    vue-cli腳手架的.babelrc文件用法說明

    這篇文章主要介紹了vue-cli腳手架的.babelrc文件用法說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Vue?props傳遞的類型和寫法分享

    Vue?props傳遞的類型和寫法分享

    這篇文章主要介紹了Vue?props傳遞的類型和寫法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • vue中導出Excel表格的實現代碼

    vue中導出Excel表格的實現代碼

    項目中我們可能會碰到導出Excel文件的需求,這篇文章主要介紹了vue中導出Excel表格的實現代碼,非常具有實用價值,需要的朋友可以參考下
    2018-10-10
  • 關于element?ui中的el-scrollbar橫向滾動問題

    關于element?ui中的el-scrollbar橫向滾動問題

    這篇文章主要介紹了關于element?ui中的el-scrollbar橫向滾動問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 在vue中安裝使用sass的實現方法

    在vue中安裝使用sass的實現方法

    這篇文章主要介紹了在vue中安裝使用sass的實現方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05

最新評論