Vue3
中异步更新策略
从零手写一.watchAPI
watchAPI 的核心就是监控值的变化,值发生变化后调用对应的回调函数
1.同步watch
const state = reactive({ name: 'zf' })
watch(() => state.name, (newValue, oldValue) => {
console.log(newValue, oldValue)
}, { flush: 'sync' }); // 同步watcher
setTimeout(() => {
state.name = 'jw'
state.name = 'zf'
}, 1000);
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
watchAPI根据传入的参数不同,有不同的调用方式
export function watch(source, cb, options:any = {}) {
dowatch(source, cb, options)
}
function dowatch(source, cb, {flush,immediate}) {
let getter = () => source.call(currentInstance); // 保证函数中的this 是当前实例
let oldValue;
const job = () => {
if(cb){
const newValue = runner(); // 获取新值
if(hasChanged(newValue,oldValue)){ // 如果有变化,调用对应的callback
cb(newValue,oldValue);
oldValue = newValue; // 更新值
}
}
}
let scheduler;
if(flush == 'sync') {
scheduler = job
}else if(flush === 'post'){
}else {
// flush === 'pre'
}
const runner = effect(getter, {
lazy: true,
scheduler
})
if(cb){
if(immediate){
job(); // 立即让cb执行
}else{
oldValue = runner(); // 仅执行不调用 cb
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2.异步watch
多次进行更改操作,最终仅仅执行一次
const state = reactive({ name: 'zf' })
watch(() => state.name, (newValue, oldValue) => {
console.log(newValue, oldValue); // xxx zf
});
setTimeout(() => {
state.name = 'jw'
state.name = 'xxx'
}, 1000);
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
根据参数不同,将任务放到不同的队列中
let pendingPreFlushCbs = [] // preCallback
let pendingPostFlushCbs = [] // postCallback
export function queuePreFlushCb(cb) {
queueCb(cb, pendingPreFlushCbs)
}
export function queuePostFlushCb(cb) {
queueCb(cb, pendingPostFlushCbs)
}
function queueCb(cb, pendingQueue) {
pendingQueue.push(cb);
queueFlush();
}
let isFlushPending = false
function queueFlush() {
if (!isFlushPending) { //保证queueFlush方法只能调用一次
isFlushPending = true;
Promise.resolve().then(flushJobs)
}
}
function flushJobs() {
isFlushPending = false;
flushPreFlushCbs(); // 刷新队列
flushPostFlushCbs();
}
function flushPreFlushCbs() {
if(pendingPreFlushCbs.length) {
const deduped: any = [...new Set(pendingPreFlushCbs)];
pendingPreFlushCbs.length = 0;
console.log(deduped)
for (let i = 0; i < deduped.length; i++) {
deduped[i]();
}
flushPreFlushCbs(); // 递归直到用尽
}
}
function flushPostFlushCbs() {
if(pendingPostFlushCbs.length) {
const deduped: any = [...new Set(pendingPostFlushCbs)];
pendingPostFlushCbs.length = 0;
for (let i = 0; i < deduped.length; i++) {
deduped[i]();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
二.watchEffect
watchEffect是没有cb的watch,当数据变化后会重新执行source函数
const state = reactive({ name: 'zf' })
watchEffect(()=>console.log(state.name));
state.name = 'jw'
1
2
3
2
3
watchEffect实现
export function watchEffect(source,options){
dowatch(source, null, options)
}
1
2
3
2
3
function dowatch(source, cb, {flush,immediate}) {
const job = () => {
if(cb){
// ....
}else{ // watchEffect 不需要新旧对比
runner()
}
}
if(cb){
// ...
}else{ // watchEffect 默认会执行一次
runner();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
三.组件异步更新原理
const App = {
setup() {
const state = reactive({ name: 'zf' });
setTimeout(() => { // 多次更新状态
state.name = 'jw';
state.name = 'zf';
state.name = 'zry';
}, 1000);
return {
state
}
},
render: (r) => {
console.log('render~~~'); // 造成多次渲染
return h('div', r.state.name)
}
}
createApp(App).mount('#app');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
给组件更新提供scheduler函数
const setupRenderEffect = (instance, initialVNode, container) => {
instance.update = effect(function componentEffect() {
// ....
}, {
scheduler: queueJob
})
}
1
2
3
4
5
6
7
2
3
4
5
6
7
queueJob的原理也是将渲染函数维护到队列中
let queue: any = []
export function queueJob(job) {
if (!queue.includes(job)) {
queue.push(job);
queueFlush();
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
function flushJobs() {
isFlushPending = false;
flushPreFlushCbs(); // 渲染之前
queue.sort((a, b) => a.id - b.id);
for (let i = 0; i < queue.length; i++) {
const job = queue[i]; // 渲染
job();
}
queue.length = 0;
flushPostFlushCbs(); // 渲染之后
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13