vue3.0的diff算法

283 阅读2分钟

vue3.0的组件的Proxy响应式对象发生改变时,组件将重新渲染,新的虚拟dom就要和旧的虚拟dom对比,来更新组件里的html元素,对比过程就是diff算法。

vue3.0响应式过程主要reactive方法把传入的对象参数转换成响应式对象。主要是用es6的Proxy对象进行数据劫持,定义全局WeakMap的reactiveMap对象,对对象进行收集,如果里面已经存在该对象的Proxy,则不进行劫持,反正则用该对象传入Proxy对象中进行数据劫持,生成新的proxy对象,收集到reactiveMap里。

vue3.0还有一个重要的方法就是effect方法,用来收集依赖,更新视图。内部方法Track是收集依赖,内部方法trigger是触发依赖,所以响应式对象里的get方法就执行的是Track,set方法就执行的是trigger,每次调用effect方法时,定义的全局变量activeEffect就为当前的effect方法,如果effect参数的回调函数用到了proxy对象的变量,立刻会执行Track方法,把响应的activeEffect收集起来,如果proxy对象值改变时,则会拿到收集到的activeEffect方法,执行里面的回调,更新视图。

组件新建,更新代码为:

  effect(function componentEffect() {
 
    // 需要创建一个effect,在effect中调用render,这样render 方法中获取数据会收集这个effect
    // 属性改变重新执行
    // 判断 第一次加载
    // instance时组件对象
    if (!instance.isMounted) {
      // 获取到render(setup)返回值
      let proxy = instance.proxy
      let subTree = instance.subTree = instance.render.call(proxy, proxy) // 执行 render 组件中 创建 渲染节点
      console.log(subTree)
      patch(null, subTree, container)
      instance.isMounted = true
    } else {
      // 更新操作
      let proxy = instance.proxy
      // 以前的虚拟dom
      const prevTree = instance.subTree
      // 最新的虚拟dom
      const nextTree = instance.render.call(proxy, proxy)
      instance.subTree = nextTree
      // diff算法,更新节点
      patch(prevTree, nextTree, container)
    }
  })
  // 元素操作 增删改查
export const nodeOps = {
  // 创建元素 createElement 注意: vue runtime-dom => 平台
  createElement: tagName => document.createElement(tagName),
  remove: child => {
    let parent = child.parentNode
    if (parent) {
      parent.removeChild(child)
    }
  },
  insert: (child, parent, ancher = null) => {
    parent.insertBefore(child, ancher)
  },
  querySelector: select => document.querySelector(select),
  setElementText: (el, text) => {
    el.textContent = text
  },
  createText: text => document.createTextNode(text),
  setText: (node, text) => node.nodeValue = text
}
   // 判断是否是同一个元素
  const isSomeVode = (n1,n2) => {
    return n1.type == n2.type && n1.key == n2.key
  }
  const unmount = (vnode) => {
    nodeOps.remove(vnode.el)// el虚拟dom的真实元素,在挂载的时候赋值
  }
  // n1是旧的,n2是新的 
  const patch = (n1, n2, container,ancher = null) => {
    // 针对不同的类型 1 组件 2元素 3 文本
    // 对比 vue3:1判断是不是同一个元素 2 同一元素 (1 props children)
    // 判断是不是同一个元素
    if (n1&&!isSomeVode(n1, n2)) {
      unmount(n1)
      n1 = null
    }
    let {shapeFlag, type} = n2
    switch (type) {
      case TEXT:
        processText(n1, n2, container)
        break
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
          console.log('元素')
          processElement(n1, n2, container, ancher)
        } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
          processComponent(n1, n2, container)
        }
    }
  }