mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[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:
parent
555eeae33d
commit
e23673b511
|
|
@ -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...');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
19
packages/react-fetch/src/ReactFetchBrowser.js
vendored
19
packages/react-fetch/src/ReactFetchBrowser.js
vendored
|
|
@ -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) {
|
||||
|
|
|
|||
19
packages/react-fetch/src/ReactFetchNode.js
vendored
19
packages/react-fetch/src/ReactFetchNode.js
vendored
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
20
packages/react-server/src/ReactFlightServer.js
vendored
20
packages/react-server/src/ReactFlightServer.js
vendored
|
|
@ -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),
|
||||
|
|
|
|||
12
packages/react-suspense-test-utils/README.md
Normal file
12
packages/react-suspense-test-utils/README.md
Normal 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.
|
||||
|
|
@ -6,4 +6,5 @@
|
|||
*
|
||||
* @flow
|
||||
*/
|
||||
export {createCache, readCache, CacheProvider} from './src/cache/ReactCache';
|
||||
|
||||
export * from './src/ReactSuspenseTestUtils';
|
||||
3
packages/react-suspense-test-utils/npm/index.js
vendored
Normal file
3
packages/react-suspense-test-utils/npm/index.js
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = require('./cjs/react-suspense-test-utils.js');
|
||||
20
packages/react-suspense-test-utils/package.json
Normal file
20
packages/react-suspense-test-utils/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
68
packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js
vendored
Normal file
68
packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js
vendored
Normal 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();
|
||||
});
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ export {
|
|||
startTransition as unstable_startTransition,
|
||||
SuspenseList,
|
||||
SuspenseList as unstable_SuspenseList,
|
||||
unstable_getCacheForType,
|
||||
// enableScopeAPI
|
||||
unstable_Scope,
|
||||
unstable_useOpaqueIdentifier,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ export {
|
|||
startTransition as unstable_startTransition,
|
||||
SuspenseList as unstable_SuspenseList,
|
||||
unstable_useOpaqueIdentifier,
|
||||
unstable_getCacheForType,
|
||||
// enableDebugTracing
|
||||
unstable_DebugTracingMode,
|
||||
} from './src/React';
|
||||
|
|
|
|||
|
|
@ -82,4 +82,5 @@ export {
|
|||
unstable_createFundamental,
|
||||
unstable_Scope,
|
||||
unstable_useOpaqueIdentifier,
|
||||
unstable_getCacheForType,
|
||||
} from './src/React';
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export {
|
|||
startTransition as unstable_startTransition,
|
||||
SuspenseList,
|
||||
SuspenseList as unstable_SuspenseList,
|
||||
unstable_getCacheForType,
|
||||
// enableScopeAPI
|
||||
unstable_Scope,
|
||||
unstable_useOpaqueIdentifier,
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
43
packages/react/src/cache/ReactCache.js
vendored
43
packages/react/src/cache/ReactCache.js
vendored
|
|
@ -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};
|
||||
|
|
@ -32,6 +32,7 @@ export {
|
|||
useDeferredValue as unstable_useDeferredValue,
|
||||
SuspenseList as unstable_SuspenseList,
|
||||
unstable_useOpaqueIdentifier,
|
||||
unstable_getCacheForType,
|
||||
// enableDebugTracing
|
||||
unstable_DebugTracingMode,
|
||||
} from './src/React';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export const enableSuspenseServerRenderer = true;
|
|||
export const enableSelectiveHydration = true;
|
||||
|
||||
export const enableLazyElements = true;
|
||||
export const enableCache = true;
|
||||
|
||||
export const disableJavaScriptURLs = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user