refactor(core): Split consumerBefore/AfterComputation (#62549)

This makes it possible to batch effects, where we can "reopen" consumers
during initial render and then finalize them after we are finally done
adding all the effects to a batch:

```
function createBatch() {
  const effect = // ... create effect node
  resetConsumerBeforeComputation(effect);
  return effect;
}

// pseudo-code
function appendEffect(effectBatch, updater) {
  if (value is a signal) {
    const prevConsumer = setActiveConsumer(effectBatch.node);
    const output = value();
    setActiveConsumer(prevConsumer);
    effectBatch.push({ signal, updater });
    return output;
  }
}

function finalizeBatch(effectBatch) {
  if (effectBatch.length > 0) {
    finalizeConsumerAfterComputation(effectBatch.node);
  }
}

const effectBatch = createBatchEffectNode();
appendEffect(signal1, (newValue) => /* something */);
appendEffect(signal2, (newValue) => /* something different */);
finalizeBatch(effectBatch);
```

PR Close #62549
This commit is contained in:
Rocky Meza
2025-07-09 11:28:35 -04:00
committed by Jessica Janiuk
parent a8fae2aae0
commit 663f48cfc1
10 changed files with 52 additions and 9 deletions
@@ -66,6 +66,9 @@ export function createWatch(fn: (onCleanup: WatchCleanupRegisterFn) => void, sch
// @public
export function defaultEquals<T>(a: T, b: T): boolean;
// @public
export function finalizeConsumerAfterComputation(node: ReactiveNode): void;
// @public (undocumented)
export function getActiveConsumer(): ReactiveNode | null;
@@ -151,6 +154,9 @@ export interface ReactiveNode {
version: Version;
}
// @public
export function resetConsumerBeforeComputation(node: ReactiveNode): void;
// @public (undocumented)
export function runEffect(node: BaseEffectNode): void;
@@ -28,6 +28,7 @@ export {
consumerDestroy,
consumerMarkDirty,
consumerPollProducersForChange,
finalizeConsumerAfterComputation,
getActiveConsumer,
isInNotificationPhase,
isReactive,
@@ -37,6 +38,7 @@ export {
producerNotifyConsumers,
producerUpdateValueVersion,
producerUpdatesAllowed,
resetConsumerBeforeComputation,
runPostProducerCreatedFn,
setActiveConsumer,
setPostProducerCreatedFn,
+30 -9
View File
@@ -359,21 +359,34 @@ export function producerMarkClean(node: ReactiveNode): void {
}
/**
* Prepare this consumer to run a computation in its reactive context.
* Prepare this consumer to run a computation in its reactive context and set
* it as the active consumer.
*
* Must be called by subclasses which represent reactive computations, before those computations
* begin.
*/
export function consumerBeforeComputation(node: ReactiveNode | null): ReactiveNode | null {
if (node) {
node.producersTail = undefined;
node.recomputing = true;
}
if (node) resetConsumerBeforeComputation(node);
return setActiveConsumer(node);
}
/**
* Finalize this consumer's state after a reactive computation has run.
* Prepare this consumer to run a computation in its reactive context.
*
* We expose this mainly for code where we manually batch effects into a single
* consumer. In those cases we may wish to "reopen" a consumer multiple times
* in initial render before finalizing it. Most code should just call
* `consumerBeforeComputation` instead of calling this directly.
*/
export function resetConsumerBeforeComputation(node: ReactiveNode): void {
node.producersTail = undefined;
node.recomputing = true;
}
/**
* Finalize this consumer's state and set previous consumer as the active consumer after a
* reactive computation has run.
*
* Must be called by subclasses which represent reactive computations, after those computations
* have finished.
@@ -384,10 +397,18 @@ export function consumerAfterComputation(
): void {
setActiveConsumer(prevConsumer);
if (!node) {
return;
}
if (node) finalizeConsumerAfterComputation(node);
}
/**
* Finalize this consumer's state after a reactive computation has run.
*
* We expose this mainly for code where we manually batch effects into a single
* consumer. In those cases we may wish to "reopen" a consumer multiple times
* in initial render before finalizing it. Most code should just call
* `consumerAfterComputation` instead of calling this directly.
*/
export function finalizeConsumerAfterComputation(node: ReactiveNode): void {
node.recomputing = false;
// We've finished incrementally rebuilding the producers list, now if there are any producers
@@ -549,6 +549,7 @@
"extractDirectiveDef",
"extractStyleParams",
"filterNonAnimatableStyles",
"finalizeConsumerAfterComputation",
"findAttrIndexInNode",
"findDirectiveDefMatches",
"flattenGroupPlayers",
@@ -850,6 +851,7 @@
"replacePostStylesAsPre",
"reportUnhandledError",
"requiresRefreshOrTraversal",
"resetConsumerBeforeComputation",
"resetPreOrderHookFlags",
"resolveDirectives",
"resolveElementFromTarget",
@@ -468,6 +468,7 @@
"extractAttrsAndClassesFromSelector",
"extractDefListOrFactory",
"extractDirectiveDef",
"finalizeConsumerAfterComputation",
"findAttrIndexInNode",
"findDirectiveDefMatches",
"findMatchingDehydratedViewForDeferBlock",
@@ -722,6 +723,7 @@
"renderView",
"reportUnhandledError",
"requiresRefreshOrTraversal",
"resetConsumerBeforeComputation",
"resetPreOrderHookFlags",
"resolveDirectives",
"resolveForwardRef",
@@ -605,6 +605,7 @@
"extractDefListOrFactory",
"extractDirectiveDef",
"fillProperties",
"finalizeConsumerAfterComputation",
"findAttrIndexInNode",
"findDirectiveDefMatches",
"findExistingListener",
@@ -982,6 +983,7 @@
"requiredTrueValidator",
"requiredValidator",
"requiresRefreshOrTraversal",
"resetConsumerBeforeComputation",
"resetPreOrderHookFlags",
"resolveComponentResources",
"resolveDirectives",
@@ -604,6 +604,7 @@
"extractDefListOrFactory",
"extractDirectiveDef",
"fillProperties",
"finalizeConsumerAfterComputation",
"findAttrIndexInNode",
"findDirectiveDefMatches",
"findExistingListener",
@@ -975,6 +976,7 @@
"reportUnhandledError",
"requiredValidator",
"requiresRefreshOrTraversal",
"resetConsumerBeforeComputation",
"resetPreOrderHookFlags",
"resolveComponentResources",
"resolveDirectives",
@@ -485,6 +485,7 @@
"extractAttrsAndClassesFromSelector",
"extractDefListOrFactory",
"extractDirectiveDef",
"finalizeConsumerAfterComputation",
"findAndReconcileMatchingDehydratedViewsImpl",
"findMatchingDehydratedView",
"findMatchingDehydratedViewImpl",
@@ -764,6 +765,7 @@
"renderView",
"reportUnhandledError",
"requiresRefreshOrTraversal",
"resetConsumerBeforeComputation",
"resetPreOrderHookFlags",
"resolveDirectives",
"resolveForwardRef",
@@ -719,6 +719,7 @@
"extractDirectiveDef",
"filter",
"finalize",
"finalizeConsumerAfterComputation",
"findAttrIndexInNode",
"findDirectiveDefMatches",
"findExistingListener",
@@ -1116,6 +1117,7 @@
"replaceSegment",
"reportUnhandledError",
"requiresRefreshOrTraversal",
"resetConsumerBeforeComputation",
"resetPreOrderHookFlags",
"resolveData",
"resolveDirectives",
@@ -399,6 +399,7 @@
"extractAttrsAndClassesFromSelector",
"extractDefListOrFactory",
"extractDirectiveDef",
"finalizeConsumerAfterComputation",
"forEachSingleProvider",
"forkInnerZoneWithAngularBehavior",
"formatErrorMessage",
@@ -616,6 +617,7 @@
"renderView",
"reportUnhandledError",
"requiresRefreshOrTraversal",
"resetConsumerBeforeComputation",
"resetPreOrderHookFlags",
"resolveDirectives",
"resolveForwardRef",