vue3源码学习(二)effect 源码学习

vue3.0源码学习(一) —— effect 源码学习

effect 源码学习回顾、复习。



vue3.x使用示例:

const state = reactive({
     name: '张三',
     nickName:'张三(书记)',
     age: 18,
     flag:false,
     n:{
         n:1
     }
 })
effect(() => {
   document.getElementById('app').innerHTML = `
        <h1>${state.flag ? state.nickName : state.name}</h1>
        <p>年龄: ${state.age}</p>
    `;
});

一、effect 函数

1、定义ReactiveEffect类

分析:effect 接收的是一个函数,且有调度函数scheduler,当初次加载和引用的数据发生改变会重新执行effect,且effect可以在同一个文件中多次使用。所以我们需要用类来抽象出来.。

class ReactiveEffect {
	public active = true //默认effect为失活状态
	constructor(public fn, public scheduler){} // 接收参数
	
	// 提供一个run方法
	run(){
		// 当失活状态则返回函数执行
		if(!this.active) return this.fn()
		
		return this.fn()
	}
}

2、实现 effect 函数

分析:effect第一个参数为一个函数,第二个参数为一个对象,effect可以多次使用,且effect可以返回一个对象单独调用effect内部的一些方法

export function effect (fn, options){
	const _effect = new ReactiveEffect(fn, options.scheduler)
	_effect .run() // 默认让响应式的effect执行一次
	const runner = _effect.run.bind(_effect)// 让用户可以手动调用effect
	runner.effect = _effect;  // 将effect挂载到runner上,用户可以通过runner拿到effect
	return runner;// 将effect返回,用户可以手动调用内部方法
}

二、依赖收集 track

1、依赖存储逻辑说明

分析: 在vue2.x和vue3的部分版本使用的方案是数组收集。在vue3高版本中使用了weakMap链路式收集依赖

//依赖收集数据结构示例:
/**
 *  targetMap {
 *      // target => object
 *      target:{ // new Map()
 *          // key => string
 *          key: [ // new Set
 *              activeEffect //effect
 *          ]
 *      }
 * }
 */
 为解决嵌套effect等情况加入parant和activeEffect

ReactiveEffect相关调整,以及定义全局activeEffect

export let activeEffect;
class ReactiveEffect {
    constructor(public fn, private scheduler){}
	public active = true// 是否激活
	public deps = [] // 用于存储依赖 需要知道effect依赖了哪些属性
	public parant = undefined; // 父级依赖 activeEffect
	
	run(){
		if(!this.active) return this.fn()
		// 示例:
        // let activeEffect = e2
        // effect(()=>{ //e1 e1.parent = null e1.deps = [name, address]
        //     name //e1
        //     effect(() => { e2 //e2.parent = e1
        //         age //e2
        //     })
        //     address // e1
        // })
        // 其他情况 意味着是激活状态
        try{ // 数据结构就是一个tree结构
            this.parant = activeEffect; // 将当前的effect作为父级依赖
            activeEffect = this; // 将当前的effect设置为activeEffect
            cleanpEffect(this); // 清空当前effect的依赖,避免重复收集
            return this.fn(); // 执行函数 这里做了依赖收集,收集当前的effect
        }finally{
            activeEffect = this.parant; // 恢复父级依赖
            this.parant = undefined; // 清空父级依赖
        }
	}
}

2、触发依赖收集

在响应式reactive将数据转换为响应式数据时触发依赖收集

get(target, key, receiver){
    // 因为触发了get,在外部传进来的key也是ReactiveFlags.IS_REACTIVE这样就达到了标识已经被代理过的逻辑
      if(key === ReactiveFlags.IS_REACTIVE) {
          return true; // 如果访问的是__v_isReactive属性,返回true
      }
      // return target[key]
      console.log('activeEffect', activeEffect, key);
      track(target, key) // 依赖收集,收集当前的effect

      const r = Reflect.get(target, key, receiver)
      if(isObject(r)){ // 只有用户取值才会二次代理
          return reactive(r)
      }

      // return Reflect.get(target, key, receiver) // 处理了this的指向问题
      // 通过Reflect来获取属性值, receiver是代理对象
      return r
  },

