欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

diff

程序员文章站 2024-01-20 22:09:58
...

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

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

diff做了什么

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

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

    标签改变

    属性变更

    文本变更

    节点删除

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

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

经过diff后我们要拿到的数据

type: ATTRS 属性变化, REPLACE: 标签变更,TEXT: 文本变更,REMOVE: 节点删除

现在开始写代码了

假设我们的数据变更了,那么就生成了一份新的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-…