vue3源码系列(四)——watch

120 阅读1分钟

这篇我将介绍vue3中的watch这个API,其实它也相当于一个effect。跟之前一样,我们先讲其用法,之后再介绍其源码。

watch用法

watch函数接受三个参数,第一个为监听的依赖值(有多种形态),第二个为依赖值变化后所要执行的回调,第三个为option,控制是否深度监听的deep,跟是否立即执行immediate。如下案例:

<!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>

    const { reactive, effect, watch, ref} = VueReactivity

    const personInfo = reactive({
      name: '张三',
      age: 18,
    })

    const name1 = ref('王五')
    const age1 = ref(19)

    // 1、传入函数
    watch(() => personInfo.name, () => {
      console.log('personInfo.name的值被改变了11')
    })

    // 2、传入响应式对象(这时默认时深度监听的,不推荐,比较耗性能)
    watch(personInfo, () => {
      console.log('personInfo.name的值被改变了22')
    })

    // 3、传入数组
    watch([name1, age1], () => {
      console.log('personInfo.name的值被改变了33')
    })

    // 重点:如果写成以下这样是不能做到监听的,因为这时他代表的是传入一个具体的值为张三,可想而知,把张三传进去肯定是不能做到响应式的
    watch(personInfo.name, () => {
      console.log('personInfo.name的值被改变了44')
    })

    setTimeout(() => {
      personInfo.name = '李四'
      name1.value = '小刚子'
    }, 2000)
    
  </script>
</body>
</html>

实现watch(核心代码)

import { ReactiveEffect } from "./effect";
import { isReactive } from "./reactive";

// 递归访问响应式对象的每个属性的目的是为了触发他的get方法,让其进行依赖收集
function traversal(value, set = new Set()) { // 考虑如果对象中有循环引用问题
    // 递归终结条件:如果不是对象就不递归了
    if(!isObject(value)) return value;
    if(set.has(value)) return value;
    set.add(value);
    for(let key in value) {
        traversal(value[key], set);
    }
    return value;
}


// source:用户传入的对象,cb:对应用户的回调
export function watch(source, cb) {

    let getter;
    if(isReactive(source)) { // 传入响应式对象
            // 对我们用户传入的对象进行循环  (递归循环,只要循环就会访问对象上的每一个属性,访问属性的时候会收集effect)
        getter = () => traversal(source); 
    }else if(isFunction(source)) { // 传入函数
        getter = source;
    }else { // 其它的方式暂未实现
        return
    }

    let cleanup;
    const onCleanup = (fn) => {
        cleanup = fn;  // 保存用户传入的函数
    }

    let oldValue;
    const job = () => {
        if(cleanup) cleanup(); // 下一次watch触发上一次watch的清理
        const newValue = effect.run();
        cb(newValue, oldValue, onCleanup);
        oldValue = newValue; // 把新值赋值给oldValue,刷新老值
    }

    const effect = new ReactiveEffect(getter, job);  // 监控自己构造的函数,变化后重新执行job方法

    oldValue = effect.run();
	
}

总结

watch也是依赖effect来实现的,依赖值变化后执行对应函数,该函数想到与effect方法中的scheduler调度器。