之路"/>
Vue3源码学习之路
- 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();
}
更多推荐
Vue3源码学习之路
发布评论