这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情
实现 ref 简版
目标:学习ref 看看 reactive 实现区别是什么 从源码的角度去分析 为什么是 ref 是.value 取值 ref 包裹对象取值为什么不需要 .value
下面是我实现的简单版本的 ref
-
当调用ref 的时候 返回了一个
createRef并执行这段函数createRef里面他进行了一个判断 两个参数 分别表示 传的值 和 是否 shallow 这里我们只需要关心 rawValue -
进来进行
isRef判断如果是一个 可以看见 其实就是通过一个标识来确认是不是ref -
最后用了一个
RefImpl类 返回 其实大致可以猜一下 标识可能就在RefImpl进行加工了
export function ref(value: any) {
return createRef(value, false);
}
function createRef(rawValue: any, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
export function isRef(r: any) {
return !!(r && r.__v_isRef === true);
}
接下来就是关注 RefImpl这个类到底做了什么 来看下面这段代码
- 可以看见在 constructor 接收了外面传的值 这时候 就如刚刚所想的一样 isRef 是在这个类上的添加的
- 接下来就是判断 是不是一个对象呀 是的话
toReactive里面 用 reactive 代理进行包装 不是就返回简单数据类型在 这个__value 这里就可以解释 ref 为啥包裹一个对象还是响应式 但是 我们可以思考一下 用ref 包裹 一个对象 多执行一次 和 包裹简单数据类型 少执行一次 谁好一点呢 - RefImpl 中使用 get set 来更新 返回值 get value 中进行了收集依赖 当 .value 的时候 我们就返回this.__value
- set 的时候 会进行判断 新旧两个值是否相等 不相等进行 判断是不是一个复杂数据类型 是的话使用 reactive 包装 然后触发更新
class RefImpl {
__v_isShallow: any;
dep: undefined;
__v_isRef: boolean;
_value: any;
constructor(value: any, __v_isShallow: boolean) {
this.__v_isShallow = __v_isShallow;
this.dep = undefined;
this.__v_isRef = true; // 标识
this._value = __v_isShallow ? value : toReactive(value);
}
get value() {
// 收集依赖
trackRefValue(this);
return this._value;
}
set value(newValue) {
// 判断是否需要更新 如果新值和旧值不相等
if (hasChanged(newValue, this._value)) {
// 如果是一个对象 那么就进行包装
this._value = isObject(newValue) ? reactive(newValue) : newValue;
// 触发更新
triggerRefValue(this);
}
}
}
我的单元测试
describe('Ref', () => {
it('create Ref', () => {
const initRef = ref('sakana');
const initNum = ref(1);
expect(initRef.value).toBe('sakana'); // 通过
expect(initNum.value).toBe(1); // 通过
initNum.value = 2;
expect(initNum.value).toBe(2); // 通过
initRef.value = 'sakana2';
expect(initRef.value).toBe('sakana2'); // 通过
expect(isRef(initRef)).toBe(true);
});
it('create Ref with object', () => {
const initRef = ref({ name: 'sakana' });
let name;
effect(() => {
name = initRef.value.name;
});
initRef.value.name = 'sakana2';
expect(initRef.value.name).toBe('sakana2'); // 通过
expect(name).toBe('sakana'); // 通过
});
});
思考
在我们面试vue3的过程中 我们会被这样一个问题 为什么 reactive 解构会失去响应式?
揭秘:
- 之前上一篇实现 reactive 中 我们其实是知道 reactive 使用了 Proxy 加 Reflect 然后自己的一套 get set 函数处理规则 通过 Proxy 返回的是一个新对象
- 当我们 尝试 解构 reactive 直接取出对象的值 那么 这个值就不在属于是 Proxy 代理 内部的 get set 收集依赖 和触发更新就不会在进行 所以 就会失去响应式