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

詳解Vue如何手寫虛擬dom并進行渲染

 更新時間:2024年03月11日 14:49:26   作者:不要止步于此  
這篇文章主要為大家詳細介紹了渲染器的工作原理,以及如何將真實dom或者組件用虛擬dom的形式進行描述并渲染,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

虛擬dom如何渲染為真實dom

虛擬dom轉(zhuǎn)換為真實dom其實就是編寫一個渲染函數(shù),將虛擬dom逐個創(chuàng)建為真實的dom元素并將對應(yīng)事件添加到當(dāng)前創(chuàng)建的元素上,再掛載到頁面的指定元素下。

認識虛擬dom

虛擬dom其實就是用來描述真實dom的javascript對象。

來看下面例子,把一個真實dom用虛擬dom來進行描述:

真實dom

<div onclick="onAlert()"><span>點擊我</span><span></span></div>
<script>
    function onAlert() {
      alert('點擊事件回調(diào)函數(shù)')
    }
</script>

轉(zhuǎn)換為虛擬dom

const vnode = {
  tag: 'div',
  props: {
    onClick: () => alert('點擊事件回調(diào)函數(shù)')
  },
  children: [
    {
      tag: 'span',
      children: '點擊我'
    },
    {
      tag: 'span',
    }
  ]
}

渲染器實現(xiàn)

/**
 * @param {object} vnode 虛擬dom對象
 * @param {HTMLElement} container 掛載虛擬dom的真實dom容器
 */
function renderer(vnode, container) {
  const { tag, props, children } = vnode
  const el = document.createElement(tag)
  for(const key in props) {
    if(/^on/.test(key)) {
      // 轉(zhuǎn)換為合法的監(jiān)聽事件名稱
      const eventNmae = key.substring(2).toLowerCase()
      // 在當(dāng)前創(chuàng)建的el元素上掛載監(jiān)聽事件
      el.addEventListener(eventNmae, props[key])
    }
  }

  if(typeof children === 'string') {
    // 創(chuàng)建一個文本節(jié)點添加到el元素下
    el.appendChild(document.createTextNode(children))
  } else if(Array.isArray(children)) {
    // 子節(jié)點為數(shù)組,遞歸調(diào)用renderer函數(shù)
    children.forEach(vnode => renderer(vnode, el))
  }
  console.log(container)
  // 將元素掛載到容器上
  container.appendChild(el)
}

現(xiàn)在將上面轉(zhuǎn)換的虛擬dom傳入函數(shù)執(zhí)行看下效果

// 把虛擬dom渲染到id為app的元素下
renderer(vnode, document.getElementById('app'))

下圖可看到虛擬dom已經(jīng)成功渲染為真實dom,并且點擊事件也成功觸發(fā)了!

虛擬dom描述組件

以上講了如何使用虛擬dom(vnode)描述真實dom,但還不夠!如果我們封裝了一個組件,又該如何使用虛擬dom進行描述呢?

總的來說,組件就是一組dom元素的封裝,這組dom元素就是組件要渲染的內(nèi)容,比如前面例子的vnode對象就可以認為是一個組件。

方法組件

const MyComponent = function () {
  return {
    tag: 'div',
    props: {
      onClick: () => alert('MyComponent點擊事件回調(diào)函數(shù)')
    },
    children: [
      {
        tag: 'span',
        children: 'MyComponent'
      },
      {
        tag: 'span',
      }
    ]
  }
}

對象組件

const MyComponent2 = {
  render() {
    return {
      tag: 'div',
      props: {
        onClick: () => alert('MyComponent2點擊事件回調(diào)函數(shù)')
      },
      children: [
        {
          tag: 'span',
          children: 'MyComponent2'
        },
        {
          tag: 'span',
        }
      ]
    }
  }
}

修改渲染器支持組件渲染

/**
 * @param {object} vnode 虛擬dom對象
 * @param {HTMLElement} container 掛載虛擬dom的真實dom容器
 */
function renderer(vnode, container) {
  const { tag } = vnode
  if(typeof tag === 'string') {
    mountElement(vnode, container)
  } else if(typeof tag === 'function') {
    mountComponent(tag(), container)
  } else if(typeof tag === 'object') {
    mountComponent(tag.render(), container)
  }
}

