- watch监控的是对象时,无法区分新值和老值,因为是对象引用的形式
- 当监控一个函数时,函数的返回值就是老值
- watch其实也是effect,会对用户填写的数据进行依赖收集
watch基础例子
packages/reativity/dist/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="./reactivity.global.js"></script>
<!-- <script src="../../../node_modules/vue/dist/vue.global.js"></script> -->
<script>
const { watch, reactive } = VueReactivity;
const state = reactive({ name: 'bowen', age: 18 });
watch(
() => state.age,
(newValue, oldValue) => {
console.log(`newValue: ${newValue}`, `oldValue: ${oldValue}`);
}
);
setTimeout(() => {
state.age = 24;
}, 1000);
</script>
</body>
</html>
packages/reactivity/src/reactive.ts > reactive
reactive函数中二次代理判断优化一下
export function isReactive(value) {
return !!(value && value[ReactiveFlags.IS_REACTIVE]);
}
// 如果传入的是已经代理过的Proxy无需再次代理
if (isReactive(target)) {
return target;
}
新建:packages/reactivity/src/watch.ts
import { isFunction, isObject } from '@vue/shared';
import { ReactiveEffect } from './effect';
import { isReactive } from './reactive';
/**
* 遍历对象
* @param value 对象
* @param set 处理循环引用
*/
function traversal(value, set = new Set()) {
if (!isObject(value)) {
return value;
}
if (set.has(value)) {
return value;
}
set.add(value);
for (const key in value) {
traversal(value[key], set);
}
return value;
}
/**
* watch
* @param source 传入的响应式对象
* @param cb 回调
*/
export function watch(source, cb) {
let getter;
if (isReactive(source)) {
// 需要对传入的对象进行递归循环,循环时访问属性,就会执行effect依赖收集
getter = () => traversal(source);
} else if (isFunction(source)) {
getter = source;
} else {
return;
}
let oldValue;
let effect;
// 值变化后执行
const job = () => {
const newValue = effect.run();
cb(newValue, oldValue);
oldValue = newValue;
};
effect = new ReactiveEffect(getter, job);
oldValue = effect.run();
}
watch中并发异步请求
当我们监听的对象并发出多次修改,并且watch中执行异步请求时,需要按顺序执行并且只渲染最后一次更改
异步例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="./reactivity.global.js"></script>
<!-- <script src="../../../node_modules/vue/dist/vue.global.js"></script> -->
<script>
const app = document.getElementById('app');
const { watch, reactive } = VueReactivity;
const state = reactive({ name: 'bowen', age: 18 });
let i = 3000;
function getData(timer) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(timer);
}, timer);
});
}
watch(
() => state.age,
async (newValue, oldValue, onCleanup) => {
let update = true;
// onCleanup中传入的函数将被下一次watch调用的时候执行
// 就是永远保持最新的更改排在最后一位进行渲染
onCleanup(() => {
update = false;
});
i -= 1000;
let r = await getData(i);
if (update) {
app.innerHTML = r;
}
}
);
state.age = 24;
state.age = 25;
state.age = 26;
</script>
</body>
</html>
watch函数中cb增加onCleanup参数
export function watch(source, cb) {
let getter;
if (isReactive(source)) {
// 需要对传入的对象进行递归循环,循环时访问属性,就会执行effect依赖收集
getter = () => traversal(source);
} else if (isFunction(source)) {
getter = source;
} else {
return;
}
let oldValue;
let effect;
let cleanup;
// 用户执行onCleanup时传入的函数
const onCleanup = (fn) => {
cleanup = fn;
};
// 值变化后执行
const job = () => {
// 下一次执行时触发上一次watch的清理
if (cleanup) {
cleanup();
}
const newValue = effect.run();
cb(newValue, oldValue, onCleanup);
oldValue = newValue;
};
effect = new ReactiveEffect(getter, job);
oldValue = effect.run();
}