目标

  1. readonly 和 reactive 相似
  2. 但不能 set,即说明不会触发依赖,也就不用做依赖收集 ```typescript import { readonly } from ‘../reactive’;

describe(‘readonly’, () => { it(‘happy path’, () => { // not set const original = { foo: 1, bar: { baz: 2 } }; const wrapper = readonly(original); expect(wrapper).not.toBe(original); expect(wrapper.foo).toBe(1); }); });

  1. <a name="kRQw4"></a>
  2. # 实现
  3. <a name="j3eAj"></a>
  4. ## 实现 readonly
  5. readonly 本质就是 reactive 功能只读方法的实现
  6. ```typescript
  7. export function readonly(raw) {
  8. return new Proxy(raw, {
  9. get(target, key) {
  10. const res = Reflect.get(target, key);
  11. return res;
  12. },
  13. set(target, key, value) {
  14. // TODO 抛出警告
  15. return true;
  16. }
  17. });
  18. }

重构优化

  1. 由于 readonly 与 reactive 很类似,可以把 get 抽离 ```typescript function createGetter (isReadonly = false) { return function get(target, key) { const res = Reflect.get(target, key);

    if(!isReadonly) { track(target, key); } return res; } }

export function reactive(raw) { return new Proxy(raw, { get: createGetter(), set(target, key, value) { const res = Reflect.set(target, key, value);

  1. trigger(target, key);
  2. return res;
  3. }

}); }

export function readonly(raw) { return new Proxy(raw, { get: createGetter(true), set(target, key, value) { // TODO 抛出警告 return true; } }); }

  1. 2. 由于编程的相对性,保持代码的一致。getter 处理完成,就可以对 setter 作处理。所以 reactive 重构为
  2. ```typescript
  3. function createSetter () {
  4. return function set(target, key, value) {
  5. const res = Reflect.set(target, key, value);
  6. trigger(target, key);
  7. return res;
  8. }
  9. }
  10. export function reactive(raw) {
  11. return new Proxy(raw, {
  12. get: createGetter(),
  13. set: createSetter(),
  14. });
  15. }

重构后为 reactive.ts

  1. function createGetter (isReadonly = false) {
  2. return function get(target, key) {
  3. const res = Reflect.get(target, key);
  4. if(!isReadonly) {
  5. track(target, key);
  6. }
  7. return res;
  8. }
  9. }
  10. function createSetter () {
  11. return function set(target, key, value) {
  12. const res = Reflect.set(target, key, value);
  13. trigger(target, key);
  14. return res;
  15. }
  16. }
  17. export function reactive(raw) {
  18. return new Proxy(raw, {
  19. get: createGetter(),
  20. set: createSetter(),
  21. });
  22. }
  23. export function readonly(raw) {
  24. return new Proxy(raw, {
  25. get: createGetter(true),
  26. set(target, key, value) {
  27. // TODO 抛出警告
  28. return true;
  29. }
  30. });
  31. }
  1. 可以看出 reactive 和 readonly 的结构是相似的,可以继续地抽离至 baseHandlers ```typescript import { track, trigger } from ‘./effect’;

// 使用缓存技术,只在初始化调用一次。之后使用这个引用 const get = createGetter(); const set = createSetter(); const readonlyGet = createGetter(true);

function createGetter(isReadonly = false) { return function get(target, key) { const res = Reflect.get(target, key);

  1. if (!isReadonly) {
  2. track(target, key);
  3. }
  4. return res;

}; }

function createSetter() { return function get(target, key, value) { const res = Reflect.set(target, key, value); trigger(target, key); return res; }; }

export const mutableHandlers = { get, set, };

export const readonlyHandlers = { get: readonlyGet, set(target, key, value) { // TODO 抛出警告 return true; }, };

  1. ```typescript
  2. import { mutableHandlers, readonlyHandlers } from './baseHandlers';
  3. export function reactive(raw) {
  4. // return new Proxy(raw, mutableHandlers);
  5. return createActiveObject(raw, mutableHandlers);
  6. }
  7. export function readonly(raw) {
  8. // return new Proxy(raw, readonlyHandlers);
  9. return createActiveObject(raw, readonlyHandlers);
  10. }
  11. // 使用函数封装表达出其意图,提高可读性
  12. function createActiveObject(raw: any, baseHandlers) {
  13. return new Proxy(raw, baseHandlers);
  14. }

实现 readonly set 抛出的警告

  1. it('warn where call set', () => {
  2. console.warn = jest.fn();
  3. const user = readonly({ age: 10 });
  4. user.age = 11;
  5. expect(console.warn).toBeCalled();
  6. });
  1. export const readonlyHandlers = {
  2. get: readonlyGet,
  3. set(target, key, value) {
  4. console.warn(`key:${key} can not set, cause target is readonly`);
  5. return true;
  6. },
  7. };