依赖收集effect.ts(副作用函数)
- 将模板转换的render函数放置在effect函数内,则实现了数据响应式。
流程简述
- 通过
reactive
创建响应式数据
- 在模板中使用该数据(访问触发Proxy.get),并将该模板转化为
render
渲染函数置于effect
副作用函数中。
- 依赖收集:将正在执行的effect函数抛出为
全局变量
,并在渲染时通过响应式数据的get方法进行依赖收集绑定
。
- effect与响应式对象的属性
互成依赖
,两者为多对多关系
。
- 一个effect可收集多个属性为依赖。存储对象
deps
。
- 一个属性可存在于多个
effect
中。存储对象weakMap:(对象:map(属性:set(effect)))
。
- 最后,当响应式数据改变时,根据依赖找到对应的
effect函数=>effect(render())
执行即可。
- 全流程均处于同步状态,精心设计利用了单线程进行依赖绑定。
核心:副作用函数effect
- effect -- 副作用函数,如果此函数依赖的数据发生变化,则重新执行该函数(类似computed)
export function effect(fn, options: any = {}) {
const _effect = new ReactiveEffect(fn, options.scheduler);
_effect.run();
const runner = _effect.run.bind(_effect);
runner._effect = _effect;
return runner;
}
响应式类 - ReactiveEffect
export let activeEffect = undefined;
export class ReactiveEffect {
public active = true;
public parent = undefined;
public deps = [];
constructor(public fn, public scheduler?) {}
run() {
if (!this.active) {
this.fn();
}
try {
this.parent = activeEffect;
activeEffect = this;
clearupEffect(this);
return this.fn();
} finally {
activeEffect = this.parent;
}
}
stop() {
if (this.active) {
this.active = false;
clearupEffect(this);
}
}
}
function clearupEffect(effect) {
const { deps } = effect;
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect);
}
deps.length = 0;
}
依赖绑定函数track
const targetMap = new WeakMap();
export function track(target, type, key) {
if (!activeEffect) return;
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()));
}
trackEffects(dep);
}
依赖更新执行effect函数trigger
export function trigger(taregt, type, key, value, oldVal) {
const depsMap = targetMap.get(taregt);
if (!depsMap) return;
let effects = depsMap.get(key);
if (effects) {
triggerEffect(effects);
}
}
封装effect常用逻辑收集、调用
export function trackEffects(dep) {
if (activeEffect) {
let shouldTrack = !dep.has(activeEffect);
if (shouldTrack) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
}
export function triggerEffect(effects) {
effects = new Set(effects);
effects.forEach((effect) => {
if (effect !== activeEffect) {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
});
}
数据代理(reactive实现)
代理流程简述
reactive
:将对象代理,并返回代理对象。
- 考虑对象中有访问器、函数,在其中通过this访问对象的属性。
- 劫持时通过
Reflect
获取、修改属性,并将Proxy
对象传入,改变其this
指向为Proxy
对象。
- 保证访问器、函数内的源对象属性访问也能被劫持到。
- 处理已代理过的对象,通过标识符
IS_REACTIVE
,标识数据是否已代理过
- 若已代理过,则直接返回该对象。
- 细节:
- 未被代理的对象无该属性拦截。
- 已被代理的对象在访问器设置了逻辑。
- 在不新增属性的情况下实现了标识判断。
export const isReactive = (obj) => {
return obj && obj[ReactiveFlags.IS_REACTIVE];
};
export const enum ReactiveFlags {
IS_REACTIVE = "__v_isReactive",
}
export const isObject = (example) => {
return typeof example === "object" && example != null;
};
export const mutableHandle = {
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true;
}
track(target, "get", key);
let res = Reflect.get(target, key, receiver);
if (isObject(res)) {
return reactive(res);
}
return res;
},
set(target, key, value, receiver) {
let oldVal = target[key];
let result = Reflect.set(target, key, value, receiver);
if (oldVal != result) {
trigger(target, "set", key, value, oldVal);
}
return Reflect.set(target, key, value, receiver);
},
}
export function reactive(obj) {
if (!isObject(obj)) return;
if (obj[ReactiveFlags.IS_REACTIVE]) {
return obj;
}
const proxyObj = new Proxy(obj, mutableHandle);
return proxyObj;
}
源码阅读补充
- 部分属性不代理
如:Symbol(内置)、__proto__等(对象内置属性)、__v_isRef、__isVue_等
- 如果对象存在原型链继承的关系
原型链上存在此属性,后子级直接修改该属性
,则只触发当前对象利用toRaw获取源对象判断是否为当前对象
- 数组处理
循环递归
- 重写数组部分方法,当数组方法需要用到源数组做操作或对比时
includes、indexOf
,内部使用源对象进行操作,保证结果准确。
- 当数组通过修改长度值为
newLen
时
- 比对newLen值与数组下标项,若小于则触发更新
- 直接监听数组长度也会触发更新
- 重写通过数组方法
修改数组本身的方法,(push,pop,shift,unshift,splice)
- 当执行时,会收集数组的length属性,并触发length属性的依赖更新,及自身的依赖更新等
- Reflect补充图例:

ref实现
- API们
- ref:将一个基本数据类型转换为响应式数据类型。
- 将原始值包装成对象
defineProperty
,与computed
的原理相似
- 若是复杂数据类型,则走
reactive
,转为响应式数据
- 并设置get访问器
收集依赖
、set触发依赖
。
- toRefs:将对象所有属性再代理一层,访问时代理到源对象上
- toRef:代理对象一个属性
- proxyRefs:反向代理,将响应式数据又反向代理成普通数据
使用
,本质还是访问.value
。
import { isArray, isObject } from "@vue/shared";
import { trackEffects, triggerEffect } from "./effect";
import { reactive } from "./reactive";
function toReavtive(val) {
return isObject(val) ? reactive(val) : val;
}
class RefImpl {
public dep = new Set();
public _value;
public __v_isRef = true;
constructor(public source) {
this.source = toReavtive(source);
}
get value() {
trackEffects(this.dep);
return this._value;
}
set value(newVal) {
if (newVal !== this.source) {
this._value = toReavtive(newVal);
this.source = newVal;
triggerEffect(this.dep);
}
}
}
export const ref = (source) => {
return new RefImpl(source);
};
class ObjectRefImpl {
constructor(public obj, public key) {}
get value() {
return this.obj[this.key];
}
set value(newVal) {
this.obj[this.key] = newVal;
}
}
export const toRef = (obj, key) => {
return new ObjectRefImpl(obj, key);
};
export const toRefs = (obj) => {
const result = isArray(obj) ? new Array(obj.length) : obj;
for (let key in result) {
toRef(result, key);
}
};
export const proxyRef = (obj) => {
return new Proxy(obj, {
get(target, key, recevier) {
let r = Reflect.get(target, key, recevier);
return r.__v_isRef ? r.value : r;
},
set(target, key, value, recevier) {
let oldVal = target[key];
if (oldVal.__v_isRef) {
oldVal.value = value;
return true;
} else {
return Reflect.set(target, key, value, recevier);
}
},
});
};