3、track 函数实现

按照既定数据结构收集依赖

/**
 *  targetMap {
 *      // target => object
 *      target:{ // new Map()
 *          // key => string
 *          key: [ // new Set
 *              activeEffect //effect
 *          ]
 *      }
 * }
 */

const targetMap = new WeakMap(); // 用WeakMap来存储依赖收集,key只能是对象
export function track(traget, key){
	if(!activeEffect) {
        // 说明没有在effect中执行所以不需要收集,或访问的不是响应式对象
        return;
    }
	
	let depsMap = targetMap.get(target); // 获取target对应的依赖收集
    if(!depsMap) {
        // 如果没有依赖收集,创建一个新的 这里的depsMap内存的是具体属性
        targetMap.set(target, depsMap = new Map());
    }
	
	let dep = depsMap.get(key) // 获取具体属性对应的依赖收集
	if(!dep){
		// 如果没有依赖收集,创建一个新的
		depsMap.set(key, dep = new Set())
	}
	
	// 将当前的effect添加到依赖收集
	if(!dep.has(activeEffect)){
		dep.add(activeEffect) // 添加当前的effect到依赖收集
		activeEffect.deps.push(activeEffect) // 将当前的依赖收集添加到activeEffect的deps中
	}
	 // 一个属性对应多个effect,一个effect对应多个属性; 多对多
}

三、触发更新 trigger

1、当set时触发trigger更新

在响应式reactive将数据转换为响应式数据时触发依赖收集

set(target, key, value, receiver){
        let oldValue = target[key] // 获取旧值

        // 通过Reflect来获取属性值, receiver是代理对象
        // set 方法的返回值是一个布尔值,表示是否设置成功
        let r = Reflect.set(target, key, value, receiver)

        if(oldValue !== value) {
            trigger(target, key, value, receiver) // 触发依赖
        }

        return r;
    }

2、track 函数实现

export function trigger(target, key, newVal, oldVal){
	 // weakMap { obj:key:set(effect)
    const depsMap = targetMap.get(target); // 获取target对应的依赖收集
	  if(!depsMap){ // 没有说明没有依赖,不需要更新
        return
    }
    const dep = depsMap.get(key); // 获取具体属性对应的依赖收集
	if(dep){
		let effects = [...dep]
		const effects = [...dep];
        effects.forEach(effect => {
            // 当我重新执行此effect时,将当前的effect放到全局上 activeEffect 上

            //这里的activeEffect是正在执行的effect
            // 因为activeEffect重新赋值是在finally中执行的,try中执行完成后才会执行finally
            // 所以当在effct中改变属性时,activeEffect还是正在执行的effect
            if(activeEffect != effect) {
                effect.run(); // 执行依赖收集中的effect
            }
        });
	}	
}

四、优化处理

1、cleanpEffect 避免重复收集

// 每次执行effect时,先清空当前effect的依赖的所有属性,避免重复收集
function cleanpEffect(effect) {
    // 每次执行effect时,先清空当前effect的依赖的所有属性,避免重复收集
    let { deps } = effect;
    // effect.deps.length = 0; // 只清空了数组,没有清空属性中的effect
    // 属性记录了effect effect {key: new Set([effect1, effect2])}
    for(let i = 0; i < deps.length; i++) {
        deps[i].delete(effect); // 将属性中的effect删除
    }
    effect.deps.length = 0; // 清空数组
}

// run 函数中加入清空逻辑
run () {
        if(!this.active) {
            return this.fn(); // 如果不激活,直接执行函数
        }
        // 其他情况 意味着是激活状态
        try{ // 数据结构就是一个tree结构
            this.parant = activeEffect; // 将当前的effect作为父级依赖
            activeEffect = this; // 将当前的effect设置为activeEffect
            cleanpEffect(this); // 清空当前effect的依赖,避免重复收集
            return this.fn(); // 执行函数 这里做了依赖收集,收集当前的effect
        }finally{
            activeEffect = this.parant; // 恢复父级依赖
            this.parant = undefined; // 清空父级依赖
        }
    }

