vue3 effect实现

该文详细介绍了如何实现Vue.js中的effect功能,包括处理effect的嵌套和并排执行,以及通过映射表进行依赖收集,以解决属性和effect之间的多对多关系。文章通过activeEffect标记和WeakMap数据结构来管理effect的执行状态和依赖关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

effect 实现

需要考虑的场景

1.用 标识,在effect嵌套,多个并排effect的格式下判断是否为 effect内部的代理对象的变量
2.用 映射表,双向标记,完成依赖收集,解决 属性 和 effect 的 多对多的关系

实现结果


export function effect(fn) {// 副作用函数
    const _effect = new ReactiveEffect(fn)
    _effect.run() // 默认让响应式的effect执行
}
// 核心类
let activeEffect;
class ReactiveEffect {
    public active = true;
    public deps = [];//依赖收集项
    public parent = undefined;
    constructor(public fn) {}
    run() {
        if (!this.active) {
            return this.fn(); // 直接执行此函数即可
        }
        // 其他情况下 意味着是激活的状态
        // 1.针对 effect 嵌套的格式,使用标记判断是否为 effect内部的代理对象的变量
        try { // tree树父子关系
            this.parent = activeEffect;
            activeEffect = this;
            return this.fn() // 会取响应式的数据
        } finally { // 方法执行完 去掉标记
            activeEffect = this.parent
            this.parent = undefined
        }
        // 2.对于 多个effect并排的情况,要方便区别变量是否在effect内部
    }
}
// 映射表
const targetMap = new WeakMap()
export function track(target, key) {
    if (!activeEffect) {
        // 取值操作没有发生在effect中
        return;
    }
    debugger
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()));
    }
    let dep = depsMap.get(key);
    if(!dep){
        depsMap.set(key,(dep = new Set()))
    }
    let shouldTrack = !dep.has(activeEffect)
    if(shouldTrack){
        // 双向的标记
        // 一个属性可能对应多个 effect,一个effect 可能对应多个属性
        // 属性 和 effect 的关系时多对多
        dep.add(activeEffect);
        activeEffect.deps.push(dep);//依赖收集
    }
}

// 在getter 中触发 track 依赖收集 处理
get(target, key, receiver) {// 目标   键名   当前代理对象
     if (ReactiveFlags.IS_REACTIVE == key) {
        // 第一次 target[ReactiveFlags.IS_REACTIVE]时,未触发getter,当代理已代理对象,会触发已代理的 getter
        return true
     }
    track(target,key)
    return Reflect.get(target, key, receiver) // 处理了 this 问题
},

场景1

在effect嵌套格式

const state = reactive({name: 'jw', age: 30});
effect(() => {
   app.innerHTML = state.name + state.age
      effect(() => {
         app.innerHTML = state.name + state.age
      })
})

多个并排effect的格式

effect(() => {
   document.getElementById('app').innerHTML = state.name + state.age
})
effect(() => {
   document.getElementById('app').innerHTML = state.name + state.age
})

解决方案: parent 标识

// 1.针对 effect 嵌套的格式和多个effect并排的情况
// 使用 activeEffect 标记,判断是否为 effect内部的回调
try { // 方法执行时 加上 标记 为 当前 实例 类
    this.parent = activeEffect;
    activeEffect = this;
    return this.fn() // 会取响应式的数据
} finally { // 方法执行完,若在最外层则去掉标记
    activeEffect = this.parent
    this.parent = undefined
}

... 

if (!activeEffect) {
    // 取值操作没有发生在effect中
    return;
}

场景2

属性 和 effect 的 多对多的关系

// 一个 effect 对 name,age 属性
effect(() => {
    app.innerHTML = state.name + state.age
})
// 多个 effect 对 name 属性
effect(() => {
    document.getElementById('app').innerHTML = state.name
})
effect(() => {
    document.getElementById('app').innerHTML = state.name
})

解决方案:映射表

//let mapping = { // 映射表格式 
//     target: {
//         key: [activeEffect]
//     }
// }
// 映射表
const targetMap = new WeakMap() 
export function track(target, key) {
    if (!activeEffect) {
        // 取值操作没有发生在effect中
        return;
    }
    debugger
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()));
    }
    let dep = depsMap.get(key);
    if (!dep) {
        depsMap.set(key, (dep = new Set()))
    }
    let shouldTrack = !dep.has(activeEffect)
    if (shouldTrack) {
        // 双向的标记
        // 一个属性可能对应多个 effect,一个effect 可能对应多个属性
        // 属性 和 effect 的关系时多对多
        dep.add(activeEffect);
        activeEffect.deps.push(dep);//依赖收集
    }
}
### Vue 3 中 `effect` 的使用方法和实例 在 Vue 3 中,响应式系统的核心概念之一是依赖追踪和副作用函数 (side-effect function),通常称为 `effect` 函数。当一个副作用函数被标记为 `effect` 后,在其内部访问的任何响应式属性都会自动建立联系,一旦这些属性发生变化,副作用函数就会重新执行。 #### 创建 Effect 要创建一个 effect,可以使用 `effect()` 方法: ```javascript import { reactive, effect } from 'vue'; const state = reactive({ count: 0, }); // 定义并立即运行一次该效应函数 effect(() => { console.log(`count is ${state.count}`); }); ``` 每当 `state.count` 发生变化时,上述定义的日志语句将会再次打印新的计数值[^1]。 #### 停止 Effect 有时可能需要停止某个特定的 effect 不再跟踪更新。这可以通过返回值实现: ```javascript const myEffect = effect(() => { console.log('current value:', state.count); }); // 当不再需要此效果时调用 stop() myEffect.stop(); ``` 这样就阻止了后续对于 `state.count` 变更触发的日志记录操作。 #### 使用懒加载 Effects 默认情况下,传递给 `effect()` 的回调会立刻被执行。但是也可以通过设置 `{ lazy: true }` 参数让这个过程延迟发生: ```javascript const lazyEffect = effect( () => `Count squared is ${state.count * state.count}`, { lazy: true } ); console.log(lazyEffect()); // 手动触发计算 ``` 这种方式适合用于构建惰性求值逻辑或性能优化场景下减少不必要的即时运算开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柳晓黑胡椒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值