Skip to content

Commit

Permalink
Use same cache for all new data in a single update
Browse files Browse the repository at this point in the history
If multiple Cache boundaries mount at the same time, they should use the
same cache, even if they are in totally separate trees.

The plan is to extend this further so that we keep reusing the same
cache for all incoming updates until one of them finishes. (Not yet
implemented.)
  • Loading branch information
acdlite committed Dec 14, 2020
1 parent e7ababf commit 299405f
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 0 deletions.
20 changes: 20 additions & 0 deletions packages/react-reconciler/src/ReactFiberLane.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,14 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {

lanes &= ~lane;
}

if (enableCache) {
// Clear the pooled cache so subsequent updates get fresh data.
// TODO: This is very naive and only works if the shell of a cache boundary
// doesn't suspend. The next, key feature is to preserve caches across
// multiple attempts (suspend -> ping) to render a new tree.
root.pooledCache = null;
}
}

export function markRootEntangled(root: FiberRoot, entangledLanes: Lanes) {
Expand All @@ -776,11 +784,23 @@ export function requestFreshCache(root: FiberRoot, renderLanes: Lanes): Cache {
if (!enableCache) {
return (null: any);
}

// Check if there's a pooled cache. This is really just a batching heuristic
// so that two transitions that happen in a similar timeframe can share the
// same cache.
const pooledCache = root.pooledCache;
if (pooledCache !== null) {
return pooledCache;
}

// Create a fresh cache.
const freshCache = {
providers: null,
data: null,
};

// This is now the pooled cache.
root.pooledCache = freshCache;
return freshCache;
}
export function getBumpedLaneForHydration(
Expand Down
20 changes: 20 additions & 0 deletions packages/react-reconciler/src/ReactFiberLane.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,14 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {

lanes &= ~lane;
}

if (enableCache) {
// Clear the pooled cache so subsequent updates get fresh data.
// TODO: This is very naive and only works if the shell of a cache boundary
// doesn't suspend. The next, key feature is to preserve caches across
// multiple attempts (suspend -> ping) to render a new tree.
root.pooledCache = null;
}
}

export function markRootEntangled(root: FiberRoot, entangledLanes: Lanes) {
Expand All @@ -776,11 +784,23 @@ export function requestFreshCache(root: FiberRoot, renderLanes: Lanes): Cache {
if (!enableCache) {
return (null: any);
}

// Check if there's a pooled cache. This is really just a batching heuristic
// so that two transitions that happen in a similar timeframe can share the
// same cache.
const pooledCache = root.pooledCache;
if (pooledCache !== null) {
return pooledCache;
}

// Create a fresh cache.
const freshCache = {
providers: null,
data: null,
};

// This is now the pooled cache.
root.pooledCache = freshCache;
return freshCache;
}
export function getBumpedLaneForHydration(
Expand Down
5 changes: 5 additions & 0 deletions packages/react-reconciler/src/ReactFiberRoot.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import {
enableSchedulerTracing,
enableSuspenseCallback,
enableCache,
} from 'shared/ReactFeatureFlags';
import {unstable_getThreadID} from 'scheduler/tracing';
import {initializeUpdateQueue} from './ReactUpdateQueue.new';
Expand Down Expand Up @@ -52,6 +53,10 @@ function FiberRootNode(containerInfo, tag, hydrate) {
this.entangledLanes = NoLanes;
this.entanglements = createLaneMap(NoLanes);

if (enableCache) {
this.pooledCache = null;
}

if (supportsHydration) {
this.mutableSourceEagerHydrationData = null;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/react-reconciler/src/ReactFiberRoot.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import {
enableSchedulerTracing,
enableSuspenseCallback,
enableCache,
} from 'shared/ReactFeatureFlags';
import {unstable_getThreadID} from 'scheduler/tracing';
import {initializeUpdateQueue} from './ReactUpdateQueue.old';
Expand Down Expand Up @@ -52,6 +53,10 @@ function FiberRootNode(containerInfo, tag, hydrate) {
this.entangledLanes = NoLanes;
this.entanglements = createLaneMap(NoLanes);

if (enableCache) {
this.pooledCache = null;
}

if (supportsHydration) {
this.mutableSourceEagerHydrationData = null;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/react-reconciler/src/ReactInternalTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {RootTag} from './ReactRootTags';
import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig';
import type {Wakeable} from 'shared/ReactTypes';
import type {Interaction} from 'scheduler/src/Tracing';
import type {Cache} from './ReactFiberCacheComponent';

// Unwind Circular: moved from ReactFiberHooks.old
export type HookType =
Expand Down Expand Up @@ -235,6 +236,8 @@ type BaseFiberRootProperties = {|

entangledLanes: Lanes,
entanglements: LaneMap<Lanes>,

pooledCache: Cache | null,
|};

// The following attributes are only used by interaction tracing builds.
Expand Down
39 changes: 39 additions & 0 deletions packages/react-reconciler/src/__tests__/ReactCache-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,43 @@ describe('ReactCache', () => {
expect(Scheduler).toHaveYielded(['A']);
expect(root).toMatchRenderedOutput('A');
});

// @gate experimental
test('multiple new Cache boundaries in the same update share the same, fresh cache', async () => {
function App({text}) {
return (
<>
<Cache>
<Suspense fallback={<Text text="Loading..." />}>
<AsyncText text="A" />
</Suspense>
</Cache>
<Cache>
<Suspense fallback={<Text text="Loading..." />}>
<AsyncText text="A" />
</Suspense>
</Cache>
</>
);
}

const root = ReactNoop.createRoot();
await ReactNoop.act(async () => {
root.render(<App showMore={false} />);
});
// Even though there are two new <Cache /> trees, they should share the same
// data cache. So there should be only a single cache miss for A.
expect(Scheduler).toHaveYielded([
'Cache miss! [A]',
'Loading...',
'Loading...',
]);
expect(root).toMatchRenderedOutput('Loading...Loading...');

await ReactNoop.act(async () => {
await resolveText('A');
});
expect(Scheduler).toHaveYielded(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
});
});

0 comments on commit 299405f

Please sign in to comment.