我们知道vnode
上存在type
属性,当type
为对象时,说明该虚拟节点为component
组件类型的。针对组件类型的vnode
,我们需要进行特殊处理
在patch
打补丁时,我们针对不同了虚拟节点会进行不同的处理,这里暂时不讨论Fragment
等特殊类型节点,我们只将虚拟节点分为ELEMENT
和COMPONENT
类型节点。
对于COMPONENT
类型节点的渲染,我们要进行两步:
instance
,用于初始化挂载props
, slots
等render
函数,得到渲染的虚拟节点,进行patch
打补丁对于第二步,每次组件更新时,我们都需要重新调用组件的render函数进行渲染,那么如何在响应式数据变化时触发对render函数的调用呢?我们可以将第二步传入effect
函数中,作为依赖被收集,那么当响应式数据发生变化时,第二步会重新执行。
可以看出,对于初始化时,我们需要进行第一第二步,而当组件更新时,我们只需要进行第二步,并对节点的一些关键属性进行更新即可,并不需要每次重新建立dom节点挂载,所以我们会在patch
Component类型节点时对是进行挂载还是更新组件进行判断
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就不会触发传入effect
的fn
)
那么nextTick
api是如何实现的也很明了了,我们只需要再创建一个微任务,在这个微任务内我们就可以获取到更新之后(触发依赖,重新渲染)的数据。
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… )