2、加入失活stop

stop(){
    if(this.active){
        cleanpEffect(this); // 停止时,清空当前effect的依赖
        this.active = false;
    }
}

五、调度函数

  • trigger 中加入调度函数处理
export function trigger(target, key, newVal, oldVal){
    // weakMap { obj:key:set(effect)
    const depsMap = targetMap.get(target);

    if(!depsMap){
        return
    }
    const dep = depsMap.get(key); 
    if(dep) {
        const effects = [...dep];
        effects.forEach(effect => {
            if(activeEffect != effect) {
                if(effect.scheduler){
                    effect.scheduler()
                }else{
                    effect.run(); // 执行依赖收集中的effect
                }
            }
        });
    }
}

六、完整代码

import { reactive, ReactiveFlags } from "./reactive";
import { activeEffect, track, trigger } from './effect'
import { isObject } from "@vue/shared";

export const mutableHandlers = {
    get(target, key, receiver){
        // 因为触发了get,在外部传进来的key也是ReactiveFlags.IS_REACTIVE这样就达到了标识已经被代理过的逻辑
        if(key === ReactiveFlags.IS_REACTIVE) {
            return true; // 如果访问的是__v_isReactive属性,返回true
        }
        // return target[key]
        console.log('activeEffect', activeEffect, key);
        track(target, key) // 依赖收集,收集当前的effect

        const r = Reflect.get(target, key, receiver)
        if(isObject(r)){ // 只有用户取值才会二次代理
            return reactive(r)
        }

        // return Reflect.get(target, key, receiver) // 处理了this的指向问题
        // 通过Reflect来获取属性值, receiver是代理对象
        return r
    },
    set(target, key, value, receiver){
        // target[key] = value
        // return true
        let oldValue = target[key] // 获取旧值

        // 通过Reflect来获取属性值, receiver是代理对象
        // set 方法的返回值是一个布尔值,表示是否设置成功
        let r = Reflect.set(target, key, value, receiver)

        if(oldValue !== value) {
            trigger(target, key, value, receiver) // 触发依赖
        }

        return r;
    }
}

// 1.将属性和运行的effect关联起来
// 2.当属性变化时,重新执行effect, effect存在嵌套执行的情况
// 3.停止监听
// 4.外部调用
// 5.避免重复收集
// 6.scheduler调度函数


// 每次执行effect时,先清空当前effect的依赖的所有属性,避免重复收集
function cleanpEffect(effect) {
    // 每次执行effect时,先清空当前effect的依赖的所有属性,避免重复收集
    let { deps } = effect;
    // effect.deps.length = 0; // 只清空了数组,没有清空属性中的effect
    // 属性记录了effect effect {key: new Set([effect1, effect2])}
    for(let i = 0; i < deps.length; i++) {
        deps[i].delete(effect); // 将属性中的effect删除
    }
    effect.deps.length = 0; // 清空数组
}

/**
 * effect(()=>{ //effect1
 *  name;
 *  effect(()=>{//effect2
 *    age;
 *  })
 *  address
 * })
 *
 * vue2/vue3早期实现方案为栈 [effect1, effect2]
 * vue3 后续版本实现方案为打标记 parentEffectActive
 * 1.在执行effect时,先将当前的EffectActive存储起来
 * 2.执行完effect后,再将EffectActive恢复
 * 3.在effect中执行依赖收集时,使用EffectActive作为当前的依赖
 * 4.在属性变化时,使用EffectActive作为当前的依赖
 */

export let activeEffect;
class ReactiveEffect{
    public active = true; // 是否激活
    public deps = []; // 用于存储依赖 需要知道effect依赖了哪些属性
    public parant = undefined; // 父级依赖 activeEffect
    // public fn
    // constructor(fn) {
    //     this.fn = fn
    // }
    // 等价于上面
    constructor(public fn, private scheduler) {
        // 将当前对象的父级依赖赋值给 EffectActive
    }

