对应单侧
readonly.spec.ts
import { reactive, readonly } from "..";
/*
* @Author: Lin zefan
* @Date: 2022-03-16 18:03:54
* @LastEditTime: 2022-03-16 19:26:56
* @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);
});
/** 检测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();
});
核心逻辑
index.ts
/* 看得见的思考
* 1. readonly 只读,不可以设置
* 2. 只读那必然不会有收集依赖、设置依赖
* 3. 那是不是基于reactive的基础上去更改?
**/
import { track, trigger } from "./effect";
export function reactive(raw) {
return new Proxy(raw, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target, key);
return res;
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver);
trigger(target, key);
return res;
},
});
}
+ export function readonly(raw) {
+ return new Proxy(raw, {
+ get(target, key, receiver) {
+ const res = Reflect.get(target, key, receiver);
+ return res;
+ },
+ set(target, key, value, receiver) {
+ // TODO,设置的时候应该是给一个警告
+ return true;
+ },
+ });
+ }
代码优化
单侧通过!但是,reactive跟readonly看起来有优化空间!改造一下
/* 看得见的思考
* 1. Proxy内部的get和set接收的是一个函数
* 2. 可以抽取一个公用的get高阶函数,给一个isReadonly入参来判断是否为readonly
**/
+ function createdGetter(isReadonly = false) {
+ return function (target, key, receiver) {
+ 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;
+ };
+ }
export function reactive(raw) {
return new Proxy(raw, {
+ get: createdGetter(),
+ set: createdSetter(),
});
}
export function readonly(raw) {
return new Proxy(raw, {
+ get: createdGetter(true),
set(target, key, value, receiver) {
// TODO,设置的时候应该是给一个警告
return true;
},
});
}
告一段落,看起来好多了。但是,好像还有优化的空间?
- 提取公用ts
- 提取创建P
在 reactivity 目录下新增 baseHandlers.ts
// baseHandlers.ts
/*
* @Author: Lin zefan
* @Date: 2022-03-16 18:30:25
* @LastEditTime: 2022-03-17 11:03:43
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\src\reactivity\baseHandlers.ts
*
*/
import { track, trigger } from "./effect";
function createdGetter(isReadonly = false) {
return function (target, key, receiver) {
const res = Reflect.get(target, key, receiver);
+ // 嵌套转换
+ if (isObject(res)) {
+ return isReadonly ? readonly(res) : reactive(res);
+ }
// 如果是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;
},
};
index.ts 更新
/*
* @Author: Lin zefan
* @Date: 2022-03-15 13:08:22
* @LastEditTime: 2022-03-17 11:03:14
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\src\reactivity\index.ts
*
*/
import { mutableHandles, readonlyHandles } from "./baseHandlers";
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);
}
shared/index.ts
/*
* @Author: Lin zefan
* @Date: 2022-03-15 19:28:09
* @LastEditTime: 2022-03-17 19:03:20
* @LastEditors: Lin zefan
* @Description: 公用hook
* @FilePath: \mini-vue3\src\shared\index.ts
*
*/
+ export function isObject(obj) {
+ return obj !== null && typeof obj === "object";
+ }