24-更新element-初始化

96 阅读3分钟

思考

  1. 如何触发侦听收集?
  2. 如何判断是初始化或者是更新?

基于这两点,逐步实现element更新流程

先来个例子

/*
 * @Author: Lin zefan
 * @Date: 2022-03-21 21:46:14
 * @LastEditTime: 2022-04-02 10:42:02
 * @LastEditors: Lin zefan
 * @Description:
 * @FilePath: \mini-vue3\example\updateComponent\App.js
 *
 */

import { h, ref } from "../../lib/mini-vue.esm.js";

export default {
  setup() {
    const counter = ref(1);
    function inc() {
      counter.value += 1;
    }
    return {
      counter,
      inc,
    };
  },
  render() {
    return h("div", {}, [
      h("div", {}, "" + this.counter),
      h("button", { onClick: this.inc }, "inc"),
    ]);
  },
};

记得把相关函数暴露出来

/*
 * @Author: Lin zefan
 * @Date: 2022-03-22 15:40:00
 * @LastEditTime: 2022-04-02 10:40:45
 * @LastEditors: Lin zefan
 * @Description: 打包入口文件
 * @FilePath: \mini-vue3\src\index.ts
 *
 */

// 暴露runtime-core模块
export * from "./runtime-dom/index";
export * from "./reactivity/ref";
export * from "./reactivity/effect";

依赖收集

在写ref、reative的时候,已经用effect做依赖收集了,vue的双向绑定也是借助这些api来的,所以在初始化阶段做依赖收集。

解构ref对象

利用 proxyRefs 解构setup中的ref对象

// component.ts
function handleSetupResult(instance, setupResult) {
  /** 这里有setup返回值有两种情况
   * 1. 是一个函数
   * 2. 是一个对象
   */
  // 如果是对象,将对象注入上下文
  if (typeof setupResult === "object") {
    // 利用proxyRefs解构ref对象
    instance.setupState = proxyRefs(setupResult);
  } else {
    // 如果是函数,那么当作render处理TODO
  }

  // other code
}

依赖收集

render.ts

 function setupRenderEffect(instance, container) {
    const { proxy, vnode, isMounted } = instance;
    effect(() => {
      // 通过render函数,获取render返回虚拟节点,并绑定render的this
      const subTree = instance.render.call(proxy);
      /**
       * 1. 调用组件render后把结果再次给到patch
       * 2. 再把对应的dom节点append到container
       * 3. 把当前实例传过去,让子组件可以通过parent获取父组件实例
       */
      patch(subTree, container, instance);
      /** 挂载当前的dom元素到$el
       * 1. 当遍历完所有Component组件后,会调用processElement
       * 2. 在processElement中,会创建dom元素,把创建的dom元素挂载到传入的vnode里面
       * 3. 当前的dom元素也就是processElement中创建的dom元素
       */
      vnode.el = subTree.$el;
    });
  }

借助effect侦听数据变化,重新触发patch

更新element判断

  1. 在实例上加一个flag来判断是否初始化
  2. 在实例上加一个key来保存老的vnode
  3. 更新element时,会比对新老vnode的差别,只做变动部分的更新

新增实例属性

component.ts

export function createComponentInstance(initVNode, parent) {
  const component = {
    // other code
    isMounted: false, // 是否初始化
    preTree: {}, // 旧的vnode
  };

 // other code
}

初始化判断

render.ts

 function setupRenderEffect(instance, container) {
    effect(() => {
      // 初始化vnode
      if (!instance.isMounted) {
        let { proxy, vnode } = instance;
        // 通过render函数,获取render返回虚拟节点,并绑定render的this
        const subTree = instance.render.call(proxy);
        /**
         * 1. 调用组件render后把结果再次给到patch
         * 2. 再把对应的dom节点append到container
         * 3. 把当前实例传过去,让子组件可以通过parent获取父组件实例
         */
        patch(null, subTree, container, instance);
        /** 挂载当前的dom元素到$el
         * 1. 当遍历完所有Component组件后,会调用processElement
         * 2. 在processElement中,会创建dom元素,把创建的dom元素挂载到传入的vnode里面
         * 3. 当前的dom元素也就是processElement中创建的dom元素
         */
        vnode.el = subTree.$el;
        // 更新初始化状态
        instance.isMounted = true;
        // 保存当前vnode
        instance.preTree = subTree;
      } else {
        let { proxy } = instance;
        // 通过render函数,获取render返回虚拟节点,并绑定render的this
        const nowTree = instance.render.call(proxy);
        // 旧vnode
        const preTree = instance.preTree;
        // 对比新老vnode
        patch(preTree, nowTree, container, instance);
        // 更新旧的vnode
        instance.preTree = nowTree;
      }
    });
  }
  
  function patch(n1, n2, container, parentComponent) {
    if (!n2) return;
    const { type } = n2;
    switch (type) {
      case Fragment:
        processFragment(n1, n2, container, parentComponent);
        break;
      case TextNode:
        processTextNode(n2, container);
        break;

      default:
        const shapeFlags = getShapeFlags(type);

        if (shapeFlags === ShapeFlags.COMPONENT) {
          // 是一个Component
          processComponent(n1, n2, container, parentComponent);
        } else if (shapeFlags === ShapeFlags.ELEMENT) {
          // 是一个element
          processElement(n1, n2, container, parentComponent);
        }

        break;
    }
  }
 

新老vnode判断

render.ts

  • n1为旧的vnode,初始化是空的,所以是创建组件阶段
  • n1不为空即非初始化,即走更新dom操作
  function processComponent(n1, n2, container, parentComponent) {
    if (!n1) {
      // 创建组件
      mountComponent(n2, container, parentComponent);
    } else {
      // 更新组件
      updateComponent(n1, n2, container, parentComponent);
    }
  }
  
  function updateComponent(n1, n2, container, parentComponent) {
    console.log("updateComponent更新");
    console.log("旧vnode", n1);
    console.log("新vnode", n2);
  }
  
  function processElement(n1, n2, container, parentComponent) {
    if (!n1) {
      mountElement(n2, container, parentComponent);
    } else {
      updateElement(n1, n2, container, parentComponent);
    }
  }
  
  function updateElement(n1, n2, container, parentComponent) {
    console.log("updateElement更新");
    console.log("旧vnode", n1);
    console.log("新vnode", n2);
  }