likes
comments
collection
share

08_实现isReactive和isReadonly

作者站长头像
站长
· 阅读数 41

08_实现isReactive和isReadonly

一、实现isReactive

isReactive: 检查一个对象是否是由 reactive 创建的响应式代理。

1. 单元测试

// src/reactivity/tests/reactive.spec.ts

import { reactive, isReactive } from '../reactive';

describe('reactive', function () {
  it('happy path', function () {
    const original = { foo: 1 };
    const observed = reactive(original);

    expect(observed).not.toBe(original);
    expect(observed.foo).toBe(original.foo);

    // + isReactive
    expect(isReactive(observed)).toBe(true);
    expect(isReactive(original)).toBe(false);
  });
});

2. 代码实现

其实我们在baseHandlerscreateGetter的时候,我们就已经传递过isReadonly的标识,那我们只要想办法将这个标识传递出来,就可以了。

那就得触发代理对象的get操作,那先在reactive.ts中导出一个isReactive

// src/reactivity/reactive.ts

export function isReactive(value) {
  return value['is_reactive'];
}

接着在get中判断读取的key是否是is_reactive,然后返回对应结果即可。

// src/reactivity/baseHandlers.ts

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

    // + 如果读取的 key 是 is_reactive, 则返回 true
    if (key === 'is_reactive') {
      return !isReadonly;
    }

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

    return res;
  };
}

至此,基本逻辑已经实现,看一下单测结果。

yarn test reactive
08_实现isReactive和isReadonly

果然失败了呢,通过报错信息我们可以看见期望Expectedfalse,而实际值Receivedundefined

其实也很容易明白🐴,普通对象没有被代理,自然不会走我们封装的get,而且也没有这个属性,所以就返回undefined

但是isReactive(observed)的测试是通过的,所以可知对代理过后的对象的判断是正确的,达到了期望。那只要想办法将undefined 转成false,且不影响isReactive(observed)返回的true即可。

两个思路:

  • 思路1:如果是true就正确返回,如果是undefined,就返回false。那就利用空值合并运算符??捕获undefined

    return value['is_reactive'] ?? false;
    
  • 思路2:利用!!运算符,将表达式强转成布尔类型

    return !!value['is_reactive'];
    

其实能够看出来,思路1比起思路2不太优雅,所以我们选择思路2。

08_实现isReactive和isReadonly

二、实现isReadonly

实现了isReactive之后,isReadonly就很类似了。

1. 单元测试

// src/reactivity/tests/readonly.spec.ts

it('happy path', () => {
  const original = { foo: 1, bar: { baz: 2 } };
  const wrapped = readonly(original);

  expect(wrapped).not.toBe(original);
  expect(wrapped.foo).toBe(1);

  // ! 不能被set
  wrapped.foo = 2;
  expect(wrapped.foo).toBe(1);

  // + isReadonly
  expect(isReadonly(wrapped)).toBe(true);
  expect(isReadonly(original)).toBe(false);
});

2. 代码实现

同上,即可。

// src/reactivity/reactive.ts

export function isReadonly(value) {
  return !!value['is_readonly'];
}
// src/reactivity/baseHandlers.ts

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

    if (key === 'is_reactive') {
      return !isReadonly;
    } else if (key === 'is_readonly') { // + is_readonly
      return isReadonly;
    }

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

    return res;
  };
}

走一下readonly的单测。

# --silent=true 是禁用控制台打印,静默测试,主要是因为之前的set会触发console.warn
yarn test readonly --silent=true
08_实现isReactive和isReadonly

三、代码重构

export function isReactive(value) {
  return !!value['is_reactive'];
}

export function isReadonly(value) {
  return !!value['is_readonly'];
}

这里就是一个优化的点,就像报错信息一样,报错可以用字典来维护起来,这里也是一样,可以用tsenum枚举来维护。

// src/reactivity/reactive.ts

export const enum ReactiveFlags {
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly'
}

// + 用到之前标识的地方,相应的进行修改
export function isReactive(value) {
  return !!value[ReactiveFlags.IS_REACTIVE];
}

export function isReadonly(value) {
  return !!value[ReactiveFlags.IS_READONLY];
}
// src/reactivity/baseHandlers.ts

import { track, trigger } from './effect';
import { ReactiveFlags } from './reactive';

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

    // + 改成枚举的方式
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly;
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly;
    }

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

    return res;
  };
}

大概就这么多,重构完以后,依旧完整的跑一遍所有的单测。

# --watchAll可以进入watch模式, 下面有很多 usage 可供使用
# 这样做的好处是, 我们不需要每次都执行 yarn test, 在第一次执行之后, 进程会自动监听测试用例的变化, 如果测试用例代码发生了变化, 会自动执行
yarn test --watchAll --silent=true
08_实现isReactive和isReadonly

ps

这是一个 早起俱乐部

⭐️ 适合人群:所有想有所改变的人,可以先从早起半小时开始!抽出30分钟,从初心开始!! ⭐️ 没有任何其它意味,只是本人想寻找一起早起、志同道合的小伙伴。

转载自:https://juejin.cn/post/7176086344815837242
评论
请登录