Last active
April 1, 2019 06:26
-
-
Save varHarrie/11b657d91f10e4492a52f004e0e1dd56 to your computer and use it in GitHub Desktop.
React Hooks
This file contains 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 * 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 } | |
} |
This file contains 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 * 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