|
| 1 | +// Modifed from "在 JavaScript 中实现和使用 Context | Sukka's Blog" |
| 2 | +// https://blog.skk.moe/post/context-in-javascript/ |
| 3 | +// CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh |
| 4 | + |
| 5 | +export type ApplyProvider = <R>(callback: () => R) => R |
| 6 | + |
| 7 | +export interface ContextProvider<T> { |
| 8 | + <R>(props: { value: T, callback: () => R }): R |
| 9 | + (props: { value: T }): ApplyProvider |
| 10 | +} |
| 11 | + |
| 12 | +export interface ContextConsumer<T> { |
| 13 | + <R>(callback: (value: T) => R): R |
| 14 | + (): T |
| 15 | +} |
| 16 | + |
| 17 | +export interface Context<T> { |
| 18 | + Provider: ContextProvider<T> |
| 19 | + Consumer: ContextConsumer<T> |
| 20 | +} |
| 21 | + |
| 22 | +const NO_VALUE_DEFAULT = Symbol('NO_VALUE_DEFAULT') |
| 23 | +type ContextValue<T> = T | typeof NO_VALUE_DEFAULT |
| 24 | + |
| 25 | +export function createContext<T>(defaultValue: ContextValue<T> = NO_VALUE_DEFAULT): Context<T> { |
| 26 | + let contextValue = defaultValue |
| 27 | + |
| 28 | + const Provider = <R>({ value, callback }: { value: T, callback?: () => R }) => { |
| 29 | + if (!callback) { |
| 30 | + return (fn: () => R) => Provider({ value, callback: fn }) |
| 31 | + } |
| 32 | + const currentValue = contextValue |
| 33 | + contextValue = value |
| 34 | + try { |
| 35 | + return callback() |
| 36 | + } |
| 37 | + finally { |
| 38 | + contextValue = currentValue |
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + const Consumer = <R>(callback?: (value: T) => R) => { |
| 43 | + if (contextValue === NO_VALUE_DEFAULT) { |
| 44 | + throw new TypeError( |
| 45 | + 'You should only use useContext inside a Provider, or provide a default value!', |
| 46 | + ) |
| 47 | + } |
| 48 | + if (!callback) { |
| 49 | + return contextValue |
| 50 | + } |
| 51 | + return callback(contextValue) |
| 52 | + } |
| 53 | + |
| 54 | + return { |
| 55 | + Provider, |
| 56 | + Consumer, |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +export function useContext<T>(contextRef: Context<T>): T { |
| 61 | + return contextRef.Consumer() |
| 62 | +} |
| 63 | + |
| 64 | +export interface ContextComposeProviderProps<R> { |
| 65 | + contexts: ApplyProvider[] |
| 66 | + callback: () => R |
| 67 | +} |
| 68 | + |
| 69 | +export function ComposeProvider<R>({ contexts, callback }: ContextComposeProviderProps<R>): R { |
| 70 | + const applyProviders = contexts.reduceRight( |
| 71 | + (composed, current) => (fn) => current(() => composed(fn)), |
| 72 | + (fn) => fn(), |
| 73 | + ) |
| 74 | + return applyProviders(callback) |
| 75 | +} |
0 commit comments