mirror of
https://github.com/facebook/react.git
synced 2026-02-26 07:35:23 +00:00
162 lines
4.1 KiB
JavaScript
162 lines
4.1 KiB
JavaScript
// @flow
|
|
|
|
import React, { createContext } from 'react';
|
|
|
|
// Cache implementation was forked from the React repo:
|
|
// https://github.com/facebook/react/blob/master/packages/react-cache/src/ReactCache.js
|
|
//
|
|
// This cache is simpler than react-cache in that:
|
|
// 1. Individual items don't need to be invalidated.
|
|
// Profiling data is invalidated as a whole.
|
|
// 2. We didn't need the added overhead of an LRU cache.
|
|
// The size of this cache is bounded by how many renders were profiled,
|
|
// and it will be fully reset between profiling sessions.
|
|
|
|
type Thenable<T> = {
|
|
then(resolve: (T) => mixed, reject: (mixed) => mixed): mixed,
|
|
};
|
|
|
|
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;
|
|
|
|
export type Resource<I, V> = {
|
|
read(I): V,
|
|
preload(I): void,
|
|
};
|
|
|
|
const Pending = 0;
|
|
const Resolved = 1;
|
|
const Rejected = 2;
|
|
|
|
const ReactCurrentDispatcher = (React: any)
|
|
.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher;
|
|
|
|
function readContext(Context, observedBits) {
|
|
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, observedBits);
|
|
}
|
|
|
|
function identityHashFn(input) {
|
|
return input;
|
|
}
|
|
|
|
const CacheContext = createContext(null);
|
|
|
|
const entries: Map<Resource<any, any>, Map<any, any>> = new Map();
|
|
|
|
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);
|
|
}
|
|
let 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,
|
|
};
|
|
entriesForResource.set(key, newResult);
|
|
return newResult;
|
|
} else {
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
export function 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 {
|
|
// Prevent access outside of render.
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
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 {
|
|
// Prevent access outside of render.
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
readContext(CacheContext);
|
|
|
|
const key = hashInput(input);
|
|
accessResult(resource, fetch, input, key);
|
|
},
|
|
};
|
|
return resource;
|
|
}
|
|
|
|
export function invalidateResources(): void {
|
|
entries.clear();
|
|
}
|