diff --git a/packages/inula-reactive/index.ts b/packages/inula-reactive/index.ts index 058b87ee..0e44a8a0 100644 --- a/packages/inula-reactive/index.ts +++ b/packages/inula-reactive/index.ts @@ -31,4 +31,4 @@ export { computed, isReactiveObj, untrack -} +}; diff --git a/packages/inula-reactive/src/RNode.ts b/packages/inula-reactive/src/RNode.ts index 6bc02ac5..869e9f42 100644 --- a/packages/inula-reactive/src/RNode.ts +++ b/packages/inula-reactive/src/RNode.ts @@ -26,7 +26,6 @@ export type State = typeof Fresh | typeof Check | typeof Dirty; type NonClean = typeof Check | typeof Dirty; export interface RNodeOptions { - root?: Root | null; isSignal?: boolean; isEffect?: boolean; isComputed?: boolean; diff --git a/packages/inula-reactive/src/RNodeCreator.ts b/packages/inula-reactive/src/RNodeCreator.ts index ea3c57bb..2cc5cbb9 100644 --- a/packages/inula-reactive/src/RNodeCreator.ts +++ b/packages/inula-reactive/src/RNodeCreator.ts @@ -17,7 +17,7 @@ import { isPrimitive } from './Utils'; import { RNode } from './RNode'; import { ProxyRNode } from './Types'; import { RProxyNode } from './RProxyNode'; -import {getRNodeVal, getRootRNode} from "./RNodeAccessor"; +import { getRNodeVal, getRootRNode } from './RNodeAccessor'; export type Reactive = RNode | Atom; @@ -61,18 +61,21 @@ export function getOrCreateChildRNode(node: RProxyNode, key: string | symbo // root: node.root, // }); - child = new RProxyNode(() => { - const rootRNode = getRootRNode(node); - // 依赖根 - rootRNode.get(); + child = new RProxyNode( + () => { + const rootRNode = getRootRNode(node); + // 依赖根 + rootRNode.get(); - return getRNodeVal(node)[key]; - }, { - isComputed: true, - parent: node, - key: key, - root: node.root, - }); + return getRNodeVal(node)[key]; + }, + { + isComputed: true, + parent: node, + key: key, + root: node.root, + } + ); } child.track(); diff --git a/packages/inula-reactive/src/RProxyNode.ts b/packages/inula-reactive/src/RProxyNode.ts index b1a03545..b943e585 100644 --- a/packages/inula-reactive/src/RProxyNode.ts +++ b/packages/inula-reactive/src/RProxyNode.ts @@ -25,7 +25,7 @@ export interface RNodeOptions { isEffect?: boolean; isComputed?: boolean; isProxy?: boolean; - parent?: RNode | null; + parent?: RProxyNode | null; key?: KEY | null; equals?: (a: any, b: any) => boolean; } @@ -55,14 +55,12 @@ export class RProxyNode extends RNode { this.key = options?.key as KEY; this.root = options?.root || {}; - if (this.parent && !this.parent.children) { - this.parent.children = new Map(); + if (this.parent) { + if (!this.parent.children) { + this.parent.children = new Map(); + } this.parent.children.set(this.key, this); } - - if (this.isComputed) { - this.update(); - } } compare(prevValue: any, value: any) { @@ -109,7 +107,7 @@ export class RProxyNode extends RNode { return getRNodeVal(this); } - setValue(value: any) { + setValue(value: T) { setRNodeVal(this, value); } } diff --git a/packages/inula-reactive/src/Utils.ts b/packages/inula-reactive/src/Utils.ts index 07ed92bb..b2607c81 100644 --- a/packages/inula-reactive/src/Utils.ts +++ b/packages/inula-reactive/src/Utils.ts @@ -30,7 +30,7 @@ export function isPrimitive(obj: unknown): boolean { return obj != null && type !== 'object' && type !== 'function'; } -export function isFunction any>(obj: unknown): obj is T { +export function isFunction any>(obj: unknown): obj is Function { return typeof obj === 'function'; } diff --git a/packages/inula-reactive/tests/reactive.test.ts b/packages/inula-reactive/tests/reactive.test.ts index abeaefd5..de444fb7 100644 --- a/packages/inula-reactive/tests/reactive.test.ts +++ b/packages/inula-reactive/tests/reactive.test.ts @@ -1,31 +1,66 @@ import { reactive, computed, watch } from '../index'; describe('test reactive', () => { - it('two signals, one computed', () => { + it('computation should work with two reactive', () => { const a = reactive(7); const b = reactive(1); let callCount = 0; - const c = computed(() => { + const product = computed(() => { callCount++; - return { a: a.get() * b.get() }; + return { value: a.get() * b.get() }; }); - watch(() => { - console.log(a.get()); - }); + // computed should be lazy + expect(callCount).toBe(0); expect(a.read()).toBe(7); a.set(2); - expect(c.a.read()).toBe(2); + expect(product.value.read()).toBe(2); b.set(3); - expect(c.a.get()).toBe(6); + expect(product.value.get()).toBe(6); + expect(callCount).toBe(2); - expect(callCount).toBe(3); - c.read(); - expect(callCount).toBe(3); + product.read(); + // computed function should not be invoked + expect(callCount).toBe(2); + }); + + it('computations should be triggered when source is same reactive', () => { + const pos = reactive({ x: 0, y: 0 }); + let xCalledTimes = 0; + let yCalledTimes = 0; + + const x = computed(() => { + xCalledTimes++; + return pos.x.get(); + }); + + const y = computed(() => { + yCalledTimes++; + return pos.y.get(); + }); + + expect(x.read()).toBe(0); + expect(y.read()).toBe(0); + expect(xCalledTimes).toBe(1); + expect(yCalledTimes).toBe(1); + + // when pos.x changed, x should be triggered and y should not + pos.x.set(1); + expect(x.read()).toBe(1); + expect(y.read()).toBe(0); + expect(xCalledTimes).toBe(2); + expect(yCalledTimes).toBe(1); + + // when pos.y changed, y should be triggered and x should not + pos.y.set(1); + expect(x.read()).toBe(1); + expect(y.read()).toBe(1); + expect(xCalledTimes).toBe(2); + expect(yCalledTimes).toBe(2); }); it('reactive is a obj', () => {