Vue3源码学习之路-实现watch功能

  • 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();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值