    run () {
        if(!this.active) {
            return this.fn(); // 如果不激活,直接执行函数
        }

        // 示例:
        // let activeEffect = e2
        // effect(()=>{ //e1 e1.parent = null e1.deps = [name, address]
        //     name //e1
        //     effect(() => { e2 //e2.parent = e1
        //         age //e2
        //     })
        //     address // e1
        // })
        // 其他情况 意味着是激活状态
        try{ // 数据结构就是一个tree结构
            this.parant = activeEffect; // 将当前的effect作为父级依赖
            activeEffect = this; // 将当前的effect设置为activeEffect
            cleanpEffect(this); // 清空当前effect的依赖,避免重复收集
            console.log('try');
            return this.fn(); // 执行函数 这里做了依赖收集,收集当前的effect
        }finally{
            console.log('finally');
            activeEffect = this.parant; // 恢复父级依赖
            this.parant = undefined; // 清空父级依赖
        }
    }
    stop(){
        if(this.active){
            cleanpEffect(this); // 停止时,清空当前effect的依赖
            this.active = false;
        }
    }
}

// 依赖收集就是 将当前的effect变成全局的 稍后取值的时候就可以拿到这个全局的effect
export function effect (fn, options={}) {
    const _effect = new ReactiveEffect(fn, options.scheduler)

    _effect.run(); // 默认让响应式的effect执行一次
    const runner = _effect.run.bind(_effect); // 让用户可以手动调用effect
    runner.effect = _effect; // 将effect挂载到runner上,用户可以通过runner拿到effect
    return runner; // 将effect返回,用户可以手动调用stop方法
}


// 依赖收集数据结构
// 依赖收集的核心就是将当前的effect和属性关联起来
// let mapping = {
//     target:{
//         name:[activeEffect1, activeEffect2],
//     }
// }

const targetMap = new WeakMap(); // 用WeakMap来存储依赖收集,key只能是对象
export function track(target,  key){
    if(!activeEffect) {
        // 说明没有在effect中执行所以不需要收集,或访问的不是响应式对象
        return;
    }
    let depsMap = targetMap.get(target); // 获取target对应的依赖收集
    if(!depsMap) {
        // 如果没有依赖收集,创建一个新的 这里的depsMap内存的是具体属性
        targetMap.set(target, depsMap = new Map());
    }

    let dep = depsMap.get(key) // 获取具体属性对应的依赖收集
    if(!dep) {
        // 如果没有依赖收集,创建一个新的
        depsMap.set(key, dep = new Set());
    }

    // 将当前的effect添加到依赖收集
    let shouldTrack = dep.has(activeEffect)
    if(!shouldTrack){
        dep.add(activeEffect); // 添加当前的effect到依赖收集
        activeEffect.deps.push(dep); // 将当前的依赖收集添加到activeEffect的deps中
    }
    // 一个属性对应多个effect,一个effect对应多个属性; 多对多
}

// 触发更新
export function trigger(target, key, newVal, oldVal){
    // weakMap { obj:key:set(effect)
    const depsMap = targetMap.get(target); // 获取target对应的依赖收集

    if(!depsMap){
        return
    }
    const dep = depsMap.get(key); // 获取具体属性对应的依赖收集
    if(dep) {
        const effects = [...dep];
        effects.forEach(effect => {
            // 当我重新执行此effect时,将当前的effect放到全局上 activeEffect 上

            //这里的activeEffect是正在执行的effect
            // 因为activeEffect重新赋值是在finally中执行的,try中执行完成后才会执行finally
            // 所以当在effct中改变属性时,activeEffect还是正在执行的effect
            if(activeEffect != effect) {
                if(effect.scheduler){
                    effect.scheduler()
                }else{
                    effect.run(); // 执行依赖收集中的effect
                }
            }
        });
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

白开水丶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值