前言
我们先顺一下vue使用响应式数据的流程:
vue 是通过 ref
和 reactive
来创建响应式值,改变响应式值,视图跟着发生变化。
我们今天就来看一下ref
和reactive
是如何实现的
准备
首先,打开ref
函数的位置
我们可以看到一个被ref
包裹的响应式数据,其实是一个RefImpl
对象
1. 创建ref
export function ref(value) {
return createRef(value);
}
function createRef(value) {
const refImpl = new RefImpl(value);
return refImpl;
}
可以看到创建一个ref对象的本质就是创建一个RefImpl
装饰对象
2. 编写RefImpl
对象
export class RefImpl {
private _rawValue: any; // 原值
private _value: any; // 代理值
public dep; // 依赖数组:用来存放依赖
public __v_isRef = true; // 区分是否是ref这个对象类型
constructor(value) {
this._rawValue = value;
// 看看value 是不是一个对象,如果是一个对象的话
// 那么需要用 reactive 包裹一下
this._value = convert(value);
this.dep = createDep(); // 创建effect对象
}
get value() {
// 收集依赖
trackRefValue(this);
return this._value;
}
set value(newValue) {
// 当新的值不等于老的值的话,
// 那么才需要触发依赖
if (hasChanged(newValue, this._rawValue)) {
// 更新值
this._value = convert(newValue);
this._rawValue = newValue;
// 触发依赖
triggerRefValue(this);
}
}
}
❤️❤️ 为什么要有_rawValue
和_value
呢?
答:_value用来保存我们加工后的具有响应式的对象,
_rawValue
保存的是原始的值
3. 依赖收集和触发
依赖收集
// trackRefValue
export function trackRefValue(ref) {
if (isTracking()) { // 判定师傅可以进行收集
trackEffects(ref.dep);
}
}
// trackEffects
let activeEffect = void 0;
export function trackEffects(dep) {
// 用 dep 来存放所有的 effect
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
(activeEffect as any).deps.push(dep);
}
}
activeEffect
:作用是用一个全局变量存储被注册的副作用函数
依赖触发
// ref.ts
export function triggerRefValue(ref) {
triggerEffects(ref.dep);
}
// effect.ts
export function triggerEffects(dep) {
// 执行收集到的所有的 effect 的 run 方法
for (const effect of dep) {
if (effect.scheduler) {
// scheduler 可以让用户自己选择调用的时机
// 这样就可以灵活的控制调用了
// 在 runtime-core 中,就是使用了 scheduler 实现了在 next ticker 中调用的逻辑
effect.scheduler();
} else {
effect.run();
}
}
}
思考
- 用
ref
创建的值.value
有什么用?