Skip to main content

React Hooks 使用技巧

基础 hooks

兼容 ssr 的 useEffect

const useEnhancedEffect =
typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect

useEvent 近似方案

useEvent RFC

import * as React from 'react';
import useEnhancedEffect from './useEnhancedEffect';

/**
* https://github.com/facebook/react/issues/14099#issuecomment-440013892
*/
export default function useEventCallback<Args extends unknown[], Return>(
fn: (...args: Args) => Return,
): (...args: Args) => Return {
const ref = React.useRef(fn);
useEnhancedEffect(() => {
ref.current = fn;
});
return React.useCallback(
(...args: Args) =>
// @ts-expect-error hide `this`
// tslint:disable-next-line:ban-comma-operator
(0, ref.current!)(...args),
[],
);
}

功能 hooks

存储事件回调

const useEventCallback = <A extends unknown[], R>(
fn: (...args: A) => R,
): ((...args: A) => R) => {
const fnRef = useRef(fn)

useEnhancedEffect(() => {
fnRef.current = fn
})

return useCallback(
(...args: A) =>
// @ts-expect-error
(0, fnRef.current)(...args),
[],
)
}

useEffect 依赖公用函数

function UserList () {
const [index, setIndex] = useState(0)
const fetchData = useCallback(() => {
featchMetod(index)
}, [index])

useEffect(() => {
fetchData()
}, [fetchData])

function doSomething () {
fetchData()
}

return (
//...
)
}

双击

function useDoubleClick () {
const [ lastClickTime, setClickTime ] = useState(0)

return (callback) => (e) => {
const currentTime = e.timeStamp
const gap = currentTime - lastClickTime
if (gap > 0 && gap < 300) {
callback && callback(e)
}
setClickTime(currentTime)
}
}

接口请求封装

export function useFetch = (config, deps) => {
const abortController = new AbortController()
const [loading, setLoading] = useState(false)
const [result, setResult] = useState()

useEffect(() => {
setLoading(true)
fetch({
...config,
signal: abortController.signal
})
.then((res) => setResult(res))
.finally(() => setLoading(false))
}, deps)

useEffect(() => {
return () => abortController.abort()
}, [])

return { result, loading }
}

URL 数据仓库

export function useQuery() {
const history = useHistory();
const { search, pathname } = useLocation();
// 保存query状态
const queryState = useRef(qs.parse(search));
// 设置query
const setQuery = handler => {
const nextQuery = handler(queryState.current);
queryState.current = nextQuery;
// replace会使组件重新渲染
history.replace({
pathname: pathname,
search: qs.stringify(nextQuery),
});
};
return [queryState.current, setQuery];
}

const [query, setQuery] = useQuery();

// 接口请求依赖 page 和 size
useEffect(() => {
api.getUsers();
}, [query.page, query, size]);

// 分页改变 触发接口重新请求
const onPageChange = page => {
setQuery(prevQuery => ({
...prevQuery,
page,
}));
};