Skip to content

Commit

Permalink
feat: toRaw
Browse files Browse the repository at this point in the history
  • Loading branch information
cuixiaorui committed Aug 13, 2021
1 parent 2ca095f commit 0f25dfb
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 27 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
- [x] trigger 触发依赖
- [x] 支持 isReactive
- [x] 支持嵌套 reactive
- [x] 支持 toRaw

### build

Expand Down
105 changes: 83 additions & 22 deletions src/reactivity/__tests__/effect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,95 @@ import { reactive } from "../src/reactive";
import { effect } from "../src/effect";

describe("effect", () => {
it("should call function", () => {
const user = reactive({
age: 1,
});
it("should run the passed function once (wrapped by a effect)", () => {
const fnSpy = jest.fn(() => {});
effect(fnSpy);
expect(fnSpy).toHaveBeenCalledTimes(1);
});

it("should observe basic properties", () => {
let dummy;
const counter = reactive({ num: 0 });
effect(() => (dummy = counter.num));

expect(dummy).toBe(0);
counter.num = 7;
expect(dummy).toBe(7);
});

it("should observe multiple properties", () => {
let dummy;
const counter = reactive({ num1: 0, num2: 0 });
effect(() => (dummy = counter.num1 + counter.num1 + counter.num2));

expect(dummy).toBe(0);
counter.num1 = counter.num2 = 7;
expect(dummy).toBe(21);
});
it("should handle multiple effects", () => {
let dummy1, dummy2;
const counter = reactive({ num: 0 });
effect(() => (dummy1 = counter.num));
effect(() => (dummy2 = counter.num));

let nextAge = 0;
const fn = () => {
nextAge = user.age + 1;
};
expect(dummy1).toBe(0);
expect(dummy2).toBe(0);
counter.num++;
expect(dummy1).toBe(1);
expect(dummy2).toBe(1);
});

effect(fn);
it("should observe nested properties", () => {
let dummy;
const counter = reactive({ nested: { num: 0 } });
effect(() => (dummy = counter.nested.num));

expect(nextAge).toBe(2);
expect(dummy).toBe(0);
counter.nested.num = 8;
expect(dummy).toBe(8);
});

it("trigger", () => {
const user = reactive({
age: 1,
});
it("should observe function call chains", () => {
let dummy;
const counter = reactive({ num: 0 });
effect(() => (dummy = getNum()));

let nextAge = 0;
const fn = () => {
nextAge = user.age + 1;
};
function getNum() {
return counter.num;
}

effect(fn);
// set -> trigger
user.age = 2;
expect(nextAge).toBe(3);
expect(dummy).toBe(0);
counter.num = 2;
expect(dummy).toBe(2);
});
// it("should call function", () => {
// const user = reactive({
// age: 1,
// });

// let nextAge = 0;
// const fn = () => {
// nextAge = user.age + 1;
// };

// effect(fn);

// expect(nextAge).toBe(2);
// });

// it("trigger", () => {
// const user = reactive({
// age: 1,
// });

// let nextAge = 0;
// const fn = () => {
// nextAge = user.age + 1;
// };

// effect(fn);
// // set -> trigger
// user.age = 2;
// expect(nextAge).toBe(3);
// });
});
9 changes: 8 additions & 1 deletion src/reactivity/__tests__/reactive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { reactive, isReactive } from "../src/reactive";
import { reactive, isReactive, toRaw, reactiveMap } from "../src/reactive";
describe("reactive", () => {
test("Object", () => {
const original = { foo: 1 };
Expand Down Expand Up @@ -26,4 +26,11 @@ describe("reactive", () => {
expect(isReactive(observed.array)).toBe(true);
expect(isReactive(observed.array[0])).toBe(true);
});

test("toRaw", () => {
const original = { foo: 1 };
const observed = reactive(original);
expect(toRaw(observed)).toBe(original);
expect(toRaw(original)).toBe(original);
});
});
9 changes: 7 additions & 2 deletions src/reactivity/src/baseHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { track, trigger } from "./effect";
import { reactive, ReactiveFlags } from "./reactive";
import { ReactiveEffect, track, trigger } from "./effect";
import { reactive, ReactiveFlags, reactiveMap } from "./reactive";
import { isObject } from "../../shared/index";

const get = createGetter();
const set = createSetter();

function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
const isExistInReactiveMap = () =>
key === ReactiveFlags.RAW && receiver === reactiveMap.get(target);

if (key === ReactiveFlags.IS_REACTIVE) {
return true;
} else if (isExistInReactiveMap()) {
return target;
}

const res = Reflect.get(target, key, receiver);
Expand Down
31 changes: 29 additions & 2 deletions src/reactivity/src/reactive.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { mutableHandlers } from "./baseHandlers";

export const reactiveMap = new WeakMap();

export const enum ReactiveFlags {
IS_REACTIVE = "__v_isReactive",
RAW = "__v_raw",
}

export function reactive(target) {
return createReactiveObject(target);
return createReactiveObject(target, reactiveMap);
}

export function isReactive(value) {
Expand All @@ -16,10 +19,34 @@ export function isReactive(value) {
return !!value[ReactiveFlags.IS_REACTIVE];
}

function createReactiveObject(target) {
export function toRaw(value) {
// 如果 value 是proxy 的话 ,那么直接返回就可以了
// 因为会触发 createGetter 内的逻辑
// 如果 value 是普通对象的话,
// 我们就应该返回普通对象
// 只要不是 proxy ,只要是得到的 undefined 的话,那么就一定是普通对象
// TODO 这里和源码里面实现的不一样,不确定后面会不会有问题
if (!value[ReactiveFlags.RAW]) {
return value;
}

return value[ReactiveFlags.RAW];
}

function createReactiveObject(target, proxyMap) {
// 核心就是 proxy
// 目的是可以侦听到用户 get 或者 set 的动作

// 如果命中的话就直接返回就好了
// 使用缓存做的优化点
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}

const proxy = new Proxy(target, mutableHandlers);

// 把创建好的 proxy 给存起来,
proxyMap.set(target, proxy);
return proxy;
}

0 comments on commit 0f25dfb

Please sign in to comment.