带着问题阅读源码Vue3.2-ref实现原理
这是带着问题阅读源码第二篇,我们主要了解 ref 这个实现原理
我们先来 ref 这个api的使用例子
const value = ref('')
function ref(value) {
return createRef(value);
}
function createRef(rawValue, shallow = false) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
ref 的实现主要是看 RefImpl 这类
// 把普通对象转换成响应式对象
const convert = (val) => isObject(val) ? reactive(val) : val;
class RefImpl {
constructor(value, _shallow = false) {
this._shallow = _shallow;
this.dep = undefined;
this.__v_isRef = true;
this._rawValue = _shallow ? value : toRaw(value);
// 如果是浅代理,则只会处理 value,反之则会调用 convert
// convert 会把普通对象 转换响应式对象
this._value = _shallow ? value : convert(value);
}
get value() {
// 触发依赖收集
trackRefValue(this);
return this._value;
}
set value(newVal) {
// 处理 数据源
newVal = this._shallow ? newVal : toRaw(newVal);
// 判断数据是否变更
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
// 处理value的是个对象的情况
this._value = this._shallow ? newVal : convert(newVal);
// 更新依赖
triggerRefValue(this, newVal);
}
}
}
从 RefImpl 这个类的构造函数里可以看出,如果 ref包裹的是个对象,则会调用 reactive 这个api
理解reactive 的原理,就能理解ref 的原理一半了,剩余一半的是处理基本数据类型的情况了。
当我们访问 ref 的value的时候,会触发get,调用 trackRefValue
function trackRefValue(ref) {
if (isTracking()) {
// 获取源数据
ref = toRaw(ref);
// 如果没 ref.dep,则 ref.dep = new set()
if (!ref.dep) {
ref.dep = createDep();
}
{
// 开始收集依赖
trackEffects(ref.dep, {
target: ref,
type: "get" /* GET */,
key: 'value'
});
}
}
}
收集依赖的逻辑与 reactive 的类似,但没有 reactive 那么复杂,少了查找target和key的过程
主要是 先处理 ref.dep, 然后就执行 把依赖存储到 ref中。
当我们设置ref的value的时候,会触发set,调用triggerRefValue
function triggerRefValue(ref, newVal) {
// 处理数据源
ref = toRaw(ref);
if (ref.dep) {
{
// 执行 更新依赖
triggerEffects(ref.dep, {
target: ref,
type: "set" /* SET */,
key: 'value',
newValue: newVal
});
}
}
}
更新依赖比较简单,拿出ref的dep,然后执行即可。
总结
ref这个api即可把包裹基本数据类型,也可以包裹引用数据类型。
ref会先设置value的get、set属性,
get的时候会调用trackRefValue,trackRefValue内部调用trackEffects收集依赖并存储到ref.dep中;
set的时候如果value是对象或者数组,则会reative把普通对象转换响应式数据,然后调用triggerRefValue,把ref.dep拿出来交给triggerEffects执行,更新依赖
如果包裹的是引用数据类型,其收集依赖和更新依赖的逻辑还是执行reative这个api。
ref的原理依赖于reactive这个api。
如果ref包裹基本数据类型,更建议shallowRef这个api,会一些判断,不需要去处理引用数据类型这种情况了。