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

Vue中渲染系統(tǒng)模塊的實現詳解

 更新時間:2023年07月17日 16:02:35   作者:會說法語的豬  
想要實現一個簡潔版的Mini-Vue框架,應該包含三個模塊:分別是:渲染系統(tǒng)模塊、可響應式系統(tǒng)模塊、應用程序入庫模塊,本文主要介紹的是渲染系統(tǒng)模塊的實現,需要的可以參考一下

我們想要實現一個簡潔版的Mini-Vue框架,應該包含三個模塊:分別是:渲染系統(tǒng)模塊、可響應式系統(tǒng)模塊、應用程序入庫模塊。

這篇就來寫一下渲染系統(tǒng)模塊。剩下兩個模塊后面有時間再更新。

vue渲染系統(tǒng)實現,應該包含三個功能,分別是:① h函數,用于返回一個VNode對象;② mount函數,用于將VNode轉為真實dom,并掛載到DOM上;③ patch函數,類似于diff算法,用于對比兩個VNode,決定如何處理新的VNode

虛擬dom本身其實就是個javascript對象。這里會寫一些核心邏輯,大家如果看vue源碼的話,會發(fā)現里面有非常多的邊界情況的處理判斷。我們只需要知道核心邏輯就可以了,也就是下面所寫的。

下面寫的會有用到個判斷類型的方法,getObjType如下: 

也是最準確的判斷數據類型的方法 

function getObjType(obj) {
  let toString = Object.prototype.toString
  let map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object'
  }
  if (obj instanceof Element) {
    return 'element'
  }
  return map[toString.call(obj)]
}

h函數 

h函數其實非常簡單,就是返回一個js對象 

/**
 * 虛擬dom本身就是個javascript對象
 * @param {String} tag 標簽名稱
 * @param {Object} props 屬性配置
 * @param {String | Array} children 子元素
 * @returns 
 */
const h = (tag, props, children) => ({ tag, props, children })

可以看到,這就是個虛擬的dom樹 

mount函數 

mount函數負責將vnode轉為真實的dom,并掛載到指定容器下。 

/**
 * 將虛擬dom轉為真實dom并掛載到指定容器
 * @param {Object} vnode 虛擬dom
 * @param {Element} container 容器
 */
const mount = (vnode, container) => {
  const el = vnode.el = document.createElement(vnode.tag)
  if(vnode.props) {
    for(let key in vnode.props) {
      const value = vnode.props[key]
      if(key.startsWith('on')) {
        el.addEventListener(key.slice(2).toLowerCase(), value)
      } else {
        el.setAttribute(key, value)
      }
    }
  }
  if(vnode.children) {
    if(getObjType(vnode.children) === 'string') {
      el.textContent = vnode.children
    } else if(getObjType(vnode.children) === 'array') {
      vnode.children.forEach(item => {
        mount(item, el)
      })
    }
  }
  container.appendChild(el)
}

這里第三個參數只考慮了,string和array的情況,還有對象的情況相對少一些,就是我們平常的使用的插槽 

patch函數 

patch函數類似于diff算法,用于兩個VNode進行對比,決定如何處理新的VNode

/**
 * 類似diff對比兩個vnode
 * @param {Object} n1 舊vnode
 * @param {Object} n2 新vnode
 */
const patch = (n1, n2) => {
  if(n1.tag !== n2.tag) {
    const n1parentEl = n1.el.parentElement
    n1parentEl.removeChild(n1.el)
    mount(n2, n1parentEl)
  } else {
    const el = n2.el = n1.el
    // 處理props
    const oldProps = n1.props || {}
    const newProps = n2.props || {}
    for(let key in newProps) {
      const oldValue = oldProps[key]
      const newValue = newProps[key]
      if(newValue !== oldValue) {
        if (key.startsWith('on')) {
          el.addEventListener(key.slice(2).toLowerCase(), newValue)
        } else {
          el.setAttribute(key, newValue)
        }
      }
    }
    for(let key in oldProps) {
      if(!(key in newProps)) {
        if (key.startsWith('on')) {
          const oldValue = oldProps[key]
          el.removeEventListener(key.slice(2).toLowerCase(), oldValue)
        } else {
          el.removeAttribute(key)
        }
      }
    }
    const oldChildren = n1.children || []
    const newChildren = n2.children || []
    if(getObjType(newChildren) === 'string') {
      if(getObjType(oldChildren) === 'string') {
        if(newChildren !== oldChildren) {
          el.textContent = newChildren
        }
      } else {
        el.innerHTML = newChildren
      }
    } else if(getObjType(newChildren) === 'array') {
      if(getObjType(oldChildren) === 'string') {
        el.innerHTML = ''
        newChildren.forEach(item => {
          mount(item, el)
        })
      } else if(getObjType(oldChildren) === 'array') {
        // oldChildren -> [vnode1, vnode2, vnode3]
        // newChildren -> [vnode1, vnode5, vnode6, vnode7, vnode8]
        // 前面有相同節(jié)點的元素進行patch操作
        const commonLength = Math.min(newChildren.length, oldChildren.length)
        for(let i = 0; i < commonLength; i++) {
          patch(oldChildren[i], newChildren[i])
        }
        // 2. newChildren.length > oldChildren.length  多余的做掛載操作
        if(newChildren.length > oldChildren.length) {
          newChildren.slice(commonLength).forEach(item => {
            mount(item, el)
          })
        }
        // 3. newChildren.length < oldChildren.length  多余的做移除操作
        if(newChildren.length < oldChildren.length) {
          oldChildren.slice(commonLength).forEach(item => {
            el.removeChild(item.el)
          })
        }
      }
    }
  }
}

完整代碼 

render.js 

/**
 * Vue渲染系統(tǒng)實現
 * 1. h函數,用于返回一個VNode對象;
 * 2. mount函數,用于將VNode掛載到DOM上;
 * 3. patch函數,用于對兩個VNode進行對比,決定如何處理新的VNode;
 */
/**
 * 虛擬dom本身就是個javascript對象
 * @param {String} tag 標簽名稱
 * @param {Object} props 屬性配置
 * @param {String | Array} children 子元素
 * @returns 
 */
const h = (tag, props, children) => ({ tag, props, children })
/**
 * 將虛擬dom轉為真實dom并掛載到指定容器
 * @param {Object} vnode 虛擬dom
 * @param {Element} container 容器
 */
const mount = (vnode, container) => {
  const el = vnode.el = document.createElement(vnode.tag)
  if(vnode.props) {
    for(let key in vnode.props) {
      const value = vnode.props[key]
      if(key.startsWith('on')) {
        el.addEventListener(key.slice(2).toLowerCase(), value)
      } else {
        el.setAttribute(key, value)
      }
    }
  }
  if(vnode.children) {
    if(getObjType(vnode.children) === 'string') {
      el.textContent = vnode.children
    } else if(getObjType(vnode.children) === 'array') {
      vnode.children.forEach(item => {
        mount(item, el)
      })
    }
  }
  container.appendChild(el)
}
/**
 * 類似diff對比兩個vnode
 * @param {Object} n1 舊vnode
 * @param {Object} n2 新vnode
 */
const patch = (n1, n2) => {
  if(n1.tag !== n2.tag) {
    const n1parentEl = n1.el.parentElement
    n1parentEl.removeChild(n1.el)
    mount(n2, n1parentEl)
  } else {
    const el = n2.el = n1.el
    // 處理props
    const oldProps = n1.props || {}
    const newProps = n2.props || {}
    for(let key in newProps) {
      const oldValue = oldProps[key]
      const newValue = newProps[key]
      if(newValue !== oldValue) {
        if (key.startsWith('on')) {
          el.addEventListener(key.slice(2).toLowerCase(), newValue)
        } else {
          el.setAttribute(key, newValue)
        }
      }
    }
    for(let key in oldProps) {
      if(!(key in newProps)) {
        if (key.startsWith('on')) {
          const oldValue = oldProps[key]
          el.removeEventListener(key.slice(2).toLowerCase(), oldValue)
        } else {
          el.removeAttribute(key)
        }
      }
    }
    const oldChildren = n1.children || []
    const newChildren = n2.children || []
    if(getObjType(newChildren) === 'string') {
      if(getObjType(oldChildren) === 'string') {
        if(newChildren !== oldChildren) {
          el.textContent = newChildren
        }
      } else {
        el.innerHTML = newChildren
      }
    } else if(getObjType(newChildren) === 'array') {
      if(getObjType(oldChildren) === 'string') {
        el.innerHTML = ''
        newChildren.forEach(item => {
          mount(item, el)
        })
      } else if(getObjType(oldChildren) === 'array') {
        // oldChildren -> [vnode1, vnode2, vnode3]
        // newChildren -> [vnode1, vnode5, vnode6, vnode7, vnode8]
        // 前面有相同節(jié)點的元素進行patch操作
        const commonLength = Math.min(newChildren.length, oldChildren.length)
        for(let i = 0; i < commonLength; i++) {
          patch(oldChildren[i], newChildren[i])
        }
        // 2. newChildren.length > oldChildren.length  多余的做掛載操作
        if(newChildren.length > oldChildren.length) {
          newChildren.slice(commonLength).forEach(item => {
            mount(item, el)
          })
        }
        // 3. newChildren.length < oldChildren.length  多余的做移除操作
        if(newChildren.length < oldChildren.length) {
          oldChildren.slice(commonLength).forEach(item => {
            el.removeChild(item.el)
          })
        }
      }
    }
  }
}
function getObjType(obj) {
  let toString = Object.prototype.toString
  let map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object'
  }
  if (obj instanceof Element) {
    return 'element'
  }
  return map[toString.call(obj)]
}

測試: 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./render.js"></script>
</head>
<body>
  <div id="app"></div>
  <script>
    const vnode = h('div', { class: 'wft' }, [
      h('h1', null, '標題'),
      h('span', null, '哈哈哈')
    ])
    setTimeout(() => {
      mount(vnode, document.getElementById('app'))
    }, 1500)
    const newVNode = h('div', { class: 'new-wft', id: 'wft' }, [
      h('h3', null, '我是h3')
    ])
    setTimeout(() => {
      patch(vnode, newVNode)
    }, 4000)
  </script>
</body>
</html>

到此這篇關于Vue中渲染系統(tǒng)模塊的實現詳解的文章就介紹到這了,更多相關Vue渲染系統(tǒng)模塊內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論