mini-vue3实现记录-组件渲染与更新

佟颖逸
2023-12-01

我们知道vnode上存在type属性,当type为对象时,说明该虚拟节点为component组件类型的。针对组件类型的vnode,我们需要进行特殊处理

patch打补丁时,我们针对不同了虚拟节点会进行不同的处理,这里暂时不讨论Fragment等特殊类型节点,我们只将虚拟节点分为ELEMENTCOMPONENT类型节点。

渲染

对于COMPONENT类型节点的渲染,我们要进行两步:

  1. 创建组件实例instance,用于初始化挂载props, slots
  2. 调用组件的render函数,得到渲染的虚拟节点,进行patch打补丁

对于第二步,每次组件更新时,我们都需要重新调用组件的render函数进行渲染,那么如何在响应式数据变化时触发对render函数的调用呢?我们可以将第二步传入effect函数中,作为依赖被收集,那么当响应式数据发生变化时,第二步会重新执行。

可以看出,对于初始化时,我们需要进行第一第二步,而当组件更新时,我们只需要进行第二步,并对节点的一些关键属性进行更新即可,并不需要每次重新建立dom节点挂载,所以我们会在patchComponent类型节点时对是进行挂载还是更新组件进行判断

 function processComponent(n1, initialVNode, container, parentComponent) {
    if (!n1) {
      // 挂载组件
      mountComponent(initialVNode, container, parentComponent);
    } else {
      // 更新组件
      updateComponent(n1, initialVNode);
    }
    // mountComponent(initialVNode, container, parentComponent);
  }

在上述的逻辑中,每次响应式数据发生变化,第二步就会重新执行一次,假设有一个const count = ref(0),我们使用for循环count.value++100次, 那么组件就会重新渲染100次。事实上,我们只需要重新渲染一次即可,如何实现呢?

渲染函数与微任务

我们可以在第一次响应式数据发生变化,触发依赖时,将渲染函数存进一个队列,并将该队列放到微任务中执行,这样做的目的在于,当响应式数据更改,触发依赖,依赖函数只会在响应式数据更新完毕(同步任务)后执行,也就是在微任务内queue遍历执行。
在前文,我们将更新渲染组件的操作直接传入effect函数,这样当响应式数据更新时会直接触发,那么我们如何达到将其存入队列的操作呢?我们可以利用前文reactivity部分实现的scheduler调度器,在调度器内,我们将更新渲染函数存入队列(使用scheduler就不会触发传入effectfn
那么nextTickapi是如何实现的也很明了了,我们只需要再创建一个微任务,在这个微任务内我们就可以获取到更新之后(触发依赖,重新渲染)的数据。

instance.update = effect({
      ...
? ? ? },
? ? ? {
? ? ? ? scheduler() {
? ? ? ? ? queueJobs(instance.update);
? ? ? ? },
? ? ? }
? ?);
// 响应式数据更新 -> 触发scheduler -> 第一次进入 isFlushing 为 false, 创建微任务,关闭创建微任务入口

// 每次调用,不断将runner添加进queue中

// 同步任务执行完毕后,微任务取出,遍历queue执行runner,清空queue


const queue: Set<any> = new Set();

// const queue: any[] = [];

let isFlushing = false; // 是否正在进行刷新队列

const p = Promise.resolve();


export function nextTick(fn) {

? return fn ? Promise.resolve().then(fn) : Promise.resolve();

}

export function queueJobs(job) {

? queue.add(job); // 将runner添加

? if (!isFlushing) {
? ? isFlushing = true;
? ? p.then(() => {
? ? ? try {
? ? ? ? queue.forEach((job: any) => job());
? ? ? } finally {
? ? ? ? isFlushing = false;
? ? ? ? queue.clear();
? ? ? }
? ? });
? }
}

详细代码请见( github.com/4noth1ng/my… )

 类似资料: