diff

首先接上文 vnode:juejin.im/post/5cf9e4…node

以前咱們先看一張圖片,來了解一下diff到patch的流程圖git

patch.png

diff作了什麼

在數據變動後,生成新的dom樹,新舊兩份樹進行對比github

  • 節點一一比對,判斷以下內容bash

    標籤改變dom

    屬性變動函數

    文本變動post

    節點刪除ui

因兄弟節點刪除形成的平移,或者與兄弟節點交換位置,那麼也會觸發上面的條件,這個顯然不是想要的結果,爲了判斷是不是原有的節點變換了位置,因此加入了key,這也是爲何推薦填寫惟一的key值,而不少人初學者直接使用了index,這樣並沒什麼用處,只是消除了警告spa

這一塊也比較複雜,因此不考慮,只作第一步的事情。主要以瞭解總體過程爲主。code

通過diff後咱們要拿到的數據

type: ATTRS 屬性變化, REPLACE: 標籤變動,TEXT: 文本變動,REMOVE: 節點刪除

patches.png

如今開始寫代碼了

假設咱們的數據變動了,那麼就生成了一份新的virtualDom了

src/index.js

let virtualDom1 = createElement('ui', { class: 'li-group' }, [
  createElement('li', { class: 'item' }, ['a']),
  createElement('li', { class: 'item' }, ['b'])
])

let virtualDom2 = createElement('ui', { class: 'lis-group' }, [
  createElement('div', { class: 'item' }, ['a']),
  createElement('li', { class: 'item' }, ['2'])
])

複製代碼

接下來對兩份virtualDom進行diff

src/index.js

import diff from './diff'
let patches = diff(virtualDom1, virtualDom2)
複製代碼
src/diff.js


const REMOVE = 'REMOVE'
const REPLACE = 'REPLACE'
const ATTRS = 'ATTRS'
const TEXT = 'TEXT'


function diff(oldThree, newThree) {
  let patches = {}
  let index = 0

  // 遞歸比較
  walk(oldThree, newThree, index, patches)

  return patches;
}

function walk(oldNode, newNode, index, patches) {
  if (!newNode) {
    // 新節點不存在,既老節點被刪除
    patches[index] = { type: REMOVE }
  } else if(isText(oldNode)&&isText(newNode)){
    // 是文本的狀況
    if(oldNode !== newNode){
      patches[index] = { type: TEXT, text: newNode }
    }
  } else if (oldNode.type !== newNode.type) {
    // 標籤變動
    patches[index] = { type: REPLACE, newNode } 
    diffChildren(oldNode.children, newNode.children, patches)
  } else if (oldNode.type === newNode.type) {
    // diff屬性
    let attrs = diffAttr(oldNode.attrs, newNode.attrs)
    if (Object.keys(attrs).length) {
      patches[index] = { type: ATTRS, attrs }
    }
    // diff子節點
    diffChildren(oldNode.children, newNode.children, patches)
  }
}


// diff屬性
function diffAttr(oldAttrs, newAttrs) {
  let attrs = {}
  for (let key in oldAttrs) {
    if (oldAttrs[key] !== newAttrs[key]) {
      attrs[key] = newAttrs[key]
    }
  }

  for (let key in newAttrs) {
    if (!oldAttrs.hasOwnProperty(key)) {
      attrs[key] = newAttrs[key]
    }
  }

  return attrs
}

// diff子節點
let INDEX = 0
function diffChildren(oldChildren, newChildren, patches) {
  oldChildren.forEach((child, idx) => {
    walk(child, newChildren[idx], ++INDEX, patches)
  })
}

function isText(val){
  return (typeof val === 'string' || typeof val === 'number')
}


export default diff

複製代碼

拿到了patches咱們就能夠調用patch函數去修改dom了

src/index.js

import patch from './patch'
patch(el, patches)
複製代碼

咱們該寫patch函數了

src/patch.js

import { render, Element, setAttr } from "./element";

let allPatches
let index = 0
function patch(node, patches) {
  allPatches = patches
  walk(node)
}

function walk(node) {
  let currentPatch = allPatches[index++]
  let childNodes = node.childNodes
  if (childNodes) {
    // 遍歷全部節點
    childNodes.forEach(child=>{
      walk(child)
    })
  }

  if (currentPatch) {
    doPatch(node, currentPatch)
  }
}

const REMOVE = 'REMOVE'
const REPLACE = 'REPLACE'
const ATTRS = 'ATTRS'
const TEXT = 'TEXT'
function doPatch(node, patches) {
  switch (patches.type) {
    case REMOVE:
      node.remove()
      break;
    case REPLACE:
      let newNode
      if (patches.newNode instanceof Element) {
        newNode = render(patches.newNode)
      } else {
        newNode = document.createTextNode(patches.newNode)
      }
      node.parentNode.replaceChild(newNode, node)
      break;
    case ATTRS:
      // 掛載屬性
      for (let key in patches.attrs) {
        let value = patches.attrs[key]
        if (value) {
          setAttr(node, key, value)
        } else {
          node.removeAttribute(key)
        }

      }
      break;
    case TEXT:
      node.textContent = patches.text
      break;
    default:
      break;
  }
}

export default patch


複製代碼

github: github.com/XueMary/my-…

相關文章
相關標籤/搜索