Created
April 9, 2024 03:23
-
-
Save aiyogg/3ce249f7ec10f13b10ccad671bf2e1ab to your computer and use it in GitHub Desktop.
React use Promise
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react" | |
import type { ReactNode } from "react" | |
const { createContext, useContext, useRef, useState } = React; | |
type PromiseCanUse<T> = Promise<T> & { | |
status?: 'pending' | 'fulfilled' | 'rejected' | |
reason?: unknown | |
value?: T | |
} | |
/** | |
* 在当前组件使用 loading/error | |
*/ | |
export function usePromise<T>(promise?: PromiseCanUse<T>) { | |
const [_, forceUpdate] = useState({}) | |
const ref = useRef<PromiseCanUse<T>>() | |
if (!promise) return { loading: false, data: promise } | |
ref.current = promise | |
if (!promise.status) { | |
promise.status = 'pending' | |
promise | |
.then( | |
(result) => { | |
promise.status = 'fulfilled' | |
promise.value = result | |
}, | |
(reason) => { | |
promise.status = 'rejected' | |
promise.reason = reason | |
} | |
) | |
.finally(() => { | |
setTimeout(() => { | |
if (ref.current === promise) { | |
forceUpdate({}) | |
} | |
}, 0) | |
}) | |
} | |
return { | |
loading: promise.status === 'pending', | |
data: promise.value, | |
error: promise.reason, | |
} | |
} | |
/** | |
* 在父级/祖父级组件中使用 Suspense/ErrorBoundary 接收 loading/error | |
*/ | |
export function use<T>(promise?: PromiseCanUse<T>) { | |
if (!promise) return promise | |
if (promise.status === 'fulfilled') { | |
return promise.value | |
} else if (promise.status === 'rejected') { | |
throw promise.reason | |
} else if (promise.status === 'pending') { | |
throw promise | |
} else { | |
promise.status = 'pending' | |
promise.then( | |
(result) => { | |
promise.status = 'fulfilled' | |
promise.value = result | |
}, | |
(reason) => { | |
promise.status = 'rejected' | |
promise.reason = reason | |
} | |
) | |
throw promise | |
} | |
} | |
const AsyncDataContext = createContext<unknown>(undefined) | |
/** | |
* 在当前组件或父级/祖父级组件中使用 Suspense/ErrorBoundary 接收 loading/error | |
*/ | |
export const Await = <T,>(props: { | |
resolver?: Promise<T> | |
children?: ReactNode | undefined | ((data?: T) => ReactNode) | |
}) => { | |
const { resolver, children } = props | |
const data = use(resolver) | |
if (typeof children === 'function') { | |
return children(data) | |
} | |
return ( | |
<AsyncDataContext.Provider value={data}> | |
{children} | |
</AsyncDataContext.Provider> | |
) | |
} | |
/** | |
* 在当前组件接收来自父级 <Await /> 组件的 data | |
* @deprecated 不推荐使用, 会丢失 ts 类型 | |
*/ | |
export const useAsyncValue = () => { | |
return useContext(AsyncDataContext) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment