[Flight] Add getCacheForType() to the dispatcher (#20315)

* Remove react/unstable_cache

We're probably going to make it available via the dispatcher. Let's remove this for now.

* Add readContext() to the dispatcher

On the server, it will be per-request.

On the client, there will be some way to shadow it.

For now, I provide it on the server, and throw on the client.

* Use readContext() from react-fetch

This makes it work on the server (but not on the client until we implement it there.)

Updated the test to use Server Components. Now it passes.

* Fixture: Add fetch from a Server Component

* readCache -> getCacheForType<T>

* Add React.unstable_getCacheForType

* Add a feature flag

* Fix Flow

* Add react-suspense-test-utils and port tests

* Remove extra Map lookup

* Unroll async/await because build system

* Add some error coverage and retry

* Add unstable_getCacheForType to Flight entry
This commit is contained in:
Dan Abramov 2020-12-03 03:44:56 +00:00 committed by GitHub
parent 555eeae33d
commit e23673b511
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 363 additions and 156 deletions

View File

@ -17,13 +17,28 @@ const app = express();
// Application
app.get('/', function(req, res) {
if (process.env.NODE_ENV === 'development') {
for (var key in require.cache) {
delete require.cache[key];
}
// This doesn't work in ESM mode.
// for (var key in require.cache) {
// delete require.cache[key];
// }
}
require('./handler.server.js')(req, res);
});
app.get('/todos', function(req, res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json([
{
id: 1,
text: 'Shave yaks',
},
{
id: 2,
text: 'Eat kale',
},
]);
});
app.listen(3001, () => {
console.log('Flight Server listening on port 3001...');
});

View File

@ -1,4 +1,5 @@
import * as React from 'react';
import {fetch} from 'react-fetch';
import Container from './Container.js';
@ -8,11 +9,17 @@ import {Counter as Counter2} from './Counter2.client.js';
import ShowMore from './ShowMore.client.js';
export default function App() {
const todos = fetch('http://localhost:3001/todos').json();
return (
<Container>
<h1>Hello, world</h1>
<Counter />
<Counter2 />
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
<ShowMore>
<p>Lorem ipsum</p>
</ShowMore>

View File

@ -23,6 +23,7 @@ import type {OpaqueIDType} from 'react-reconciler/src/ReactFiberHostConfig';
import {NoMode} from 'react-reconciler/src/ReactTypeOfMode';
import ErrorStackParser from 'error-stack-parser';
import invariant from 'shared/invariant';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols';
import {
@ -100,6 +101,10 @@ function nextHook(): null | Hook {
return hook;
}
function getCacheForType<T>(resourceType: () => T): T {
invariant(false, 'Not implemented.');
}
function readContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
@ -298,6 +303,7 @@ function useOpaqueIdentifier(): OpaqueIDType | void {
}
const Dispatcher: DispatcherType = {
getCacheForType,
readContext,
useCallback,
useContext,

View File

@ -20,6 +20,7 @@ import type PartialRenderer from './ReactPartialRenderer';
import {validateContextBounds} from './ReactPartialRendererContext';
import invariant from 'shared/invariant';
import {enableCache} from 'shared/ReactFeatureFlags';
import is from 'shared/objectIs';
type BasicStateAction<S> = (S => S) | S;
@ -214,6 +215,10 @@ export function resetHooksState(): void {
workInProgressHook = null;
}
function getCacheForType<T>(resourceType: () => T): T {
invariant(false, 'Not implemented.');
}
function readContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
@ -512,3 +517,7 @@ export const Dispatcher: DispatcherType = {
// Subscriptions are not setup in a server environment.
useMutableSource,
};
if (enableCache) {
Dispatcher.getCacheForType = getCacheForType;
}

View File

@ -9,7 +9,7 @@
import type {Wakeable} from 'shared/ReactTypes';
import {readCache} from 'react/unstable-cache';
import {unstable_getCacheForType} from 'react';
const Pending = 0;
const Resolved = 1;
@ -34,16 +34,13 @@ type Result = PendingResult | ResolvedResult | RejectedResult;
// TODO: this is a browser-only version. Add a separate Node entry point.
const nativeFetch = window.fetch;
const fetchKey = {};
function readResultMap(): Map<string, Result> {
const resources = readCache().resources;
let map = resources.get(fetchKey);
if (map === undefined) {
map = new Map();
resources.set(fetchKey, map);
}
return map;
function getResultMap(): Map<string, Result> {
return unstable_getCacheForType(createResultMap);
}
function createResultMap(): Map<string, Result> {
return new Map();
}
function toResult(thenable): Result {
@ -120,7 +117,7 @@ Response.prototype = {
};
function preloadResult(url: string, options: mixed): Result {
const map = readResultMap();
const map = getResultMap();
let entry = map.get(url);
if (!entry) {
if (options) {

View File

@ -11,8 +11,7 @@ import type {Wakeable} from 'shared/ReactTypes';
import * as http from 'http';
import * as https from 'https';
import {readCache} from 'react/unstable-cache';
import {unstable_getCacheForType} from 'react';
type FetchResponse = {|
// Properties
@ -75,16 +74,12 @@ type RejectedResult = {|
type Result<V> = PendingResult | ResolvedResult<V> | RejectedResult;
const fetchKey = {};
function getResultMap(): Map<string, Result<FetchResponse>> {
return unstable_getCacheForType(createResultMap);
}
function readResultMap(): Map<string, Result<FetchResponse>> {
const resources = readCache().resources;
let map = resources.get(fetchKey);
if (map === undefined) {
map = new Map();
resources.set(fetchKey, map);
}
return map;
function createResultMap(): Map<string, Result<FetchResponse>> {
return new Map();
}
function readResult<T>(result: Result<T>): T {
@ -166,7 +161,7 @@ Response.prototype = {
};
function preloadResult(url: string, options: mixed): Result<FetchResponse> {
const map = readResultMap();
const map = getResultMap();
let entry = map.get(url);
if (!entry) {
if (options) {

View File

@ -10,30 +10,28 @@
'use strict';
describe('ReactFetchNode', () => {
let ReactCache;
let ReactFetchNode;
let http;
let fetch;
let waitForSuspense;
let server;
let serverEndpoint;
let serverImpl;
beforeEach(done => {
jest.resetModules();
if (__EXPERIMENTAL__) {
ReactCache = require('react/unstable-cache');
// TODO: A way to pass load context.
ReactCache.CacheProvider._context._currentValue = ReactCache.createCache();
ReactFetchNode = require('react-fetch');
fetch = ReactFetchNode.fetch;
}
fetch = require('react-fetch').fetch;
http = require('http');
waitForSuspense = require('react-suspense-test-utils').waitForSuspense;
server = http.createServer((req, res) => {
serverImpl(req, res);
});
server.listen(done);
serverEndpoint = `http://localhost:${server.address().port}/`;
serverEndpoint = null;
server.listen(() => {
serverEndpoint = `http://localhost:${server.address().port}/`;
done();
});
});
afterEach(done => {
@ -41,55 +39,83 @@ describe('ReactFetchNode', () => {
server = null;
});
async function waitForSuspense(fn) {
while (true) {
try {
return fn();
} catch (promise) {
if (typeof promise.then === 'function') {
await promise;
} else {
throw promise;
}
}
}
}
// @gate experimental
it('can read text', async () => {
it('can fetch text from a server component', async () => {
serverImpl = (req, res) => {
res.write('ok');
res.write('mango');
res.end();
};
await waitForSuspense(() => {
const response = fetch(serverEndpoint);
expect(response.status).toBe(200);
expect(response.statusText).toBe('OK');
expect(response.ok).toBe(true);
expect(response.text()).toEqual('ok');
// Can read again:
expect(response.text()).toEqual('ok');
const text = await waitForSuspense(() => {
return fetch(serverEndpoint).text();
});
expect(text).toEqual('mango');
});
// @gate experimental
it('can read json', async () => {
it('can fetch json from a server component', async () => {
serverImpl = (req, res) => {
res.write(JSON.stringify({name: 'Sema'}));
res.end();
};
await waitForSuspense(() => {
const response = fetch(serverEndpoint);
expect(response.status).toBe(200);
expect(response.statusText).toBe('OK');
expect(response.ok).toBe(true);
expect(response.json()).toEqual({
name: 'Sema',
});
// Can read again:
expect(response.json()).toEqual({
name: 'Sema',
});
const json = await waitForSuspense(() => {
return fetch(serverEndpoint).json();
});
expect(json).toEqual({name: 'Sema'});
});
// @gate experimental
it('provides response status', async () => {
serverImpl = (req, res) => {
res.write(JSON.stringify({name: 'Sema'}));
res.end();
};
const response = await waitForSuspense(() => {
return fetch(serverEndpoint);
});
expect(response).toMatchObject({
status: 200,
statusText: 'OK',
ok: true,
});
});
// @gate experimental
it('handles different paths', async () => {
serverImpl = (req, res) => {
switch (req.url) {
case '/banana':
res.write('banana');
break;
case '/mango':
res.write('mango');
break;
case '/orange':
res.write('orange');
break;
}
res.end();
};
const outputs = await waitForSuspense(() => {
return [
fetch(serverEndpoint + 'banana').text(),
fetch(serverEndpoint + 'mango').text(),
fetch(serverEndpoint + 'orange').text(),
];
});
expect(outputs).toMatchObject(['banana', 'mango', 'orange']);
});
// @gate experimental
it('can produce an error', async () => {
serverImpl = (req, res) => {};
expect.assertions(1);
try {
await waitForSuspense(() => {
return fetch('BOOM');
});
} catch (err) {
expect(err.message).toEqual('Invalid URL: BOOM');
}
});
});

View File

@ -25,6 +25,7 @@ import {
enableDebugTracing,
enableSchedulingProfiler,
enableNewReconciler,
enableCache,
decoupleUpdatePriorityFromScheduler,
enableUseRefAccessWarning,
} from 'shared/ReactFeatureFlags';
@ -1815,6 +1816,10 @@ function dispatchAction<S, A>(
}
}
function getCacheForType<T>(resourceType: () => T): T {
invariant(false, 'Not implemented.');
}
export const ContextOnlyDispatcher: Dispatcher = {
readContext,
@ -1835,6 +1840,9 @@ export const ContextOnlyDispatcher: Dispatcher = {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(ContextOnlyDispatcher: Dispatcher).getCacheForType = getCacheForType;
}
const HooksDispatcherOnMount: Dispatcher = {
readContext,
@ -1877,6 +1885,9 @@ const HooksDispatcherOnUpdate: Dispatcher = {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnUpdate: Dispatcher).getCacheForType = getCacheForType;
}
const HooksDispatcherOnRerender: Dispatcher = {
readContext,
@ -1898,6 +1909,9 @@ const HooksDispatcherOnRerender: Dispatcher = {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnRerender: Dispatcher).getCacheForType = getCacheForType;
}
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
@ -2052,6 +2066,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnMountInDEV: Dispatcher).getCacheForType = getCacheForType;
}
HooksDispatcherOnMountWithHookTypesInDEV = {
readContext<T>(
@ -2174,6 +2191,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).getCacheForType = getCacheForType;
}
HooksDispatcherOnUpdateInDEV = {
readContext<T>(
@ -2296,6 +2316,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).getCacheForType = getCacheForType;
}
HooksDispatcherOnRerenderInDEV = {
readContext<T>(
@ -2419,6 +2442,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).getCacheForType = getCacheForType;
}
InvalidNestedHooksDispatcherOnMountInDEV = {
readContext<T>(
@ -2556,6 +2582,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).getCacheForType = getCacheForType;
}
InvalidNestedHooksDispatcherOnUpdateInDEV = {
readContext<T>(
@ -2693,6 +2722,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).getCacheForType = getCacheForType;
}
InvalidNestedHooksDispatcherOnRerenderInDEV = {
readContext<T>(
@ -2831,4 +2863,7 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).getCacheForType = getCacheForType;
}
}

View File

@ -25,6 +25,7 @@ import {
enableDebugTracing,
enableSchedulingProfiler,
enableNewReconciler,
enableCache,
decoupleUpdatePriorityFromScheduler,
enableUseRefAccessWarning,
} from 'shared/ReactFeatureFlags';
@ -1815,6 +1816,10 @@ function dispatchAction<S, A>(
}
}
function getCacheForType<T>(resourceType: () => T): T {
invariant(false, 'Not implemented.');
}
export const ContextOnlyDispatcher: Dispatcher = {
readContext,
@ -1835,6 +1840,9 @@ export const ContextOnlyDispatcher: Dispatcher = {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(ContextOnlyDispatcher: Dispatcher).getCacheForType = getCacheForType;
}
const HooksDispatcherOnMount: Dispatcher = {
readContext,
@ -1856,6 +1864,9 @@ const HooksDispatcherOnMount: Dispatcher = {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnMount: Dispatcher).getCacheForType = getCacheForType;
}
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
@ -1877,6 +1888,9 @@ const HooksDispatcherOnUpdate: Dispatcher = {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnUpdate: Dispatcher).getCacheForType = getCacheForType;
}
const HooksDispatcherOnRerender: Dispatcher = {
readContext,
@ -1898,6 +1912,9 @@ const HooksDispatcherOnRerender: Dispatcher = {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnRerender: Dispatcher).getCacheForType = getCacheForType;
}
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
@ -2052,6 +2069,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnMountInDEV: Dispatcher).getCacheForType = getCacheForType;
}
HooksDispatcherOnMountWithHookTypesInDEV = {
readContext<T>(
@ -2174,6 +2194,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).getCacheForType = getCacheForType;
}
HooksDispatcherOnUpdateInDEV = {
readContext<T>(
@ -2296,6 +2319,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).getCacheForType = getCacheForType;
}
HooksDispatcherOnRerenderInDEV = {
readContext<T>(
@ -2419,6 +2445,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).getCacheForType = getCacheForType;
}
InvalidNestedHooksDispatcherOnMountInDEV = {
readContext<T>(
@ -2556,6 +2585,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).getCacheForType = getCacheForType;
}
InvalidNestedHooksDispatcherOnUpdateInDEV = {
readContext<T>(
@ -2693,6 +2725,9 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).getCacheForType = getCacheForType;
}
InvalidNestedHooksDispatcherOnRerenderInDEV = {
readContext<T>(
@ -2831,4 +2866,7 @@ if (__DEV__) {
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).getCacheForType = getCacheForType;
}
}

View File

@ -274,6 +274,7 @@ type BasicStateAction<S> = (S => S) | S;
type Dispatch<A> = A => void;
export type Dispatcher = {|
getCacheForType?: <T>(resourceType: () => T) => T,
readContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,

View File

@ -74,6 +74,7 @@ type Segment = {
export type Request = {
destination: Destination,
bundlerConfig: BundlerConfig,
cache: Map<Function, mixed>,
nextChunkId: number,
pendingChunks: number,
pingedSegments: Array<Segment>,
@ -97,6 +98,7 @@ export function createRequest(
const request = {
destination,
bundlerConfig,
cache: new Map(),
nextChunkId: 0,
pendingChunks: 0,
pingedSegments: pingedSegments,
@ -652,7 +654,9 @@ function retrySegment(request: Request, segment: Segment): void {
function performWork(request: Request): void {
const prevDispatcher = ReactCurrentDispatcher.current;
const prevCache = currentCache;
ReactCurrentDispatcher.current = Dispatcher;
currentCache = request.cache;
const pingedSegments = request.pingedSegments;
request.pingedSegments = [];
@ -665,6 +669,7 @@ function performWork(request: Request): void {
}
ReactCurrentDispatcher.current = prevDispatcher;
currentCache = prevCache;
}
let reentrant = false;
@ -743,6 +748,8 @@ function unsupportedHook(): void {
invariant(false, 'This Hook is not supported in Server Components.');
}
let currentCache: Map<Function, mixed> | null = null;
const Dispatcher: DispatcherType = {
useMemo<T>(nextCreate: () => T): T {
return nextCreate();
@ -757,6 +764,19 @@ const Dispatcher: DispatcherType = {
useTransition(): [(callback: () => void) => void, boolean] {
return [() => {}, false];
},
getCacheForType<T>(resourceType: () => T): T {
invariant(
currentCache,
'Reading the cache is only supported while rendering.',
);
let entry: T | void = (currentCache.get(resourceType): any);
if (entry === undefined) {
entry = resourceType();
// TODO: Warn if undefined?
currentCache.set(resourceType, entry);
}
return entry;
},
readContext: (unsupportedHook: any),
useContext: (unsupportedHook: any),
useReducer: (unsupportedHook: any),

View File

@ -0,0 +1,12 @@
# react-suspense-test-utils
This package is meant to be used alongside yet-to-be-released, experimental React features. It's unlikely to be useful in any other context.
**Do not use in a real application.** We're publishing this early for
demonstration purposes.
**Use it at your own risk.**
# No, Really, It Is Unstable
The API ~~may~~ will change wildly between versions.

View File

@ -6,4 +6,5 @@
*
* @flow
*/
export {createCache, readCache, CacheProvider} from './src/cache/ReactCache';
export * from './src/ReactSuspenseTestUtils';

View File

@ -0,0 +1,3 @@
'use strict';
module.exports = require('./cjs/react-suspense-test-utils.js');

View File

@ -0,0 +1,20 @@
{
"name": "react-suspense-test-utils",
"version": "0.1.0",
"private": true,
"repository": {
"type" : "git",
"url" : "https://github.com/facebook/react.git",
"directory": "packages/react-suspense-test-utils"
},
"license": "MIT",
"files": [
"LICENSE",
"README.md",
"index.js",
"cjs/"
],
"peerDependencies": {
"react": "^17.0.0"
}
}

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import invariant from 'shared/invariant';
const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
function unsupported() {
invariant(false, 'This feature is not supported by ReactSuspenseTestUtils.');
}
export function waitForSuspense<T>(fn: () => T): Promise<T> {
const cache: Map<Function, mixed> = new Map();
const testDispatcher: Dispatcher = {
getCacheForType<R>(resourceType: () => R): R {
let entry: R | void = (cache.get(resourceType): any);
if (entry === undefined) {
entry = resourceType();
// TODO: Warn if undefined?
cache.set(resourceType, entry);
}
return entry;
},
readContext: unsupported,
useContext: unsupported,
useMemo: unsupported,
useReducer: unsupported,
useRef: unsupported,
useState: unsupported,
useLayoutEffect: unsupported,
useCallback: unsupported,
useImperativeHandle: unsupported,
useEffect: unsupported,
useDebugValue: unsupported,
useDeferredValue: unsupported,
useTransition: unsupported,
useOpaqueIdentifier: unsupported,
useMutableSource: unsupported,
};
// Not using async/await because we don't compile it.
return new Promise((resolve, reject) => {
function retry() {
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = testDispatcher;
try {
const result = fn();
resolve(result);
} catch (thrownValue) {
if (typeof thrownValue.then === 'function') {
thrownValue.then(retry, retry);
} else {
reject(thrownValue);
}
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
}
retry();
});
}

View File

@ -50,6 +50,7 @@ export {
startTransition as unstable_startTransition,
SuspenseList,
SuspenseList as unstable_SuspenseList,
unstable_getCacheForType,
// enableScopeAPI
unstable_Scope,
unstable_useOpaqueIdentifier,

View File

@ -45,6 +45,7 @@ export {
startTransition as unstable_startTransition,
SuspenseList as unstable_SuspenseList,
unstable_useOpaqueIdentifier,
unstable_getCacheForType,
// enableDebugTracing
unstable_DebugTracingMode,
} from './src/React';

View File

@ -82,4 +82,5 @@ export {
unstable_createFundamental,
unstable_Scope,
unstable_useOpaqueIdentifier,
unstable_getCacheForType,
} from './src/React';

View File

@ -49,6 +49,7 @@ export {
startTransition as unstable_startTransition,
SuspenseList,
SuspenseList as unstable_SuspenseList,
unstable_getCacheForType,
// enableScopeAPI
unstable_Scope,
unstable_useOpaqueIdentifier,

View File

@ -17,8 +17,7 @@
"umd/",
"jsx-runtime.js",
"jsx-dev-runtime.js",
"unstable-index.server.js",
"unstable-cache.js"
"unstable-index.server.js"
],
"main": "index.js",
"exports": {

View File

@ -33,6 +33,7 @@ import {lazy} from './ReactLazy';
import {forwardRef} from './ReactForwardRef';
import {memo} from './ReactMemo';
import {
getCacheForType,
useCallback,
useContext,
useEffect,
@ -110,6 +111,7 @@ export {
useDeferredValue,
REACT_SUSPENSE_LIST_TYPE as SuspenseList,
REACT_LEGACY_HIDDEN_TYPE as unstable_LegacyHidden,
getCacheForType as unstable_getCacheForType,
// enableFundamentalAPI
createFundamental as unstable_createFundamental,
// enableScopeAPI

View File

@ -36,6 +36,12 @@ function resolveDispatcher() {
return dispatcher;
}
export function getCacheForType<T>(resourceType: () => T): T {
const dispatcher = resolveDispatcher();
// $FlowFixMe This is unstable, thus optional
return dispatcher.getCacheForType(resourceType);
}
export function useContext<T>(
Context: ReactContext<T>,
unstable_observedBits: number | boolean | void,

View File

@ -1,26 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
describe('ReactCache', () => {
let ReactCache;
beforeEach(() => {
if (__EXPERIMENTAL__) {
ReactCache = require('react/unstable-cache');
}
});
// TODO: test something useful.
// @gate experimental
it('exports something', () => {
expect(ReactCache.readCache).not.toBe(undefined);
});
});

View File

@ -1,43 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @flow
*/
import type {ReactContext} from 'shared/ReactTypes';
import {createContext} from 'react';
import invariant from 'shared/invariant';
type Cache = {|
resources: Map<any, any>,
|};
// TODO: should there be a default cache?
const CacheContext: ReactContext<null | Cache> = createContext(null);
function CacheImpl() {
this.resources = new Map();
// TODO: cancellation token.
}
function createCache(): Cache {
// $FlowFixMe
return new CacheImpl();
}
function readCache(): Cache {
// TODO: this doesn't subscribe.
// But we really want load context anyway.
const value = CacheContext._currentValue;
if (value instanceof CacheImpl) {
return value;
}
invariant(false, 'Could not read the cache.');
}
const CacheProvider = CacheContext.Provider;
export {createCache, readCache, CacheProvider};

View File

@ -32,6 +32,7 @@ export {
useDeferredValue as unstable_useDeferredValue,
SuspenseList as unstable_SuspenseList,
unstable_useOpaqueIdentifier,
unstable_getCacheForType,
// enableDebugTracing
unstable_DebugTracingMode,
} from './src/React';

View File

@ -52,6 +52,7 @@ export const enableSelectiveHydration = __EXPERIMENTAL__;
// Flight experiments
export const enableLazyElements = __EXPERIMENTAL__;
export const enableCache = __EXPERIMENTAL__;
// Only used in www builds.
export const enableSchedulerDebugging = false;

View File

@ -21,6 +21,7 @@ export const enableSchedulerTracing = __PROFILE__;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;
export const enableLazyElements = false;
export const enableCache = false;
export const enableSchedulerDebugging = false;
export const debugRenderPhaseSideEffectsForStrictMode = true;
export const disableJavaScriptURLs = false;

View File

@ -23,6 +23,7 @@ export const enableSchedulerTracing = __PROFILE__;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;
export const enableLazyElements = false;
export const enableCache = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;

View File

@ -23,6 +23,7 @@ export const enableSchedulerTracing = __PROFILE__;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;
export const enableLazyElements = false;
export const enableCache = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;

View File

@ -23,6 +23,7 @@ export const enableSchedulerTracing = __PROFILE__;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;
export const enableLazyElements = false;
export const enableCache = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;

View File

@ -23,6 +23,7 @@ export const enableSchedulerTracing = __PROFILE__;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;
export const enableLazyElements = false;
export const enableCache = false;
export const enableSchedulerDebugging = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;

View File

@ -23,6 +23,7 @@ export const enableSchedulerTracing = __PROFILE__;
export const enableSuspenseServerRenderer = false;
export const enableSelectiveHydration = false;
export const enableLazyElements = false;
export const enableCache = false;
export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;

View File

@ -23,6 +23,7 @@ export const enableSchedulerTracing = false;
export const enableSuspenseServerRenderer = true;
export const enableSelectiveHydration = true;
export const enableLazyElements = false;
export const enableCache = false;
export const disableJavaScriptURLs = true;
export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;

View File

@ -59,6 +59,7 @@ export const enableSuspenseServerRenderer = true;
export const enableSelectiveHydration = true;
export const enableLazyElements = true;
export const enableCache = true;
export const disableJavaScriptURLs = true;

View File

@ -367,5 +367,7 @@
"376": "Only global symbols received from Symbol.for(...) can be passed to client components. The symbol Symbol.for(%s) cannot be found among global symbols. Remove %s from this object, or avoid the entire object: %s",
"377": "BigInt (%s) is not yet supported in client component props. Remove %s from this object or use a plain number instead: %s",
"378": "Type %s is not supported in client component props. Remove %s from this object, or avoid the entire object: %s",
"379": "Refs cannot be used in server components, nor passed to client components."
"379": "Refs cannot be used in server components, nor passed to client components.",
"380": "Reading the cache is only supported while rendering.",
"381": "This feature is not supported by ReactSuspenseTestUtils."
}

View File

@ -135,15 +135,6 @@ const bundles = [
externals: ['react'],
},
/******* React Cache (experimental, new) *******/
{
bundleTypes: __EXPERIMENTAL__ ? [NODE_DEV, NODE_PROD, NODE_PROFILING] : [],
moduleType: ISOMORPHIC,
entry: 'react/unstable-cache',
global: 'ReactCache',
externals: ['react'],
},
/******* React Fetch Browser (experimental, new) *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
@ -366,6 +357,15 @@ const bundles = [
],
},
/******* React Suspense Test Utils *******/
{
bundleTypes: [NODE_ES2015],
moduleType: RENDERER_UTILS,
entry: 'react-suspense-test-utils',
global: 'ReactSuspenseTestUtils',
externals: ['react'],
},
/******* React ART *******/
{
bundleTypes: [