vue3 源码的 reactive是如何进行依赖收集

241 阅读3分钟

相关面试题

1.vue是如何避免收集到重复的副作用函数

2.使用proxy代理相比defineProperty 性能好

我们都知道vue3的双向数据绑定是用proxy来实现;那么proxy 是如何跟track 函数,trigger函数跟effect副作用函数进行关联呢

首先我们来理清代码执行过程

1.代码从effect开始执行, 执行副作用函数,里面包含获取代理对象的属性;

2.因为获取是代理对象的属性,会进入reactive里面的 proxy进行执行get方法, 从而触发里面的track方法进行收集

3.track方法里面如何获取当effect作用函数呢,需要将副作用赋值给全局变量 activeEffect

image.png

1.effect函数是将副作用赋者给activeEffect

image2.png

相关代码以及解说

reactive.js

reactive实现proxy 代理,对对象的属性进行拦截,同时使用track来收集依赖副作用函数, trigger来触发收集的依赖副作用函数


import { track, trigger } from './effect'
import { isObject, hasChanged, isArray } from '../utils';

const proxyMap = new WeakMap();
export const reactive = (obj) => {

    // 传入的值不是对象
    if (!isObject(obj)) return obj
    if (isReactive(obj)) return obj; // 解决嵌套 reactive(reactive(obj))
    if(proxyMap.has(obj)) return proxyMap.get(obj) // 解决 已经代理过了  const a= reactive(obj) const b= reactive(obj)
    const proxy = new Proxy(obj, {
        get: (target, key, receiver) => {
            if (key === '__isReactive') {
                return true
            }
            const res = Reflect.get(target, key, receiver)
            track(target, key)
            // 面试题解决 职业访问才对对象进行双向数据绑定,既是 收集依赖
             return isObject(res) ? reactive(res) : res;
        },
        set(target, key, value, receiver) {
            const oldValue = target[key];
            const oldLength = target.length;
            const res = Reflect.set(target, key, value, receiver);
            if (hasChanged(value, oldValue)) {
              trigger(target, key);
              if (isArray(target) && target.length !== oldLength) {
                trigger(target, 'length');
              }
            }
            return res;
          },
    })

    proxyMap.set(obj, proxy)

    return proxy

}


export const isReactive = (target) => {
    return !!(target && target.__isReactive)
}

effect.js

该文件实现effect副作用函数,track函数跟trigger函数

需要关注问题是

1.track收集依赖的数据结构是什么

2.effect的副作用函数如何保存起来

let activeEffect // 保存副作用
let activeEffectStack = []; // 处理嵌套的effect 比如 effect(() => {effect(() => {})})
export const effect = (fn) => {

    const effectFn = () => {
        try {
            // 将副作用函数保存起来,未了在tack函数获取到
            activeEffect = effectFn
            activeEffectStack.push(activeEffect)
            return fn()
        } finally {
            activeEffectStack.pop()
            activeEffect = activeEffectStack[activeEffectStack.length - 1]
        }
    }
    effectFn();
    return effectFn
}

// track如何跟副作用进行关联;首先从代码的执行顺序进行思考,
// 1.代码从effect开始执行, 执行副作用函数,里面包含获取代理对象的属性;
// 2.因为获取是代理对象的属性,会进入reactive proxy进行执行get方法, 从而触发里面的track方法进行收集
// 3.track方法里面如何获取当fn作用函数呢,需要将副作用赋值给全局变量 activeEffect

// 收集依赖数据结构为
// 比如响应对象是obj= { age: 10,name: 'wu'} 对应数据结构是 { [obj]: {age: [activeEffect], name: [activeEffect]}}

// 面试题为什么用 set来收集副作用 ,避免收集重复的副作用

const targetMap = new WeakMap();
export const track = (target, key) => {

    // 判断是否副作用函数存在
    if (!activeEffect) {
        return
    }
    // 先判断是否已经收集过了
    let desMap = targetMap.get(target)
    if (!desMap) {
        targetMap.set(target, desMap = (new Map()))
    }

    let des = desMap.get(key)
    if (!des) {
        desMap.set(key, des = (new Set()))
    }
    // 面试题为什么用 set来收集副作用 ,避免收集重复的副作用
    des.add(activeEffect)
}

// 当代理对象的值发送改变,从targetMap找出副作用,然后执行它
export const trigger = (target,key) => {
    const desMap = targetMap.get(target)
    if (!desMap) return 
    const des = desMap.get(key)
    if (!des) return
    des.forEach(effect => {
        effect()
    });
}

utils/index.js

export function isObject(value) { 
return typeof value === 'object' && value !== null; 
} 
export function isFunction(value) { 
return typeof value === 'function'; 
}
export function isArray(value) { 
return Array.isArray(value); 
} 
export function hasChanged(value, oldValue) { 
return value !== oldValue && (value === value || oldValue === oldValue); 
}