fix: multiple computation lost reactive when track same reactive.

This commit is contained in:
haiqin 2024-01-24 14:50:38 +08:00
parent 2a041e6e3d
commit ecbe842154
6 changed files with 58 additions and 22 deletions

View File

@ -31,4 +31,4 @@ export {
computed,
isReactiveObj,
untrack
}
};

View File

@ -26,7 +26,6 @@ export type State = typeof Fresh | typeof Check | typeof Dirty;
type NonClean = typeof Check | typeof Dirty;
export interface RNodeOptions {
root?: Root<any> | null;
isSignal?: boolean;
isEffect?: boolean;
isComputed?: boolean;

View File

@ -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<T = any> = RNode<T> | Atom<T>;
export function createReactive<T extends any>(raw?: T): ReactiveProxy<T> {
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<T>;
}
}
export function createComputed<T>(fn: T) {
const rNode = new RProxyNode(fn, { isComputed: true });
const rNode = new RProxyNode(fn, {isComputed: true});
return rNode.proxy;
}

View File

@ -25,7 +25,7 @@ export interface RNodeOptions {
isEffect?: boolean;
isComputed?: boolean;
isProxy?: boolean;
parent?: RNode<any> | null;
parent?: RProxyNode<any> | null;
key?: KEY | null;
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.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<T = any> extends RNode<T> {
return getRNodeVal(this);
}
setValue(value: any) {
setValue(value: T) {
setRNodeVal(this, value);
}
}

View File

@ -30,7 +30,7 @@ export function isPrimitive(obj: unknown): boolean {
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';
}

View File

@ -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', () => {