先来个例子
/*
* @Author: Lin ZeFan
* @Date: 2022-04-09 13:18:11
* @LastEditTime: 2022-04-09 13:26:01
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\example\nextTicker\NextTicker.js
*
*/
// 测试 nextTick 逻辑
import { h, ref, getCurrentInstance } from "../../lib/mini-vue.esm.js";
// 如果 for 循环改变 count 的值 100 次的话
// 会同时触发 100 次的 update 页面逻辑
// 这里可以把 update 页面的逻辑放到微任务中执行
// 避免更改了响应式对象就会执行 update 的逻辑
// 因为只有最后一次调用 update 才是有价值的
window.count = ref(1);
// 如果一个响应式变量同时触发了两个组件的 update
// 会发生什么有趣的事呢?
const Child1 = {
name: "NextTickerChild1",
setup() {},
render() {
return h("div", {}, `child1 count: ${window.count.value}`);
},
};
const Child2 = {
name: "NextTickerChild2",
setup() {
const currentInstance = getCurrentInstance();
function onClick() {
for (let index = 0; index < 100; index++) {
count.value++;
}
console.log("currentInstance", currentInstance);
}
return {
onClick,
};
},
render() {
return h("div", {}, [
h("div", {}, `child2 count: ${window.count.value}`),
h(
"button",
{
onClick: this.onClick,
},
"修改count"
),
]);
},
};
export default {
name: "NextTicker",
setup() {},
render() {
return h(
"div",
{ tId: "nextTicker" },
[h(Child1), h(Child2)]
// `for nextTick: count: ${window.count.value}`
);
},
};
假如我们有个按钮,里面循环了100次,更改了count的值,会发生什么?
会触发100次的effect。。如果页面复杂了,浏览器就得被玩坏了!
Vue3的视图更新,其实是异步的!不会多次触发更新。那Vue3又是怎么做的异步更新呢?
- 把同步任务放到了微任务队列,同步执行完才去执行微任务
- 借助scheduler触发微任务队列来完成异步更新的
- 内部通过pendingFlag来判断调用状态
实现异步更新
render.ts
import { queueJobs } from "./scheduler";
function setupRenderEffect(instance, container, anchor) {
instance.runner = effect(
() => {
// other code
},
{
scheduler() {
// 将本次 update 加入到任务队列中
queueJobs(instance.runner);
},
}
)
}
scheduler.ts
/*
* @Author: Lin ZeFan
* @Date: 2022-04-09 13:35:06
* @LastEditTime: 2022-04-09 14:54:17
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\src\runtime-core\scheduler.ts
*
*/
// 队列
const queue: any[] = [];
// 当前队列调用状态
let jobFlag = false;
export function queueJobs(job) {
// 不存在时再添加进去
if (!queue.includes(job)) {
queue.push(job);
}
queueFlush();
}
function queueFlush() {
if (jobFlag) return;
jobFlag = true;
Promise.resolve().then(() => {
let job;
while ((job = queue.shift())) {
jobFlag = false;
job && job();
}
});
}
实现nextTick
Vue3的nextTick也是把要执行的fn放进微任务队列执行的
// scheduler.ts
const P = Promise.resolve();
export function nextTick(fn) {
return fn ? P.then(fn) : P;
}
基于nextTick优化微任务队列执行
// scheduler.ts
function queueFlush() {
if (isFlushPending) return;
// 通过pending flag来阻止多次调用
isFlushPending = true;
// 把当前任务放进了微任务队列
}
function flushJobs() {
isFlushPending = false;
let job;
while ((job = queue.shift())) {
job && job();
}
}