Skip to content

Commit 98a2a78

Browse files
committed
fix: notify inner effects in the same order as non-inner effects
1 parent 4983199 commit 98a2a78

File tree

2 files changed

+52
-57
lines changed

2 files changed

+52
-57
lines changed

src/index.ts

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ interface Signal<T = any> extends ReactiveNode {
1616
pendingValue: T;
1717
}
1818

19-
const queuedEffects: (Effect | EffectScope | undefined)[] = [];
19+
const queued: (Effect | EffectScope | undefined)[] = [];
2020
const {
2121
link,
2222
unlink,
@@ -31,7 +31,28 @@ const {
3131
return updateSignal(node as Signal);
3232
}
3333
},
34-
notify,
34+
notify(effect: Effect | EffectScope) {
35+
let flags = effect.flags;
36+
let insertIndex = queuedLength;
37+
let firstInsertedIndex = insertIndex;
38+
39+
do {
40+
effect.flags = flags & ~(2 satisfies ReactiveFlags.Watching) | 64 /* Queued */;
41+
queued[insertIndex++] = effect;
42+
effect = effect.subs?.sub as Effect | EffectScope;
43+
if (effect === undefined || (flags = effect.flags) & 64 /* Queued */) {
44+
break;
45+
}
46+
} while (true);
47+
48+
queuedLength = insertIndex;
49+
50+
while (firstInsertedIndex < insertIndex - 1) {
51+
const left = queued[firstInsertedIndex];
52+
queued[firstInsertedIndex++] = queued[insertIndex - 1];
53+
queued[--insertIndex] = left;
54+
}
55+
},
3556
unwatched(node: Signal | Computed | Effect | EffectScope) {
3657
if (!(node.flags & 1 satisfies ReactiveFlags.Mutable)) {
3758
effectScopeOper.call(node);
@@ -46,7 +67,7 @@ const {
4667
let cycle = 0;
4768
let batchDepth = 0;
4869
let notifyIndex = 0;
49-
let queuedEffectsLength = 0;
70+
let queuedLength = 0;
5071
let activeSub: ReactiveNode | undefined;
5172

5273
export function getActiveSub(): ReactiveNode | undefined {
@@ -183,19 +204,6 @@ function updateSignal(s: Signal): boolean {
183204
return s.currentValue !== (s.currentValue = s.pendingValue);
184205
}
185206

186-
function notify(e: Effect | EffectScope) {
187-
const flags = e.flags;
188-
if (!(flags & 64 /* Queued */)) {
189-
e.flags = flags | 64 /* Queued */;
190-
const subs = e.subs;
191-
if (subs !== undefined) {
192-
notify(subs.sub as Effect | EffectScope);
193-
} else {
194-
queuedEffects[queuedEffectsLength++] = e;
195-
}
196-
}
197-
}
198-
199207
function run(e: Effect | EffectScope, flags: ReactiveFlags): void {
200208
if (
201209
flags & 16 satisfies ReactiveFlags.Dirty
@@ -218,27 +226,17 @@ function run(e: Effect | EffectScope, flags: ReactiveFlags): void {
218226
e.flags &= ~(4 satisfies ReactiveFlags.RecursedCheck);
219227
purgeDeps(e);
220228
}
221-
} else {
222-
let link = e.deps;
223-
while (link !== undefined) {
224-
const dep = link.dep;
225-
const depFlags = dep.flags;
226-
if (depFlags & 64 /* Queued */) {
227-
run(dep, dep.flags = depFlags & ~(64 /* Queued */));
228-
}
229-
link = link.nextDep;
230-
}
231229
}
232230
}
233231

234232
function flush(): void {
235-
while (notifyIndex < queuedEffectsLength) {
236-
const effect = queuedEffects[notifyIndex]!;
237-
queuedEffects[notifyIndex++] = undefined;
233+
while (notifyIndex < queuedLength) {
234+
const effect = queued[notifyIndex]!;
235+
queued[notifyIndex++] = undefined;
238236
run(effect, effect.flags &= ~(64 /* Queued */));
239237
}
240238
notifyIndex = 0;
241-
queuedEffectsLength = 0;
239+
queuedLength = 0;
242240
}
243241

244242
function computedOper<T>(this: Computed<T>): T {

tests/effect.spec.ts

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -81,64 +81,61 @@ test('should not trigger inner effect when resolve maybe dirty', () => {
8181
a(2);
8282
});
8383

84-
test('should trigger inner effects in sequence', () => {
84+
test('should notify inner effects in the same order as non-inner effects', () => {
8585
const a = signal(0);
8686
const b = signal(0);
8787
const c = computed(() => a() - b());
88-
const order: string[] = [];
88+
const order1: string[] = [];
89+
const order2: string[] = [];
90+
const order3: string[] = [];
8991

9092
effect(() => {
91-
c();
93+
order1.push('effect1');
94+
a();
95+
});
96+
effect(() => {
97+
order1.push('effect2');
98+
a();
99+
b();
100+
});
92101

102+
effect(() => {
103+
c();
93104
effect(() => {
94-
order.push('first inner');
105+
order2.push('effect1');
95106
a();
96107
});
97-
98108
effect(() => {
99-
order.push('last inner');
109+
order2.push('effect2');
100110
a();
101111
b();
102112
});
103113
});
104114

105-
order.length = 0;
106-
107-
startBatch();
108-
b(1);
109-
a(1);
110-
endBatch();
111-
112-
expect(order).toEqual(['first inner', 'last inner']);
113-
});
114-
115-
test('should trigger inner effects in sequence in effect scope', () => {
116-
const a = signal(0);
117-
const b = signal(0);
118-
const order: string[] = [];
119-
120115
effectScope(() => {
121-
122116
effect(() => {
123-
order.push('first inner');
117+
order3.push('effect1');
124118
a();
125119
});
126-
127120
effect(() => {
128-
order.push('last inner');
121+
order3.push('effect2');
129122
a();
130123
b();
131124
});
132125
});
133126

134-
order.length = 0;
127+
order1.length = 0;
128+
order2.length = 0;
129+
order3.length = 0;
135130

136131
startBatch();
137132
b(1);
138133
a(1);
139134
endBatch();
140135

141-
expect(order).toEqual(['first inner', 'last inner']);
136+
expect(order1).toEqual(['effect2', 'effect1']);
137+
expect(order2).toEqual(order1);
138+
expect(order3).toEqual(order1);
142139
});
143140

144141
test('should custom effect support batch', () => {

0 commit comments

Comments
 (0)