单侧
// readonly.spec.ts
import { isReactive, isReadonly, reactive, readonly } from "..";
/*
* @Author: Lin zefan
* @Date: 2022-03-16 18:03:54
* @LastEditTime: 2022-03-17 11:12:06
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\src\reactivity\test\readonly.spec.ts
*
*/
describe("readonly", () => {
/** 检测readonly
* 1. 是否为只读
* 2. 检测是否为reactive、readonly对象
*/
it("happy path", () => {
const original = { foo: 1, bar: { baz: 2 } };
const state = reactive({
foo: 1,
});
const wrapped = readonly(original);
wrapped.foo++;
expect(wrapped.foo).toBe(1);
expect(wrapped).not.toBe(original);
+ expect(isReactive(state)).toBe(true);
+ expect(isReadonly(wrapped)).toBe(true);
+ expect(isReadonly(original)).toBe(false);
});
/** 检测readonly内部调用set
* 1. 调用Proxy set会输出一个console.warn
* 2. 校验是否成功输出console.warn
*/
it("warn then call set", () => {
console.warn = jest.fn();
const u = readonly({
age: 10,
});
u.age = 11;
expect(console.warn).toBeCalled();
});
});
核心逻辑
isReactive
index.ts
/*
* @Author: Lin zefan
* @Date: 2022-03-15 13:08:22
* @LastEditTime: 2022-03-17 11:10:26
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\src\reactivity\index.ts
*
*/
/* 看得见的思考
* 1. 判断是否为reactive对象,是不是可以借助一个固有的key来判断?
* 2. 我们知道,可以通过 obj[key] 来获取值,那是不是可以在set的时候,拦截一下?
*/
import { mutableHandles, readonlyHandles } from "./baseHandlers";
+ export const enum ReactiveEnum {
+ IS_REACTIVE = "__v_isReactive",
+ }
function createdBaseHandler(raw, baseHandler) {
return new Proxy(raw, baseHandler);
}
export function reactive(raw) {
return createdBaseHandler(raw, mutableHandles);
}
export function readonly(raw) {
return createdBaseHandler(raw, readonlyHandles);
}
+ export function isReadonly(raw) {
+ // 双取反是为了兼容返回undefined
+ return !!raw[ReactiveEnum.IS_READONLY];
+ }
baseHandlers.ts
/*
* @Author: Lin zefan
* @Date: 2022-03-16 18:30:25
* @LastEditTime: 2022-03-17 11:12:04
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\src\reactivity\baseHandlers.ts
*
*/
+ import { ReactiveEnum } from ".";
import { track, trigger } from "./effect";
function createdGetter(isReadonly = false) {
return function (target, key, receiver) {
+ // 判断是否为reactive
+ if (key === ReactiveEnum.IS_REACTIVE) {
+ return !isReadonly;
+ }
const res = Reflect.get(target, key, receiver);
// 如果是readonly,不会进行收集
!isReadonly && track(target, key);
return res;
};
}
function createdSetter() {
return function (target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver);
trigger(target, key);
return res;
};
}
// 避免多次创建,这里直接用变量接收~
const get = createdGetter();
const set = createdSetter();
const readonlyGet = createdGetter(true);
// 可变动的
export const mutableHandles = {
get,
set,
};
// 只读的
export const readonlyHandles = {
get: readonlyGet,
set(target, key, value, receiver) {
// 给一个警告
console.warn(`${key}是只读的,因为被readonly包裹了`, target);
return true;
},
};
isReadonly
其实理解isReactive,会发现isReadonly是一模一样的!!直接开工
index.ts
export const enum ReactiveEnum {
IS_REACTIVE = "__v_isReactive",
+ IS_READONLY = "__v_isReadonly",
}
+ export function isReadonly(raw) {
+ // 双取反是为了兼容返回undefined
+ return !!raw[ReactiveEnum.IS_READONLY];
+ }
baseHandlers.ts
function createdGetter(isReadonly = false) {
return function (target, key, receiver) {
console.log("获取的key", key);
// 判断是否为reactive
if (key === ReactiveEnum.IS_REACTIVE) {
return !isReadonly;
}
// 判断是否为readonly
+ if (key === ReactiveEnum.IS_READONLY) {
+ return isReadonly;
+ }
const res = Reflect.get(target, key, receiver);
// 如果是readonly,不会进行收集
!isReadonly && track(target, key);
return res;
};
}