Skip to content

Commit

Permalink
feat(ccstate): signal will distinct same value (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
yc-kanyun authored Dec 27, 2024
1 parent fb8cf43 commit 89d98a2
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/twelve-plants-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'ccstate': minor
---

feat(ccstate): state & computed will distinct change by default
6 changes: 3 additions & 3 deletions packages/ccstate/src/core/__tests__/distinct.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, it, vi } from 'vitest';
import { command, computed, state } from '../signal/factory';
import { getDefaultStore } from '../store/store';

it('default state & computed is not distincted', () => {
it('default state & computed is distincted', () => {
const base$ = state(0);
const computed$ = computed((get) => get(base$));

Expand All @@ -12,6 +12,6 @@ it('default state & computed is not distincted', () => {
getDefaultStore().sub(computed$, command(traceComputed));

getDefaultStore().set(base$, 0);
expect(traceBase).toHaveBeenCalledTimes(1);
expect(traceComputed).toHaveBeenCalledTimes(1);
expect(traceBase).not.toHaveBeenCalled();
expect(traceComputed).not.toHaveBeenCalled();
});
26 changes: 13 additions & 13 deletions packages/ccstate/src/core/__tests__/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ it('should not fire on subscribe', () => {
expect(callback2).not.toHaveBeenCalled();
});

it('should fire subscription if primitive atom value is the same', () => {
it('should not fire subscription if primitive atom value is the same', () => {
const store = createStore();
const countAtom = state(0);
const callback = vi.fn();
store.sub(countAtom, command(callback));
callback.mockClear();
store.set(countAtom, 0);
expect(callback).toBeCalled();
expect(callback).not.toBeCalled();
});

it('should fire subscription even if derived atom value is the same', () => {
Expand Down Expand Up @@ -150,7 +150,7 @@ it('should update async atom with deps after await', async () => {
unsub();
});

it('should fire subscription when async atom promise is the same', () => {
it('should not fire subscription when async atom promise is the same', () => {
const promise = Promise.resolve();
const promiseAtom = state(promise, {
debugLabel: 'promiseAtom',
Expand Down Expand Up @@ -180,17 +180,16 @@ it('should fire subscription when async atom promise is the same', () => {
expect(promiseListener).not.toHaveBeenCalled();
expect(derivedListener).not.toHaveBeenCalled();

derivedGetter.mockClear();
store.set(promiseAtom, promise);
expect(derivedGetter).toHaveBeenCalledTimes(2);
expect(promiseListener).toBeCalled();
expect(derivedListener).toBeCalled();
expect(derivedGetter).not.toHaveBeenCalled();
expect(promiseListener).not.toBeCalled();
expect(derivedListener).not.toBeCalled();

promiseListener.mockClear();
derivedListener.mockClear();
store.set(promiseAtom, promise);
expect(derivedGetter).toHaveBeenCalledTimes(3);
expect(promiseListener).toBeCalled();
expect(derivedListener).toBeCalled();
expect(derivedGetter).not.toHaveBeenCalled();
expect(promiseListener).not.toBeCalled();
expect(derivedListener).not.toBeCalled();

promiseUnsub();
derivedUnsub();
Expand Down Expand Up @@ -357,16 +356,17 @@ it('should update derived atoms during write', () => {
expect(store.get(countAtom)).toBe(2);
});

it('should recompute a derived atom value even if unchanged (#2168)', () => {
it('should not recompute a derived atom value if unchanged (#2168)', () => {
const store = createStore();
const countAtom = state(1);
const zeroAtom = computed((get) => get(countAtom) * 0);
const deriveFn = vi.fn((get: Getter) => get(zeroAtom));
const derivedAtom = computed(deriveFn);
expect(store.get(derivedAtom)).toBe(0);
expect(deriveFn).toHaveBeenCalledTimes(1);
store.set(countAtom, (c) => c + 1);
expect(store.get(derivedAtom)).toBe(0);
expect(deriveFn).toHaveBeenCalledTimes(2);
expect(deriveFn).toHaveBeenCalledTimes(1);
});

it('should notify pending write triggered asynchronously and indirectly (#2451)', async () => {
Expand Down
7 changes: 5 additions & 2 deletions packages/ccstate/src/core/signal/computed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
} from '../../../types/core/store';
import { withGeValInterceptor } from '../interceptor';
import { canReadAsCompute } from '../typing-util';
import { shouldDistinct } from './signal';

function checkEpoch<T>(
readComputed: ReadComputed,
Expand Down Expand Up @@ -147,8 +148,10 @@ export function evaluateComputed<T>(

cleanupMissingDependencies(unmount, computed$, lastDeps, dependencies, context, mutation);

computedState.val = evalVal;
computedState.epoch += 1;
if (!shouldDistinct(computed$, evalVal, context)) {
computedState.val = evalVal;
computedState.epoch += 1;
}

return computedState;
}
10 changes: 10 additions & 0 deletions packages/ccstate/src/core/signal/signal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Signal } from '../../../types/core/signal';
import type { StoreContext } from '../../../types/core/store';

export function currentValue<T>(signal: Signal<T>, context: StoreContext): T | undefined {
return context.stateMap.get(signal)?.val as T | undefined;
}

export function shouldDistinct<T>(signal: Signal<T>, value: T, context: StoreContext) {
return currentValue(signal, context) === value;
}
5 changes: 5 additions & 0 deletions packages/ccstate/src/core/store/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
StoreSet,
SetArgs,
} from '../../../types/core/store';
import { shouldDistinct } from '../signal/signal';

function pushDirtyMarkers(signalState: StateState<unknown>, context: StoreContext, mutation: Mutation) {
let queue: Computed<unknown>[] = Array.from(signalState.mounted?.readDepts ?? []);
Expand Down Expand Up @@ -89,6 +90,10 @@ function innerSetState<T>(
newValue = val;
}

if (shouldDistinct(signal$, newValue, context)) {
return;
}

const signalState = context.stateMap.get(signal$);
if (!signalState) {
context.stateMap.set(signal$, {
Expand Down

0 comments on commit 89d98a2

Please sign in to comment.