diff --git a/packages/react-reconciler/src/__tests__/ReactCache-test.js b/packages/react-reconciler/src/__tests__/ReactCache-test.js index 6bf494b1e0ad9..970bf940f27cd 100644 --- a/packages/react-reconciler/src/__tests__/ReactCache-test.js +++ b/packages/react-reconciler/src/__tests__/ReactCache-test.js @@ -2,6 +2,7 @@ let React; let ReactNoop; let Cache; let getCacheSignal; +let getCacheForType; let Scheduler; let act; let Suspense; @@ -9,10 +10,8 @@ let Offscreen; let useCacheRefresh; let startTransition; let useState; -let cache; -let getTextCache; -let textCaches; +let caches; let seededCache; describe('ReactCache', () => { @@ -25,68 +24,66 @@ describe('ReactCache', () => { Scheduler = require('scheduler'); act = require('jest-react').act; Suspense = React.Suspense; - cache = React.experimental_cache; Offscreen = React.unstable_Offscreen; getCacheSignal = React.unstable_getCacheSignal; + getCacheForType = React.unstable_getCacheForType; useCacheRefresh = React.unstable_useCacheRefresh; startTransition = React.startTransition; useState = React.useState; - textCaches = []; + caches = []; seededCache = null; + }); - if (gate(flags => flags.enableCache)) { - getTextCache = cache(() => { - if (seededCache !== null) { - // Trick to seed a cache before it exists. - // TODO: Need a built-in API to seed data before the initial render (i.e. - // not a refresh because nothing has mounted yet). - const textCache = seededCache; - seededCache = null; - return textCache; - } - - const data = new Map(); - const version = textCaches.length + 1; - const textCache = { - version, - data, - resolve(text) { - const record = data.get(text); - if (record === undefined) { - const newRecord = { - status: 'resolved', - value: text, - cleanupScheduled: false, - }; - data.set(text, newRecord); - } else if (record.status === 'pending') { - record.value.resolve(); - } - }, - reject(text, error) { - const record = data.get(text); - if (record === undefined) { - const newRecord = { - status: 'rejected', - value: error, - cleanupScheduled: false, - }; - data.set(text, newRecord); - } else if (record.status === 'pending') { - record.value.reject(); - } - }, - }; - textCaches.push(textCache); - return textCache; - }); + function createTextCache() { + if (seededCache !== null) { + // Trick to seed a cache before it exists. + // TODO: Need a built-in API to seed data before the initial render (i.e. + // not a refresh because nothing has mounted yet). + const cache = seededCache; + seededCache = null; + return cache; } - }); + + const data = new Map(); + const version = caches.length + 1; + const cache = { + version, + data, + resolve(text) { + const record = data.get(text); + if (record === undefined) { + const newRecord = { + status: 'resolved', + value: text, + cleanupScheduled: false, + }; + data.set(text, newRecord); + } else if (record.status === 'pending') { + record.value.resolve(); + } + }, + reject(text, error) { + const record = data.get(text); + if (record === undefined) { + const newRecord = { + status: 'rejected', + value: error, + cleanupScheduled: false, + }; + data.set(text, newRecord); + } else if (record.status === 'pending') { + record.value.reject(); + } + }, + }; + caches.push(cache); + return cache; + } function readText(text) { const signal = getCacheSignal(); - const textCache = getTextCache(); + const textCache = getCacheForType(createTextCache); const record = textCache.data.get(text); if (record !== undefined) { if (!record.cleanupScheduled) { @@ -163,18 +160,18 @@ describe('ReactCache', () => { function seedNextTextCache(text) { if (seededCache === null) { - seededCache = getTextCache(); + seededCache = createTextCache(); } seededCache.resolve(text); } function resolveMostRecentTextCache(text) { - if (textCaches.length === 0) { + if (caches.length === 0) { throw Error('Cache does not exist.'); } else { // Resolve the most recently created cache. An older cache can by - // resolved with `textCaches[index].resolve(text)`. - textCaches[textCaches.length - 1].resolve(text); + // resolved with `caches[index].resolve(text)`. + caches[caches.length - 1].resolve(text); } } @@ -818,18 +815,9 @@ describe('ReactCache', () => { // @gate experimental || www test('refresh a cache with seed data', async () => { - let refreshWithSeed; + let refresh; function App() { - const refresh = useCacheRefresh(); - const [seed, setSeed] = useState({fn: null}); - if (seed.fn) { - seed.fn(); - seed.fn = null; - } - refreshWithSeed = fn => { - setSeed({fn}); - refresh(); - }; + refresh = useCacheRefresh(); return ; } @@ -857,14 +845,11 @@ describe('ReactCache', () => { await act(async () => { // Refresh the cache with seeded data, like you would receive from a // server mutation. - // TODO: Seeding multiple typed textCaches. Should work by calling `refresh` + // TODO: Seeding multiple typed caches. Should work by calling `refresh` // multiple times with different key/value pairs - startTransition(() => - refreshWithSeed(() => { - const textCache = getTextCache(); - textCache.resolve('A'); - }), - ); + const cache = createTextCache(); + cache.resolve('A'); + startTransition(() => refresh(createTextCache, cache)); }); // The root should re-render without a cache miss. // The cache is not cleared up yet, since it's still reference by the root @@ -1639,152 +1624,4 @@ describe('ReactCache', () => { expect(Scheduler).toHaveYielded(['More']); expect(root).toMatchRenderedOutput(); }); - - // @gate enableCache - it('cache objects and primitive arguments and a mix of them', async () => { - const root = ReactNoop.createRoot(); - const types = cache((a, b) => ({a: typeof a, b: typeof b})); - function Print({a, b}) { - return types(a, b).a + ' ' + types(a, b).b + ' '; - } - function Same({a, b}) { - const x = types(a, b); - const y = types(a, b); - return (x === y).toString() + ' '; - } - function FlippedOrder({a, b}) { - return (types(a, b) === types(b, a)).toString() + ' '; - } - function FewerArgs({a, b}) { - return (types(a, b) === types(a)).toString() + ' '; - } - function MoreArgs({a, b}) { - return (types(a) === types(a, b)).toString() + ' '; - } - await act(async () => { - root.render( - <> - - - - - - , - ); - }); - expect(root).toMatchRenderedOutput('string string true false false false '); - await act(async () => { - root.render( - <> - - - - - - , - ); - }); - expect(root).toMatchRenderedOutput('string object true false false false '); - const obj = {}; - await act(async () => { - root.render( - <> - - - - - - , - ); - }); - expect(root).toMatchRenderedOutput('string object true false false false '); - const sameObj = {}; - await act(async () => { - root.render( - <> - - - - - - , - ); - }); - expect(root).toMatchRenderedOutput('object object true true false false '); - const objA = {}; - const objB = {}; - await act(async () => { - root.render( - <> - - - - - - , - ); - }); - expect(root).toMatchRenderedOutput('object object true false false false '); - const sameSymbol = Symbol(); - await act(async () => { - root.render( - <> - - - - - - , - ); - }); - expect(root).toMatchRenderedOutput('symbol symbol true true false false '); - const notANumber = +'nan'; - await act(async () => { - root.render( - <> - - - - - - , - ); - }); - expect(root).toMatchRenderedOutput('number number true false false false '); - }); - - // @gate enableCache - it('cached functions that throw should cache the error', async () => { - const root = ReactNoop.createRoot(); - const throws = cache(v => { - throw new Error(v); - }); - let x; - let y; - let z; - function Test() { - try { - throws(1); - } catch (e) { - x = e; - } - try { - throws(1); - } catch (e) { - y = e; - } - try { - throws(2); - } catch (e) { - z = e; - } - - return 'Blank'; - } - await act(async () => { - root.render(); - }); - expect(x).toBe(y); - expect(z).not.toBe(x); - }); }); diff --git a/packages/react/index.classic.fb.js b/packages/react/index.classic.fb.js index d7b56c551f194..6e90736a1c2fe 100644 --- a/packages/react/index.classic.fb.js +++ b/packages/react/index.classic.fb.js @@ -32,7 +32,6 @@ export { isValidElement, lazy, memo, - experimental_cache, startTransition, startTransition as unstable_startTransition, // TODO: Remove once call sights updated to startTransition unstable_Cache, diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js index b296be4bb194c..9b40832086381 100644 --- a/packages/react/index.experimental.js +++ b/packages/react/index.experimental.js @@ -29,7 +29,6 @@ export { isValidElement, lazy, memo, - experimental_cache, startTransition, unstable_Cache, unstable_DebugTracingMode, diff --git a/packages/react/index.js b/packages/react/index.js index 62c7d2f281404..145513fb93047 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -54,7 +54,6 @@ export { isValidElement, lazy, memo, - experimental_cache, startTransition, unstable_Cache, unstable_DebugTracingMode, diff --git a/packages/react/index.modern.fb.js b/packages/react/index.modern.fb.js index ba6a505d96920..0fc400a8940a0 100644 --- a/packages/react/index.modern.fb.js +++ b/packages/react/index.modern.fb.js @@ -31,7 +31,6 @@ export { isValidElement, lazy, memo, - experimental_cache, startTransition, startTransition as unstable_startTransition, // TODO: Remove once call sights updated to startTransition unstable_Cache, diff --git a/packages/react/src/React.js b/packages/react/src/React.js index 3cb0d13a13a05..fe217ee406143 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -35,7 +35,6 @@ import {createContext} from './ReactContext'; import {lazy} from './ReactLazy'; import {forwardRef} from './ReactForwardRef'; import {memo} from './ReactMemo'; -import {cache} from './ReactCache'; import { getCacheSignal, getCacheForType, @@ -104,7 +103,6 @@ export { forwardRef, lazy, memo, - cache as experimental_cache, useCallback, useContext, useEffect, diff --git a/packages/react/src/ReactCache.js b/packages/react/src/ReactCache.js deleted file mode 100644 index b308ad9e16d24..0000000000000 --- a/packages/react/src/ReactCache.js +++ /dev/null @@ -1,126 +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 ReactCurrentCache from './ReactCurrentCache'; - -const UNTERMINATED = 0; -const TERMINATED = 1; -const ERRORED = 2; - -type UnterminatedCacheNode = { - s: 0, - v: void, - o: null | WeakMap>, - p: null | Map>, -}; - -type TerminatedCacheNode = { - s: 1, - v: T, - o: null | WeakMap>, - p: null | Map>, -}; - -type ErroredCacheNode = { - s: 2, - v: mixed, - o: null | WeakMap>, - p: null | Map>, -}; - -type CacheNode = - | TerminatedCacheNode - | UnterminatedCacheNode - | ErroredCacheNode; - -function createCacheRoot(): WeakMap> { - return new WeakMap(); -} - -function createCacheNode(): CacheNode { - return { - s: UNTERMINATED, // status, represents whether the cached computation returned a value or threw an error - v: undefined, // value, either the cached result or an error, depending on s - o: null, // object cache, a WeakMap where non-primitive arguments are stored - p: null, // primitive cache, a regular Map where primitive arguments are stored. - }; -} - -export function cache, T>(fn: (...A) => T): (...A) => T { - return function() { - const dispatcher = ReactCurrentCache.current; - if (!dispatcher) { - // If there is no dispatcher, then we treat this as not being cached. - // $FlowFixMe: We don't want to use rest arguments since we transpile the code. - return fn.apply(null, arguments); - } - const fnMap = dispatcher.getCacheForType(createCacheRoot); - const fnNode = fnMap.get(fn); - let cacheNode: CacheNode; - if (fnNode === undefined) { - cacheNode = createCacheNode(); - fnMap.set(fn, cacheNode); - } else { - cacheNode = fnNode; - } - for (let i = 0, l = arguments.length; i < l; i++) { - const arg = arguments[i]; - if ( - typeof arg === 'function' || - (typeof arg === 'object' && arg !== null) - ) { - // Objects go into a WeakMap - let objectCache = cacheNode.o; - if (objectCache === null) { - cacheNode.o = objectCache = new WeakMap(); - } - const objectNode = objectCache.get(arg); - if (objectNode === undefined) { - cacheNode = createCacheNode(); - objectCache.set(arg, cacheNode); - } else { - cacheNode = objectNode; - } - } else { - // Primitives go into a regular Map - let primitiveCache = cacheNode.p; - if (primitiveCache === null) { - cacheNode.p = primitiveCache = new Map(); - } - const primitiveNode = primitiveCache.get(arg); - if (primitiveNode === undefined) { - cacheNode = createCacheNode(); - primitiveCache.set(arg, cacheNode); - } else { - cacheNode = primitiveNode; - } - } - } - if (cacheNode.s === TERMINATED) { - return cacheNode.v; - } - if (cacheNode.s === ERRORED) { - throw cacheNode.v; - } - try { - // $FlowFixMe: We don't want to use rest arguments since we transpile the code. - const result = fn.apply(null, arguments); - const terminatedNode: TerminatedCacheNode = (cacheNode: any); - terminatedNode.s = TERMINATED; - terminatedNode.v = result; - return result; - } catch (error) { - // We store the first error that's thrown and rethrow it. - const erroredNode: ErroredCacheNode = (cacheNode: any); - erroredNode.s = ERRORED; - erroredNode.v = error; - throw error; - } - }; -} diff --git a/packages/react/src/ReactSharedSubset.experimental.js b/packages/react/src/ReactSharedSubset.experimental.js index 1283c51a9ad53..cb6c746c9bce8 100644 --- a/packages/react/src/ReactSharedSubset.experimental.js +++ b/packages/react/src/ReactSharedSubset.experimental.js @@ -24,7 +24,6 @@ export { isValidElement, lazy, memo, - experimental_cache, startTransition, unstable_DebugTracingMode, unstable_getCacheSignal,