function mountElement(vnode, container) {
  const { tag, props, children } = vnode
  const el = document.createElement(tag)
  for(const key in props) {
    if(/^on/.test(key)) {
      // 轉(zhuǎn)換為合法的監(jiān)聽事件名稱
      const eventNmae = key.substring(2).toLowerCase()
      // 在當(dāng)前創(chuàng)建的el元素上掛載監(jiān)聽事件
      el.addEventListener(eventNmae, props[key])
    }
  }

  if(typeof children === 'string') {
    // 創(chuàng)建一個文本節(jié)點添加到el元素下
    el.appendChild(document.createTextNode(children))
  } else if(Array.isArray(children)) {
    // 子節(jié)點為數(shù)組,遞歸調(diào)用renderer函數(shù)
    children.forEach(vnode => renderer(vnode, el))
  }
  console.log(container)
  // 將元素掛載到容器上
  container.appendChild(el)
}

function mountComponent(vnode, container) {
  // 遞歸調(diào)用renderer
  renderer(vnode, container)
}

渲染組件

const vnode = {
  tag: 'div',
  children: [
    {
      tag: 'span',
      props: {
        onClick: () => alert('span點擊事件回調(diào)函數(shù)')
      },
      children: '我是span標簽'
    },
    // 組件
    {
      tag: MyComponent,
    },
    // 組件
    {
      tag: MyComponent2,
    }
  ]
}

// 把虛擬dom渲染到id為app的元素下
renderer(vnode, document.getElementById('app'))

下圖可看到,對應(yīng)的組件及事件都已經(jīng)掛載成功!

總結(jié)

最后,是不是覺得渲染器其實也沒有想象中那么難!其實這只是一個創(chuàng)建節(jié)點的渲染器,但其精髓在于更新節(jié)點。假設(shè)我們對虛擬dom做了一些小修改,渲染器需要精確找到vnode對象的變更點且只更新變更的內(nèi)容,而不是重新走一遍創(chuàng)建節(jié)點的流程。

以上就是詳解Vue如何手寫虛擬dom并進行渲染的詳細內(nèi)容,更多關(guān)于Vue虛擬dom的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • VUE前端實現(xiàn)token的無感刷新3種方案(refresh_token)

    VUE前端實現(xiàn)token的無感刷新3種方案(refresh_token)

    這篇文章主要給大家介紹了關(guān)于VUE前端實現(xiàn)token的無感刷新3種方案(refresh_token)的相關(guān)資料,為了提供更好的用戶體驗,我們可以通過實現(xiàn)Token的無感刷新機制來避免用戶在使用過程中的中斷,需要的朋友可以參考下
    2023-11-11
  • 詳解利用eventemitter2實現(xiàn)Vue組件通信

    詳解利用eventemitter2實現(xiàn)Vue組件通信

    這篇文章主要介紹了詳解利用eventemitter2實現(xiàn)Vue組件通信,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Vue封裝組件并上傳到npm的教程詳解

    Vue封裝組件并上傳到npm的教程詳解

    這篇文章主要為大家詳細介紹了Vue封裝組件并上傳到npm的相關(guān)教程,文中的示例代碼講解詳細,具有一定的借鑒價值,有需要的小伙伴可以參考下
    2024-04-04
  • vue雙向綁定數(shù)據(jù)限制長度的方法

    vue雙向綁定數(shù)據(jù)限制長度的方法

    這篇文章主要為大家詳細介紹了vue雙向綁定數(shù)據(jù)限制長度的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • 在vue中讀取本地Json文件的方法

    在vue中讀取本地Json文件的方法

    今天小編就為大家分享一篇在vue中讀取本地Json文件的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09
  • 如何手寫簡易的 Vue Router

    如何手寫簡易的 Vue Router

    這篇文章主要介紹了如何手寫簡易的 Vue Router,幫助大家更好的理解和使用vue,感興趣的朋友可以了解下
    2020-10-10
  • vue3父子同信的雙向數(shù)據(jù)的項目實現(xiàn)

    vue3父子同信的雙向數(shù)據(jù)的項目實現(xiàn)

    我們知道的是,父傳子的通信,和子傳父的通信,那如何實現(xiàn)父子相互通信的呢,本文就來詳細的介紹一下,感興趣的可以了解一下
    2023-08-08
  • VUE引入騰訊地圖并實現(xiàn)軌跡動畫的詳細步驟

    VUE引入騰訊地圖并實現(xiàn)軌跡動畫的詳細步驟

    這篇文章主要介紹了VUE引入騰訊地圖并實現(xiàn)軌跡動畫,引入步驟大概是在 html 中通過引入 script 標簽加載API服務(wù),結(jié)合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2022-09-09
  • vue 中自定義指令改變data中的值

    vue 中自定義指令改變data中的值

    這篇文章主要介紹了vue 中自定義指令改變data中的值,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2017-06-06
  • vue.js+element-ui動態(tài)配置菜單的實例

    vue.js+element-ui動態(tài)配置菜單的實例

    今天小編就為大家分享一篇vue.js+element-ui動態(tài)配置菜單的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09

最新評論