fix: multiple computation lost reactive when track same reactive.
This commit is contained in:
parent
2a041e6e3d
commit
ecbe842154
|
@ -31,4 +31,4 @@ export {
|
||||||
computed,
|
computed,
|
||||||
isReactiveObj,
|
isReactiveObj,
|
||||||
untrack
|
untrack
|
||||||
}
|
};
|
||||||
|
|
|
@ -26,7 +26,6 @@ export type State = typeof Fresh | typeof Check | typeof Dirty;
|
||||||
type NonClean = typeof Check | typeof Dirty;
|
type NonClean = typeof Check | typeof Dirty;
|
||||||
|
|
||||||
export interface RNodeOptions {
|
export interface RNodeOptions {
|
||||||
root?: Root<any> | null;
|
|
||||||
isSignal?: boolean;
|
isSignal?: boolean;
|
||||||
isEffect?: boolean;
|
isEffect?: boolean;
|
||||||
isComputed?: boolean;
|
isComputed?: boolean;
|
||||||
|
|
|
@ -17,23 +17,23 @@ import { isPrimitive } from './Utils';
|
||||||
import { RNode } from './RNode';
|
import { RNode } from './RNode';
|
||||||
import { ProxyRNode } from './Types';
|
import { ProxyRNode } from './Types';
|
||||||
import { RProxyNode } from './RProxyNode';
|
import { RProxyNode } from './RProxyNode';
|
||||||
import {getRNodeVal, getRootRNode} from "./RNodeAccessor";
|
import { getRNodeVal, getRootRNode } from './RNodeAccessor';
|
||||||
|
|
||||||
export type Reactive<T = any> = RNode<T> | Atom<T>;
|
export type Reactive<T = any> = RNode<T> | Atom<T>;
|
||||||
|
|
||||||
export function createReactive<T extends any>(raw?: T): ReactiveProxy<T> {
|
export function createReactive<T extends any>(raw?: T): ReactiveProxy<T> {
|
||||||
if (isPrimitive(raw) || raw === null || raw === undefined) {
|
if (isPrimitive(raw) || raw === null || raw === undefined) {
|
||||||
return new RNode(raw, { isSignal: true });
|
return new RNode(raw, {isSignal: true});
|
||||||
} else {
|
} else {
|
||||||
const node = new RProxyNode(null, {
|
const node = new RProxyNode(null, {
|
||||||
root: { $: raw },
|
root: {$: raw},
|
||||||
});
|
});
|
||||||
return node.proxy as ReactiveProxy<T>;
|
return node.proxy as ReactiveProxy<T>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createComputed<T>(fn: T) {
|
export function createComputed<T>(fn: T) {
|
||||||
const rNode = new RProxyNode(fn, { isComputed: true });
|
const rNode = new RProxyNode(fn, {isComputed: true});
|
||||||
return rNode.proxy;
|
return rNode.proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ export interface RNodeOptions {
|
||||||
isEffect?: boolean;
|
isEffect?: boolean;
|
||||||
isComputed?: boolean;
|
isComputed?: boolean;
|
||||||
isProxy?: boolean;
|
isProxy?: boolean;
|
||||||
parent?: RNode<any> | null;
|
parent?: RProxyNode<any> | null;
|
||||||
key?: KEY | null;
|
key?: KEY | null;
|
||||||
equals?: (a: any, b: any) => boolean;
|
equals?: (a: any, b: any) => boolean;
|
||||||
}
|
}
|
||||||
|
@ -55,8 +55,10 @@ export class RProxyNode<T = any> extends RNode<T> {
|
||||||
this.key = options?.key as KEY;
|
this.key = options?.key as KEY;
|
||||||
this.root = options?.root || {};
|
this.root = options?.root || {};
|
||||||
|
|
||||||
if (this.parent && !this.parent.children) {
|
if (this.parent) {
|
||||||
this.parent.children = new Map();
|
if (!this.parent.children) {
|
||||||
|
this.parent.children = new Map();
|
||||||
|
}
|
||||||
this.parent.children.set(this.key, this);
|
this.parent.children.set(this.key, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +111,7 @@ export class RProxyNode<T = any> extends RNode<T> {
|
||||||
return getRNodeVal(this);
|
return getRNodeVal(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(value: any) {
|
setValue(value: T) {
|
||||||
setRNodeVal(this, value);
|
setRNodeVal(this, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ export function isPrimitive(obj: unknown): boolean {
|
||||||
return obj != null && type !== 'object' && type !== 'function';
|
return obj != null && type !== 'object' && type !== 'function';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFunction<T extends (...prev: any) => any>(obj: unknown): obj is T {
|
export function isFunction<T extends (...prev: any) => any>(obj: unknown): obj is Function {
|
||||||
return typeof obj === 'function';
|
return typeof obj === 'function';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,66 @@
|
||||||
import { reactive, computed, watch } from '../index';
|
import { reactive, computed, watch } from '../index';
|
||||||
|
|
||||||
describe('test reactive', () => {
|
describe('test reactive', () => {
|
||||||
it('two signals, one computed', () => {
|
it('computation should work with two reactive', () => {
|
||||||
const a = reactive(7);
|
const a = reactive(7);
|
||||||
const b = reactive(1);
|
const b = reactive(1);
|
||||||
let callCount = 0;
|
let callCount = 0;
|
||||||
|
|
||||||
const c = computed(() => {
|
const product = computed(() => {
|
||||||
callCount++;
|
callCount++;
|
||||||
return { a: a.get() * b.get() };
|
return { value: a.get() * b.get() };
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(() => {
|
// computed should be lazy
|
||||||
console.log(a.get());
|
expect(callCount).toBe(0);
|
||||||
});
|
|
||||||
|
|
||||||
expect(a.read()).toBe(7);
|
expect(a.read()).toBe(7);
|
||||||
|
|
||||||
a.set(2);
|
a.set(2);
|
||||||
expect(c.a.read()).toBe(2);
|
expect(product.value.read()).toBe(2);
|
||||||
|
|
||||||
b.set(3);
|
b.set(3);
|
||||||
expect(c.a.get()).toBe(6);
|
expect(product.value.get()).toBe(6);
|
||||||
|
expect(callCount).toBe(2);
|
||||||
|
|
||||||
expect(callCount).toBe(3);
|
product.read();
|
||||||
c.read();
|
// computed function should not be invoked
|
||||||
expect(callCount).toBe(3);
|
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', () => {
|
it('reactive is a obj', () => {
|
||||||
|
|
Loading…
Reference in New Issue