Skip to content

Instantly share code, notes, and snippets.

@varHarrie
Last active April 1, 2019 06:26
Show Gist options
  • Save varHarrie/11b657d91f10e4492a52f004e0e1dd56 to your computer and use it in GitHub Desktop.
Save varHarrie/11b657d91f10e4492a52f004e0e1dd56 to your computer and use it in GitHub Desktop.
React Hooks
import * as React from 'react'
type Args<F> = F extends (...args: infer A) => any ? A : never
type Parser<T> = (data: any, prevData: T) => T
interface AsyncState<T> {
/** 是否加载中 */
loading: boolean
/** 成功数据 */
data: T | undefined
/** 错误信息 */
error: Error | undefined
}
interface AsyncOptions<T = any, F = any> {
/** 异步函数 */
func: F
/** 函数参数 */
args: Args<F>
/** 懒加载(即手动调用) */
lazy?: boolean
/** data初始值 */
initial?: T
/** 回调解析器 */
parser?: Parser<T>
}
function getPromise(func: any, args: any[]) {
const result = typeof func === 'function' ? func(...args) : func
return result instanceof Promise ? result : Promise.resolve(result)
}
function handleResolve<T>(
data: any,
prevData: T,
parser: Parser<T>
): AsyncState<T> {
return {
loading: false,
data: parser ? parser(data, prevData) : data,
error: undefined
}
}
function handleReject(error: Error) {
return { loading: false, data: undefined, error }
}
/** 异步处理hook */
export default function useAsync<T = any, F = any>(
options: AsyncOptions<T, F>
) {
const { func, args, lazy, initial, parser } = options
const refMounted = React.useRef(false)
const [trigger, setTrigger] = React.useState(0)
const [state, setState] = React.useState<AsyncState<T>>({
loading: false,
data: initial,
error: undefined
})
const invoke = React.useCallback(() => setTrigger(trigger + 1), [trigger])
React.useEffect(() => {
refMounted.current = true
return () => {
refMounted.current = false
}
}, [])
React.useEffect(() => {
if (lazy || state.loading) {
return
}
setState({ ...state, loading: true })
getPromise(func, args)
.then((data) => handleResolve(data, state.data, parser))
.catch((error) => handleReject(error))
.then((result) => refMounted.current && setState(result))
}, [trigger, ...args])
return { ...state, invoke }
}
import * as React from 'react'
interface AsyncState<T> {
loading: boolean
data: T | undefined
error: Error | undefined
}
interface Action {
type: string
payload?: any
}
type AsyncReducer<T> = (
prevState: AsyncState<T>,
action: Action
) => AsyncState<T>
const initialState: AsyncState<any> = {
loading: false,
data: undefined,
error: undefined
}
const reducer: AsyncReducer<any> = (state, action) => {
switch (action.type) {
case 'LOAD_START':
return { ...state, loading: true }
case 'LOAD_SUCCESS':
return { loading: false, data: action.payload, error: undefined }
case 'LOAD_FAILURE':
return { loading: false, data: undefined, error: action.payload }
default:
return state
}
}
export default function useAsync<T = any>(
fn: (prevData: T) => Promise<T>,
deps: ReadonlyArray<any>
) {
const refMounted = React.useRef(false)
const [state, dispatch] = React.useReducer<AsyncReducer<T>>(
reducer,
initialState
)
const invoke = React.useCallback((prevData: any) => {
const promise = fn(arguments.length ? prevData : state.data)
if (!(promise instanceof Promise)) {
return Promise.resolve()
}
dispatch({ type: 'LOAD_START' })
return promise
.then(
(data) =>
refMounted.current &&
dispatch({ type: 'LOAD_SUCCESS', payload: data })
)
.catch(
(error) =>
refMounted.current &&
dispatch({ type: 'LOAD_FAILURE', payload: error })
)
}, deps)
React.useEffect(() => {
refMounted.current = true
return () => {
refMounted.current = false
}
}, [])
React.useEffect(() => {
invoke()
}, [invoke])
return { ...state, invoke }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment