响应式实现vue2和vue3

响应式实现

  1. 实现响应式原理,需要知道数据被那些函数依赖
  2. 数据改变的时候去调用依赖他的函数

vue2

重写get和set方法,在调用数据的时候,将依赖进行收集,将依赖的函数执行

import Dep from "./dep";
export class Observer {
    constructor(value) {
        this.walk(value);
    }

    /**
     * 遍历对象所有的属性,调用 defineReactive
     * 拦截对象属性的 get 和 set 方法
     */
    walk(obj) {
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i]);
        }
    }
}
/**
 * Define a reactive property on an Object.
 */
export function defineReactive(obj, key, val) {
    const property = Object.getOwnPropertyDescriptor(obj, key);
    // 读取用户可能自己定义了的 get、set
    const getter = property && property.get;
    const setter = property && property.set;
    // val 没有传进来话进行手动赋值
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key];
    }

    /*********************************************/
    const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher
    /*********************************************/

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val;
            /*********************************************/
            // 1.这里需要去保存当前在执行的函数
            if (Dep.target) {
                dep.depend();
            }
            /*********************************************/
            return value;
        },
        set: function reactiveSetter(newVal) {
            if (setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }
            /*********************************************/
            // 2.将依赖当前数据依赖的函数执行
            dep.notify();
            /*********************************************/
        },
    });
}

将第一步和第二步的东西封装为dep

export default class Dep {
    static target; //当前在执行的函数
    subs; // 依赖的函数
    constructor() {
        this.subs = []; // 保存所有需要执行的函数
    }

    addSub(sub) {
        this.subs.push(sub);
    }

    depend() {
        // 触发 get 的时候走到这里
        if (Dep.target) {
            // 委托给 Dep.target 去调用 addSub
            Dep.target.addDep(this);
        }
    }

    notify() {
        for (let i = 0, l = this.subs.length; i < l; i++) {
            this.subs[i].update();
        }
    }
}

Dep.target = null; // 静态变量,全局唯一

保存当前正在执行的函数

import Dep from "./dep";
export default class Watcher {
    constructor(Fn) {
        this.getter = Fn;
        this.get();
    }

    /**
     * Evaluate the getter, and re-collect dependencies.
     */
    get() {
        Dep.target = this; // 保存包装了当前正在执行的函数的 Watcher
        let value;
        try {
          	// 调用当前传进来的函数,触发对象属性的 get
            value = this.getter.call();
        } catch (e) {
            throw e;
        }
        return value;
    }

    /**
     * Add a dependency to this directive.
     */
    addDep(dep) {
      	// 触发 get 后会走到这里,收集当前依赖
        // 当前正在执行的函数的 Watcher 保存到 dep 中的 subs 中
        dep.addSub(this);
    }

    /**
     * Subscriber interface.
     * Will be called when a dependency changes.
     */
  	// 修改对象属性值的时候触发 set,走到这里
    update() {
        this.run();
    }

    /**
     * Scheduler job interface.
     * Will be called by the scheduler.
     */
    run() {
        this.get();
    }
}

每个属性有一个subs数组,watcher会保存当前正在执行的函数,当读取属性的时候触发get,将当前watcher保存到subs数组中。
这里需要考虑两件事情,1.Dep中不要重复收集watcher, 2.重置,如果该属性对dep中的watcher没有影响,watcher中的updateComponent不会获得属性,需要将watcher将dep中删除

  • 去重:
    • 方案1: Dep中的subs数组换为set
    • 方案2: Dep对象中引入id,Watcher对象中记录所有的Dep的id,下次重新收集的时候,如果dep中的id已经存在,就不再收集该watcher了
    /*************改动***************************/
    let uid = 0;
    /****************************************/
    export default class Dep {
        static target; //当前在执行的函数
        subs; // 依赖的函数
      	id; // Dep 对象标识
        constructor() {
          /**************改动**************************/
            this.id = uid++;
          /****************************************/
            this.subs = []; // 保存所有需要执行的函数
        }
    
        addSub(sub) {
            this.subs.push(sub);
        }
        depend() {
            if (Dep.target) {
                // 委托给 Dep.target 去调用 addSub
                Dep.target.addDep(this);
            }
        }
    
        notify() {
            for (let i = 0, l = this.subs.length; i < l; i++) {
                this.subs[i].update();
            }
        }
    }
    
    Dep.target = null; // 静态变量,全局唯一
    
    import Dep from "./dep";
    export default class Watcher {
      constructor(Fn) {
        this.getter = Fn;
        /*************改动***************************/
        this.depIds = new Set(); // 拥有 has 函数可以判断是否存在某个 id
        /****************************************/
        this.get();
      }
    
      /**
         * Evaluate the getter, and re-collect dependencies.
         */
      get() {
        Dep.target = this; // 保存包装了当前正在执行的函数的 Watcher
        let value;
        try {
          value = this.getter.call();
        } catch (e) {
          throw e;
        } finally {
          this.cleanupDeps();
        }
        return value;
      }
    
      /**
         * Add a dependency to this directive.
         */
      addDep(dep) {
        /*************改动***************************/
        const id = dep.id;
        if (!this.depIds.has(id)) {
          dep.addSub(this);
        }
        /****************************************/
    
      }
    
      /**
         * Subscriber interface.
         * Will be called when a dependency changes.
         */
      update() {
        this.run();
      }
    
      /**
         * Scheduler job interface.
         * Will be called by the scheduler.
         */
      run() {
        this.get();
      }
    }
    
  • 重置
    • 全量式移除,保证watcher所影响的所有dep对象,当重新收集watcher之前,把watcher从记录中所有dep对象移除
    • 增量式移除,重新收集依赖的时候,用一个新的变量记录所有dep对象,之后在和旧的dep对象列表对比,如果新的没有,旧的中有,就将当前watcher从该dep对象中移除
    import { remove } from "./util";
    /*
    export function remove(arr, item) {
        if (arr.length) {
            const index = arr.indexOf(item);
            if (index > -1) {
                return arr.splice(index, 1);
            }
        }
    }
    */
    let uid = 0;
    
    export default class Dep {
        static target; //当前在执行的函数
        subs; // 依赖的函数
        id; // Dep 对象标识
        constructor() {
            this.id = uid++;
            this.subs = []; // 保存所有需要执行的函数
        }
    		
        addSub(sub) {
            this.subs.push(sub);
        }
      /*************新增************************/
        removeSub(sub) {
            remove(this.subs, sub);
        }
      /****************************************/
        depend() {
            if (Dep.target) {
                // 委托给 Dep.target 去调用 addSub
                Dep.target.addDep(this);
            }
        }
    
        notify() {
            for (let i = 0, l = this.subs.length; i < l; i++) {
                this.subs[i].update();
            }
        }
    }
    
    Dep.target = null; // 静态变量,全局唯一
    
    
    在watcher类中,引入this.deps来保存所有旧的dep对象,引入this.newDeps来保存所有新的Dep对象
    import Dep from "./dep";
    export default class Watcher {
        constructor(Fn) {
            this.getter = Fn;
            this.depIds = new Set(); // 拥有 has 函数可以判断是否存在某个 id
          	/*************新增************************/
            this.deps = [];
            this.newDeps = []; // 记录新一次的依赖
            this.newDepIds = new Set();
          	/****************************************/
            this.get();
        }
    
        /**
         * Evaluate the getter, and re-collect dependencies.
         */
        get() {
            Dep.target = this; // 保存包装了当前正在执行的函数的 Watcher
            let value;
            try {
                value = this.getter.call();
            } catch (e) {
                throw e;
            } finally {
              	/*************新增************************/
                this.cleanupDeps();
              	/****************************************/
            }
            return value;
        }
    
        /**
         * Add a dependency to this directive.
         */
        addDep(dep) {
            const id = dep.id;
          /*************新增************************/
            // 新的依赖已经存在的话,同样不需要继续保存
            if (!this.newDepIds.has(id)) {
                this.newDepIds.add(id);
                this.newDeps.push(dep);
                if (!this.depIds.has(id)) {
                    dep.addSub(this);
                }
            }
          /****************************************/
        }
    
        /**
         * Clean up for dependency collection.
         */
      	/*************新增************************/
        cleanupDeps() {
            let i = this.deps.length;
            // 比对新旧列表,找到旧列表里有,但新列表里没有,来移除相应 Watcher
            while (i--) {
                const dep = this.deps[i];
                if (!this.newDepIds.has(dep.id)) {
                    dep.removeSub(this);
                }
            }
    
            // 新的列表赋值给旧的,新的列表清空
            let tmp = this.depIds;
            this.depIds = this.newDepIds;
            this.newDepIds = tmp;
            this.newDepIds.clear();
            tmp = this.deps;
            this.deps = this.newDeps;
            this.newDeps = tmp;
            this.newDeps.length = 0;
        }
      	/****************************************/
        /**
         * Subscriber interface.
         * Will be called when a dependency changes.
         */
        update() {
            this.run();
        }
    
        /**
         * Scheduler job interface.
         * Will be called by the scheduler.
         */
        run() {
            this.get();
        }
    }
    
    

