广告位联系
返回顶部
分享到

vue3中的响应式原理-effect介绍

JavaScript 来源:互联网 作者:F11站长开发者 发布时间:2022-08-13 07:36:39 人浏览
摘要

effect的基本实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 export let activeEffect = undefined;// 当前正在执行的effect class ReactiveEffect { active = true; deps = []; // 收集effect中使用到的属性 pa

effect的基本实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

export let activeEffect = undefined;// 当前正在执行的effect

class ReactiveEffect {

    active = true;

    deps = []; // 收集effect中使用到的属性

    parent = undefined;

    constructor(public fn) { }

    run() {

        if (!this.active) { // 不是激活状态

            return this.fn();

        }

        try {

            this.parent = activeEffect; // 当前的effect就是他的父亲

            activeEffect = this; // 设置成正在激活的是当前effect

            return this.fn();

        } finally {

            activeEffect = this.parent; // 执行完毕后还原activeEffect

            this.parent = undefined;

        }

    }

}

export function effect(fn, options?) {

    const _effect = new ReactiveEffect(fn); // 创建响应式effect

    _effect.run(); // 让响应式effect默认执行

}

依赖收集

1

2

3

4

5

6

7

8

get(target, key, receiver) {

    if (key === ReactiveFlags.IS_REACTIVE) {

        return true;

    }

    const res = Reflect.get(target, key, receiver);

    track(target, 'get', key);  // 依赖收集

    return res;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

const targetMap = new WeakMap(); // 记录依赖关系

export function track(target, type, key) {

    if (activeEffect) {

        let depsMap = targetMap.get(target); // {对象:map}

        if (!depsMap) {

            targetMap.set(target, (depsMap = new Map()))

        }

        let dep = depsMap.get(key);

        if (!dep) {

            depsMap.set(key, (dep = new Set())) // {对象:{ 属性 :[ dep, dep ]}}

        }

        let shouldTrack = !dep.has(activeEffect)

        if (shouldTrack) {

            dep.add(activeEffect);

            activeEffect.deps.push(dep); // 让effect记住dep,这样后续可以用于清理

        }

    }

}

将属性和对应的effect维护成映射关系,后续属性变化可以触发对应的effect函数重新run

触发更新

1

2

3

4

5

6

7

8

9

set(target, key, value, receiver) {

    // 等会赋值的时候可以重新触发effect执行

    let oldValue = target[key]

    const result = Reflect.set(target, key, value, receiver);

    if (oldValue !== value) {

        trigger(target, 'set', key, value, oldValue)

    }

    return result;

}

1

2

3

4

5

6

7

8

9

10

export function trigger(target, type, key?, newValue?, oldValue?) {

    const depsMap = targetMap.get(target); // 获取对应的映射表

    if (!depsMap) {

        return

    }

    const effects = depsMap.get(key);

    effects && effects.forEach(effect => {

        if (effect !== activeEffect) effect.run(); // 防止循环

    })

}

分支切换与cleanup

在渲染时我们要避免副作用函数产生的遗留

1

2

3

4

5

6

7

8

9

10

11

12

const state = reactive({ flag: true, name: 'jw', age: 30 })

effect(() => { // 副作用函数 (effect执行渲染了页面)

    console.log('render')

    document.body.innerHTML = state.flag ? state.name : state.age

});

setTimeout(() => {

    state.flag = false;

    setTimeout(() => {

        console.log('修改name,原则上不更新')

        state.name = 'zf'

    }, 1000);

}, 1000)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

function cleanupEffect(effect) {

    const { deps } = effect; // 清理effect

    for (let i = 0; i < deps.length; i++) {

        deps[i].delete(effect);

    }

    effect.deps.length = 0;

}

class ReactiveEffect {

    active = true;

    deps = []; // 收集effect中使用到的属性

    parent = undefined;

    constructor(public fn) { }

    run() {

        try {

            this.parent = activeEffect; // 当前的effect就是他的父亲

            activeEffect = this; // 设置成正在激活的是当前effect

+           cleanupEffect(this);

            return this.fn(); // 先清理在运行

        }

    }

}

这里要注意的是:触发时会进行清理操作(清理effect),在重新进行收集(收集effect)。在循环过程中会导致死循环。

1

2

3

let effect = () => {};

let s = new Set([effect])

s.forEach(item=>{s.delete(effect); s.add(effect)}); // 这样就导致死循环了

停止effect

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

export class ReactiveEffect {

    stop(){

        if(this.active){ 

            cleanupEffect(this);

            this.active = false

        }

    }

}

export function effect(fn, options?) {

    const _effect = new ReactiveEffect(fn); 

    _effect.run();

    const runner = _effect.run.bind(_effect);

    runner.effect = _effect;

    return runner; // 返回runner

}

调度执行

trigger触发时,我们可以自己决定副作用函数执行的时机、次数、及执行方式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

export function effect(fn, options:any = {}) {

    const _effect = new ReactiveEffect(fn,options.scheduler); // 创建响应式effect

    // if(options){

    //     Object.assign(_effect,options); // 扩展属性

    // }

    _effect.run(); // 让响应式effect默认执行

    const runner = _effect.run.bind(_effect);

    runner.effect = _effect;

    return runner; // 返回runner

}

export function trigger(target, type, key?, newValue?, oldValue?) {

    const depsMap = targetMap.get(target);

    if (!depsMap) {

        return

    }

    let effects = depsMap.get(key);

    if (effects) {

        effects = new Set(effects);

        for (const effect of effects) {

            if (effect !== activeEffect) { 

                if(effect.scheduler){ // 如果有调度函数则执行调度函数

                    effect.scheduler()

                }else{

                    effect.run(); 

                }

            }

        }

    }

}

深度代理 

1

2

3

4

5

6

7

8

9

10

11

12

get(target, key, receiver) {

    if (key === ReactiveFlags.IS_REACTIVE) {

        return true;

    }

    // 等会谁来取值就做依赖收集

    const res = Reflect.get(target, key, receiver);

    track(target, 'get', key);

    if(isObject(res)){

        return reactive(res);

    }

    return res;

}

当取值时返回的值是对象,则返回这个对象的代理对象,从而实现深度代理

总结

为了实现响应式,我们使用了new Proxy

effect默认数据变化要能更新,我们先将正在执行的effect作为全局变量,渲染(取值),然后在get方法中进行依赖收集

依赖收集的数据格式weakMap(对象:map(属性:set(effect))

用户数据发生变化,会通过对象属性来查找对应的effect集合,全部执行;

调度器的实现,创建effect时,把scheduler存在实例上,调用runner时,判断如果有调度器就调用调度器,否则执行runner


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://blog.csdn.net/gudeng007/article/details/123974062
相关文章
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计