mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
We added this unstable feature a few years ago, as a way to opt out of context updates, but it didn't prove useful in practice. We have other proposals for how to address the same problem, like context selectors. Since it was prefixed with `unstable_`, we should be able to remove it without consequence. The hook API already warned if you used it. Even if someone is using it somewhere, it's meant to be an optimization only, so if they are using the API properly, it should not have any semantic impact.
188 lines
4.8 KiB
JavaScript
188 lines
4.8 KiB
JavaScript
/**
|
|
* 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 {Thenable} from 'shared/ReactTypes';
|
|
|
|
import * as React from 'react';
|
|
|
|
import {createLRU} from './LRU';
|
|
|
|
type Suspender = {then(resolve: () => mixed, reject: () => mixed): mixed, ...};
|
|
|
|
type PendingResult = {|
|
|
status: 0,
|
|
value: Suspender,
|
|
|};
|
|
|
|
type ResolvedResult<V> = {|
|
|
status: 1,
|
|
value: V,
|
|
|};
|
|
|
|
type RejectedResult = {|
|
|
status: 2,
|
|
value: mixed,
|
|
|};
|
|
|
|
type Result<V> = PendingResult | ResolvedResult<V> | RejectedResult;
|
|
|
|
type Resource<I, V> = {
|
|
read(I): V,
|
|
preload(I): void,
|
|
...
|
|
};
|
|
|
|
const Pending = 0;
|
|
const Resolved = 1;
|
|
const Rejected = 2;
|
|
|
|
const ReactCurrentDispatcher =
|
|
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
|
.ReactCurrentDispatcher;
|
|
|
|
function readContext(Context) {
|
|
const dispatcher = ReactCurrentDispatcher.current;
|
|
if (dispatcher === null) {
|
|
throw new Error(
|
|
'react-cache: read and preload may only be called from within a ' +
|
|
"component's render. They are not supported in event handlers or " +
|
|
'lifecycle methods.',
|
|
);
|
|
}
|
|
return dispatcher.readContext(Context);
|
|
}
|
|
|
|
function identityHashFn(input) {
|
|
if (__DEV__) {
|
|
if (
|
|
typeof input !== 'string' &&
|
|
typeof input !== 'number' &&
|
|
typeof input !== 'boolean' &&
|
|
input !== undefined &&
|
|
input !== null
|
|
) {
|
|
console.error(
|
|
'Invalid key type. Expected a string, number, symbol, or boolean, ' +
|
|
'but instead received: %s' +
|
|
'\n\nTo use non-primitive values as keys, you must pass a hash ' +
|
|
'function as the second argument to createResource().',
|
|
input,
|
|
);
|
|
}
|
|
}
|
|
return input;
|
|
}
|
|
|
|
const CACHE_LIMIT = 500;
|
|
const lru = createLRU(CACHE_LIMIT);
|
|
|
|
const entries: Map<Resource<any, any>, Map<any, any>> = new Map();
|
|
|
|
const CacheContext = React.createContext(null);
|
|
|
|
function accessResult<I, K, V>(
|
|
resource: any,
|
|
fetch: I => Thenable<V>,
|
|
input: I,
|
|
key: K,
|
|
): Result<V> {
|
|
let entriesForResource = entries.get(resource);
|
|
if (entriesForResource === undefined) {
|
|
entriesForResource = new Map();
|
|
entries.set(resource, entriesForResource);
|
|
}
|
|
const entry = entriesForResource.get(key);
|
|
if (entry === undefined) {
|
|
const thenable = fetch(input);
|
|
thenable.then(
|
|
value => {
|
|
if (newResult.status === Pending) {
|
|
const resolvedResult: ResolvedResult<V> = (newResult: any);
|
|
resolvedResult.status = Resolved;
|
|
resolvedResult.value = value;
|
|
}
|
|
},
|
|
error => {
|
|
if (newResult.status === Pending) {
|
|
const rejectedResult: RejectedResult = (newResult: any);
|
|
rejectedResult.status = Rejected;
|
|
rejectedResult.value = error;
|
|
}
|
|
},
|
|
);
|
|
const newResult: PendingResult = {
|
|
status: Pending,
|
|
value: thenable,
|
|
};
|
|
const newEntry = lru.add(newResult, deleteEntry.bind(null, resource, key));
|
|
entriesForResource.set(key, newEntry);
|
|
return newResult;
|
|
} else {
|
|
return (lru.access(entry): any);
|
|
}
|
|
}
|
|
|
|
function deleteEntry(resource, key) {
|
|
const entriesForResource = entries.get(resource);
|
|
if (entriesForResource !== undefined) {
|
|
entriesForResource.delete(key);
|
|
if (entriesForResource.size === 0) {
|
|
entries.delete(resource);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function unstable_createResource<I, K: string | number, V>(
|
|
fetch: I => Thenable<V>,
|
|
maybeHashInput?: I => K,
|
|
): Resource<I, V> {
|
|
const hashInput: I => K =
|
|
maybeHashInput !== undefined ? maybeHashInput : (identityHashFn: any);
|
|
|
|
const resource = {
|
|
read(input: I): V {
|
|
// react-cache currently doesn't rely on context, but it may in the
|
|
// future, so we read anyway to prevent access outside of render.
|
|
readContext(CacheContext);
|
|
const key = hashInput(input);
|
|
const result: Result<V> = accessResult(resource, fetch, input, key);
|
|
switch (result.status) {
|
|
case Pending: {
|
|
const suspender = result.value;
|
|
throw suspender;
|
|
}
|
|
case Resolved: {
|
|
const value = result.value;
|
|
return value;
|
|
}
|
|
case Rejected: {
|
|
const error = result.value;
|
|
throw error;
|
|
}
|
|
default:
|
|
// Should be unreachable
|
|
return (undefined: any);
|
|
}
|
|
},
|
|
|
|
preload(input: I): void {
|
|
// react-cache currently doesn't rely on context, but it may in the
|
|
// future, so we read anyway to prevent access outside of render.
|
|
readContext(CacheContext);
|
|
const key = hashInput(input);
|
|
accessResult(resource, fetch, input, key);
|
|
},
|
|
};
|
|
return resource;
|
|
}
|
|
|
|
export function unstable_setGlobalCacheLimit(limit: number) {
|
|
lru.setLimit(limit);
|
|
}
|