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..b8da0594 100644 --- a/packages/inula-reactive/src/RNodeCreator.ts +++ b/packages/inula-reactive/src/RNodeCreator.ts @@ -17,23 +17,23 @@ 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; export function createReactive(raw?: T): ReactiveProxy { if (isPrimitive(raw) || raw === null || raw === undefined) { - return new RNode(raw, { isSignal: true }); + return new RNode(raw, {isSignal: true}); } else { const node = new RProxyNode(null, { - root: { $: raw }, + root: {$: raw}, }); return node.proxy as ReactiveProxy; } } export function createComputed(fn: T) { - const rNode = new RProxyNode(fn, { isComputed: true }); + const rNode = new RProxyNode(fn, {isComputed: true}); return rNode.proxy; } diff --git a/packages/inula-reactive/src/RProxyNode.ts b/packages/inula-reactive/src/RProxyNode.ts index b1a03545..e4a2118a 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,8 +55,10 @@ 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); } @@ -109,7 +111,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', () => {