diff

965 阅读2分钟

首先接上文 vnode:juejin.cn/post/684490…

之前我们先看一张图片,来了解一下diff到patch的流程图

patch.png

diff做了什么

在数据变更后,生成新的dom树,新旧两份树进行对比

  • 节点一一比对,判断如下内容

    标签改变

    属性变更

    文本变更

    节点删除

因兄弟节点删除造成的平移,或者与兄弟节点交换位置,那么也会触发上面的条件,这个显然不是想要的结果,为了判断是否是原有的节点变换了位置,所以加入了key,这也是为什么推荐填写唯一的key值,而很多人初学者直接使用了index,这样并没什么用处,只是消除了警告

这一块也比较复杂,所以不考虑,只做第一步的事情。主要以了解整体过程为主。

经过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-…