当vue开发中存在组件嵌套组件的情况

import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
    text: "hello, world",
    inner: "内部",
};
observe(data);

const updateMyComponent = () => {
    console.log("子组件收到:", data.inner);
};

const updateParentComponent = () => {
    new Watcher(updateMyComponent);
    console.log("父组件收到:", data.text);
};

new Watcher(updateParentComponent);

data.text = "hello, liang";

new watcher首先保存当前函数,然后执行当前函数将全局的Dep.target赋值为watcher对象。但是当出现组件嵌套的时候,会出现bug。
这里使用栈来保存watcher。执行函数将当前watcher入栈,执行函数完毕之后将watcher弹出栈,Dep.target始终指向栈顶watcher,代表当前执行的函数

const targetStack = [];

export function pushTarget(target) {
    targetStack.push(target);
    Dep.target = target;
}

export function popTarget() {
    targetStack.pop();
    Dep.target = targetStack[targetStack.length - 1]; // 赋值为栈顶元素
import { pushTarget, popTarget } from "./dep";
export default class Watcher {
    constructor(Fn) {
        this.getter = Fn;
        this.depIds = new Set(); // 拥有 has 函数可以判断是否存在某个 id
        this.deps = [];
        this.newDeps = []; // 记录新一次的依赖
        this.newDepIds = new Set();
        this.get();
    }

    /**
     * Evaluate the getter, and re-collect dependencies.
     */
    get() {
      /************修改的地方*******************************/
        pushTarget(this); // 保存包装了当前正在执行的函数的 Watcher
       /*******************************************/
        let value;
        try {
            value = this.getter.call();
        } catch (e) {
            throw e;
        } finally {
          /************修改的地方*******************************/
            popTarget();
          /*******************************************/
            this.cleanupDeps();
        }
        return value;
    }
   ...
}
import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
    text: "hello, world",
};
observe(data);
let show = true;
const updateComponent = () => {
    if (show) {
        console.log(data.text);
        show = false;
    }
};

new Watcher(updateComponent);

new Watcher(() => console.log("依赖", data.text));

data.text = "123";

会出现报错,报错为cannot read properties of undefined.
这个watcher不依赖当前的splice方法,直接将dep中的subs数组元素进行删除。但是现在正在遍历sub数组

notify() {
	for(let i = 0, l = this.subs.length; i < 1; i++) {
		this.subs[i].update
	}
}

将notify进行修改,就可以改正

notify() {
	const subs = this.subs.slice()
	for(let i = 0, l = subs.length; i < l; i++) {
		subs[i].update()
	}
}

vue3

vue3响应式原理

ref: 接受一个内部值,返回一个响应式的,可以更改的ref对象,该对象只有一个指向其内部值的属性.value。主要是用来定义简单类型的数据比如字符串,数字,布尔值等单一的对象

const count = ref(0)
console.log(count.value) // 0

reactive(): 创建一个响应式对象,用于包装JavaScript对象和数组等复杂类型的数据。

const obj = reactive({ count: 0})
obj.count++

ref解包

const count = ref(1)
const obj = reactive({count})
console.log(obj.count === count.value) // true
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)
const count = ref(1)
const obj = reactive({})
obj.count = count

重写get和set方法,在调用数据的时候,将依赖的函数执行,这里使用的是proxy代理函数

reactive

// 源对象和代理对象的映射表。原对象和代理对象关联起来
// 对象是响应式对象
export const reactiveMap = new WeakMap();
// 对象是只读对象
export const readonlyMap = new WeakMap();
// 对象是浅代理对象
export const shallowReadonlyMap = new WeakMap();
export function reactive(target) {
  return createReactiveObject(target, reactiveMap, mutableHandlers);
}
function createReactiveObject(target, proxyMap, baseHandlers) {
  // 核心就是 proxy
  // 目的是可以侦听到用户 get 或者 set 的动作
  // 如果命中的话就直接返回就好了
  // 使用缓存做的优化点
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  const proxy = new Proxy(target, baseHandlers);
  // 把创建好的 proxy 给存起来,
  proxyMap.set(target, proxy);
  return proxy;
}

这里稍微等下,下章继续,先看ref吧。

ref

function convert(value) {
  return isObject(value) ? reactive(value) : value;
}
export function ref(value) {
  return createRef(value);
}
function createRef(value) {
  const refImpl = new RefImpl(value);
  return refImpl;
}
export class RefImpl {
	...
  constructor(value) {
    this._rawValue = value;
    this._value = convert(value);
  }
  get value() {
  	...
    return this._value;
  }

  set value(newValue) {
    // 当新的值不等于老的值的话,
    // 那么才需要触发依赖
    if (hasChanged(newValue, this._rawValue)) {
      // 更新值
      this._value = convert(newValue);
      this._rawValue = newValue;
      ...
    }
  }
}

依赖收集和依赖的函数执行,这里需要封装函数
依赖收集trackRefValue
设置一个属性dep,存储依赖。
这里上面就是用的收集依赖的方案一,使用set方案收集依赖。这样就可以防止重复收集。

  get value() {
    // 收集依赖
    trackRefValue(this); // 依赖收集
    return this._value;
  }
export class RefImpl {
	public dep // 存储依赖
	public __v_isRef = true // 表示为ref对象
	constructor(value) {
		this.dep = createDep()
	}
}
// 这里的set对应的是vue2中的dep中的sub,但是vue2使用id来实现的,但是vue3使用的是es6中的set
export function createDep(effects?) {
  const dep = new Set(effects);
  return dep;
}
export function trackRefValue(ref) {
  if (shouldTrack && activeEffect) {
    // ref = toRaw(ref)
    trackEffect(
      activeEffect,	
      ref.dep
    )
  }
}
export function trackEffect(
  effect: ReactiveEffect,
  dep: Dep,
) {
	// ...
	dep.set(effect, effect._trackId)
}

依赖执行triggerRefValue

set value(newValue) {
    // 当新的值不等于老的值的话,那么才需要触发依赖
	if (hasChanged(newValue, this._rawValue)) {
      // 更新值
      this._value = convert(newValue);
      this._rawValue = newValue;
      // 触发依赖
      triggerRefValue(this, newVal, oldValue);
    }
}
export function triggerRefValue(ref) {
  ref = toRaw(ref)
  const dep = ref.dep
  if (dep) {
    triggerEffects(
      dep,
      dirtyLevel,
      __DEV__
        ? {
            target: ref,
            type: TriggerOpTypes.SET,
            key: 'value',
            newValue: newVal,
            oldValue: oldVal,
          }
        : void 0,
    )
  }
  triggerEffects(ref.dep);
}
export function triggerEffects(dep) {
  // 执行收集到的所有的 effect 的 run 方法
  for (const effect of dep) {	
  	effect.trigger()
  }
}

重置: 增量式移除,重新收集依赖的时候,用一个新的变量记录所有的dep,然后在旧的dep对象进行对比,如果新的没有,旧的中有,将当前effect移除,这一块也解决了上述vue2中最后一个bug:

  // 检查当前副作用是否已经被这个依赖跟踪过  
  // 这里使用 effect._trackId 作为副作用的唯一标识符  
