Skip to content

Commit 2b65507

Browse files
feat: add computed peek (#66)
Co-authored-by: JounQin <[email protected]>
1 parent f692fc7 commit 2b65507

File tree

3 files changed

+68
-29
lines changed

3 files changed

+68
-29
lines changed

.changeset/neat-dots-hug.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'unuse-reactivity': minor
3+
'unuse': minor
4+
'unuse-angular': minor
5+
'unuse-react': minor
6+
'unuse-solid': minor
7+
'unuse-vue': minor
8+
---
9+
10+
feat: add computed peek

packages/unuse-reactivity/src/unComputed/index.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, it, vi } from 'vitest';
22
import { isUnComputed, UN_COMPUTED, unComputed } from '.';
3+
import { unEffect } from '../unEffect';
34
import { unSignal } from '../unSignal';
45

56
describe('unComputed', () => {
@@ -13,6 +14,7 @@ describe('unComputed', () => {
1314

1415
expect(myComputed).toBeTypeOf('object');
1516
expect(myComputed.get).toBeTypeOf('function');
17+
expect(myComputed.peek).toBeTypeOf('function');
1618
expect(isUnComputed(myComputed)).toBe(true);
1719
});
1820

@@ -64,6 +66,23 @@ describe('unComputed', () => {
6466
expect(fnSpy).toHaveBeenCalledTimes(2);
6567
});
6668

69+
it('should peek the current value without triggering effects', () => {
70+
const fn1Spy = vi.fn();
71+
72+
const mySignal = unSignal(42);
73+
const myComputed = unComputed(mySignal.get);
74+
75+
unEffect(() => {
76+
myComputed.peek();
77+
fn1Spy();
78+
});
79+
80+
mySignal.set(100);
81+
expect(myComputed.peek()).toBe(100);
82+
83+
expect(fn1Spy).toHaveBeenCalledTimes(1);
84+
});
85+
6786
describe('isUnComputed', () => {
6887
it('should return true for an UnComputed', () => {
6988
const myComputed = unComputed(() => 42);

packages/unuse-reactivity/src/unComputed/index.ts

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
checkDirty,
44
link,
55
REACTIVITY_STATE,
6+
setCurrentSub,
67
shallowPropagate,
78
updateComputed,
89
} from '../unReactiveSystem';
@@ -12,32 +13,6 @@ export interface UnComputedState<T> extends ReactiveNode {
1213
getter: (previousValue?: T) => T;
1314
}
1415

15-
export function computedOper<T>(this: UnComputedState<T>): T {
16-
const flags = this.flags;
17-
if (
18-
flags & (16 satisfies ReactiveFlags.Dirty) ||
19-
(flags & (32 satisfies ReactiveFlags.Pending) &&
20-
checkDirty(this.deps!, this))
21-
) {
22-
if (updateComputed(this)) {
23-
const subs = this.subs;
24-
if (subs !== undefined) {
25-
shallowPropagate(subs);
26-
}
27-
}
28-
} else if (flags & (32 satisfies ReactiveFlags.Pending)) {
29-
this.flags = flags & ~(32 satisfies ReactiveFlags.Pending);
30-
}
31-
32-
if (REACTIVITY_STATE.activeSub !== undefined) {
33-
link(this, REACTIVITY_STATE.activeSub);
34-
} else if (REACTIVITY_STATE.activeScope !== undefined) {
35-
link(this, REACTIVITY_STATE.activeScope);
36-
}
37-
38-
return this.value!;
39-
}
40-
4116
/**
4217
* A unique symbol used to identify `UnComputed` objects.
4318
*
@@ -59,6 +34,11 @@ export interface UnComputed<T> {
5934
* Retrieves the current value of the computed.
6035
*/
6136
get(): T;
37+
38+
/**
39+
* Retrieves the current value of the signal without triggering effects.
40+
*/
41+
peek(): T;
6242
}
6343

6444
/**
@@ -69,19 +49,49 @@ export interface UnComputed<T> {
6949
* @returns An `UnComputed` object that has a `get` method to retrieve the current value.
7050
*/
7151
export function unComputed<T>(callback: () => T): UnComputed<T> {
72-
const get: UnComputed<T>['get'] = computedOper.bind({
52+
const state: UnComputedState<T> = {
7353
value: undefined,
7454
subs: undefined,
7555
subsTail: undefined,
7656
deps: undefined,
7757
depsTail: undefined,
7858
flags: 17 as ReactiveFlags.Mutable | ReactiveFlags.Dirty,
7959
getter: callback,
80-
} satisfies UnComputedState<T>) as UnComputed<T>['get'];
60+
};
8161

8262
return {
8363
[UN_COMPUTED]: true,
84-
get,
64+
get() {
65+
const flags = state.flags;
66+
if (
67+
flags & (16 satisfies ReactiveFlags.Dirty) ||
68+
(flags & (32 satisfies ReactiveFlags.Pending) &&
69+
checkDirty(state.deps!, state))
70+
) {
71+
if (updateComputed(state)) {
72+
const subs = state.subs;
73+
if (subs !== undefined) {
74+
shallowPropagate(subs);
75+
}
76+
}
77+
} else if (flags & (32 satisfies ReactiveFlags.Pending)) {
78+
state.flags = flags & ~(32 satisfies ReactiveFlags.Pending);
79+
}
80+
81+
if (REACTIVITY_STATE.activeSub !== undefined) {
82+
link(state, REACTIVITY_STATE.activeSub);
83+
} else if (REACTIVITY_STATE.activeScope !== undefined) {
84+
link(state, REACTIVITY_STATE.activeScope);
85+
}
86+
87+
return state.value!;
88+
},
89+
peek() {
90+
const prev = setCurrentSub(undefined);
91+
const val = this.get();
92+
setCurrentSub(prev);
93+
return val;
94+
},
8595
};
8696
}
8797

0 commit comments

Comments
 (0)