export function trackEffect(
  effect: ReactiveEffect,
  dep: Dep,
) {
	// 如果没有被跟踪过
  if (dep.get(effect) !== effect._trackId) {
  	// 将给副作用与依赖关联起来
    dep.set(effect, effect._trackId)
    // 获取旧的依赖
    const oldDep = effect.deps[effect._depsLength]
    // 旧的依赖不是当前依赖
    if (oldDep !== dep) {
      if (oldDep) {
      	// 执行旧的依赖
        cleanupDepEffect(oldDep, effect)
      }
      // 将新的dep添加到副作用的依赖列表中
      effect.deps[effect._depsLength++] = dep
    } else {
    	// 如果旧的依赖是我们要添加的dep,只需要简单的添加_depsLength
      effect._depsLength++
    }
  }
}
function cleanupDepEffect(dep: Dep, effect: ReactiveEffect) {
  const trackId = dep.get(effect)
  if (trackId !== undefined && effect._trackId !== trackId) {
    dep.delete(effect)
    if (dep.size === 0) {
      dep.cleanup()
    }
  }
}

shallowRef: ref的浅层作用形式,shallowRef不会对对象进行深度的响应式处理,也就是shallowRef包含的对象内部的属性发生变化的时候,shallowRef本身不会触发重新渲染或响应式更新。

effect

export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions,
): ReactiveEffectRunner {
  const _effect = new ReactiveEffect(fn, NOOP, () => {
    if (_effect.dirty) {
      _effect.run()
    }
  })
  if (options) {
    extend(_effect, options)
    if (options.scope) recordEffectScope(_effect, options.scope)
  }
  if (!options || !options.lazy) {
    _effect.run()
  }
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}
export class ReactiveEffect<T = any> {
  active = true // 当前effect实例是否在工作,会调用stop就会停止工作
  deps: Dep[] = []
  constructor(
    public fn: () => T,
    public trigger: () => void,
    public scheduler?: EffectScheduler,
    scope?: EffectScope,
  ) {
    recordEffectScope(this, scope)
  }

  public get dirty() {
  	...
  }

  public set dirty(v) {
  	...
  }

  run() {
    this._dirtyLevel = DirtyLevels.NotDirty
    if (!this.active) {
      return this.fn()  // 执行依赖收集
    }
    let lastShouldTrack = shouldTrack
    let lastEffect = activeEffect
    try {
      shouldTrack = true
      activeEffect = this
      this._runnings++
      preCleanupEffect(this)
      return this.fn()
    } finally {
      postCleanupEffect(this)
      this._runnings--
      activeEffect = lastEffect
      shouldTrack = lastShouldTrack
    }
  }

  stop() {
  	...
  }
}

这个run作用就是用来执行副作用函数fn,并且在执行过程中进行依赖收集

当vue开发中存在组件嵌套组件的情况

在上面的vue2中,使用的是栈。但是在vue3中的实现为如下

this._dirtyLevel = DirtyLevels.NotDirty
if (!this.active) {
  return this.fn()
}
let lastShouldTrack = shouldTrack
let lastEffect = activeEffect
try {
  shouldTrack = true
  activeEffect = this
  this._runnings++
  preCleanupEffect(this)
  return this.fn()
} finally {
  postCleanupEffect(this)
  this._runnings--
  activeEffect = lastEffect
  shouldTrack = lastShouldTrack
}
function preCleanupEffect(effect: ReactiveEffect) {
  effect._trackId++
  effect._depsLength = 0
}
function postCleanupEffect(effect: ReactiveEffect) {
  if (effect.deps.length > effect._depsLength) {
    for (let i = effect._depsLength; i < effect.deps.length; i++) {
      cleanupDepEffect(effect.deps[i], effect)
    }
    effect.deps.length = effect._depsLength
  }
}
function cleanupDepEffect(dep: Dep, effect: ReactiveEffect) {
  const trackId = dep.get(effect)
  if (trackId !== undefined && effect._trackId !== trackId) {
    dep.delete(effect)
    if (dep.size === 0) {
      dep.cleanup()
    }
  }
}

如果当前effect实例是不工作状态,就仅仅执行一下fn,则不需要收集依赖。由于在一个effect.run的过程中可能会触发另外的effect.run,暂存上一次的activeEffect, shouldTrack, 目的是为了把本次执行完成之后的activeEffect,shouldTrack恢复回去。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值