赞
踩
$ npm install --save ahooks
# or
$ yarn add ahooks
import { useRequest } from 'ahooks';
适用于对时间精度要求不高的情况,要求高的话还是需要结合服务器时间来判断。
const [popoverVisible, setPopoverVisible] = useState(true);
useCountDown({ leftTime: 5 * 1000, onEnd: () => {
setPopoverVisible(false);
}, });
const { targetDate, interval = 1000, onEnd } = options || {}; //剩余毫秒数,calcLeft:计算与当前时间的毫秒差值的方法 const [timeLeft, setTimeLeft] = useState(() => calcLeft(targetDate)); //onEnd方法的引用,useLatest返回当前最新值的 Hook,可以避免闭包问题。 const onEndRef = useLatest(onEnd); useEffect(() => { ... // 立即执行一次 setTimeLeft(calcLeft(targetDate)); //实际还是使用setInterval const timer = setInterval(() => { const targetLeft = calcLeft(targetDate); setTimeLeft(targetLeft); if (targetLeft === 0) { clearInterval(timer); onEndRef.current?.(); } }, interval); //组件销毁的同时销毁计数器,释放资源 return () => clearInterval(timer); }, [targetDate, interval]);
适用于一些异步操作的场景,比如对请求的后续操作,要判断当前页面是否还在。
const unUnMountedRef = useUnmountedRef();
//需求:fetchTagsInfo接口调用成功后再调用fetchOnWayTradeOrder
fetchTagsInfo().then(() => {
//如果此时页面切换,组件已卸载,则无需调用后续接口
if (unUnMountedRef.current) return;
fetchOnWayTradeOrder();
});
const unmountedRef = useRef(false);
useEffect(() => {
unmountedRef.current = false;
return () => {
//实际就是在useEffect的卸载方法中修改标记unmountedRef
unmountedRef.current = true;
};
}, []);
return unmountedRef;
useMount(() => {
//页面初始化时滑到最顶端
window.scrollTo(0, 0);
});
const useMount = (fn: () => void) => {
...
useEffect(() => {
fn?.();
}, []);
};
//组件卸载时执行
useUnmount(() => {
dispatch({
type: 'purchase/changeApproModal',
payload: false,
});
});
const useUnmount = (fn: () => void) => {
...
const fnRef = useLatest(fn);
useEffect(
() => () => {
fnRef.current();
},
[],
);
};
//设置定时器,返回值是一个清除定时器的方法
const interval = useInterval(() => {
fetchServerTime();
}, 1000 * 60);
//销毁定时器
useUnmount(() => {
if (interval && typeof interval === 'function') {
interval();
}
});
const fnRef = useLatest(fn); const timerRef = useRef(null); useEffect(() => { ... //使用setInterval定义一个定时器 timerRef.current = setInterval(() => { fnRef.current(); }, delay); //组件销毁时自动销毁定时器 return () => { if (timerRef.current) { clearInterval(timerRef.current); } }; }, [delay]); //返回一个销毁定时器的方法,用于手动销毁 const clear = useCallback(() => { if (timerRef.current) { clearInterval(timerRef.current); } }, []); return clear;
export const createUpdateEffect =
(hook) => (effect, deps) => {
//标记是否已初始化
const isMounted = useRef(false);
...
//hook就是react.useLayoutEffect
hook(() => {
if (!isMounted.current) {
isMounted.current = true;
} else {
//只有当组件已初始化过后才会执行
return effect();
}
}, deps);
};
function useLockFn(fn: (...args: P) => Promise<V>) { //锁标记 const lockRef = useRef(false); return useCallback( async (...args: P) => { if (lockRef.current) return; //第一次执行函数,上锁 lockRef.current = true; try { const ret = await fn(...args); //执行完,解锁 lockRef.current = false; return ret; } catch (e) { //出异常,解锁 lockRef.current = false; throw e; } }, [fn], ); }
export default function useClickAway( onClickAway: (event: T) => void, //触发函数 target: BasicTarget | BasicTarget[], //DOM 节点或者 Ref,支持数组 eventName: string | string[] = 'click', //指定需要监听的事件,支持数组 ) { const onClickAwayRef = useLatest(onClickAway); useEffectWithTarget( () => { //3.精髓在于如何处理事件 const handler = (event: any) => { const targets = Array.isArray(target) ? target : [target]; if ( targets.some((item) => { const targetElement = getTargetElement(item); //没有找到指定的dom,或者指定的dom包含了事件发生的target,也就是事件发生在指定的dom,就不会执行后续方法 return !targetElement || targetElement.contains(event.target); }) ) { return; } //判断事件是否发生在其他dom对象上,是的话就调用onClickAway方法 onClickAwayRef.current(event); }; //1.找到需要绑定事件的DOM节点,判断当前根节点是否是document或者影子root,可以理解为DOM中的DOM? const documentOrShadow = getDocumentOrShadow(target); //可以传入多个事件 const eventNames = Array.isArray(eventName) ? eventName : [eventName]; //2.绑定多个事件 eventNames.forEach((event) => documentOrShadow.addEventListener(event, handler)); return () => { eventNames.forEach((event) => documentOrShadow.removeEventListener(event, handler)); }; }, Array.isArray(eventName) ? eventName : [eventName], target, ); }
const [position, setPosition] = useRafState<Position>(); useEffectWithTarget(() => { //target不传就是document const el = getTargetElement(target, document); ... const updatePosition = () => { let newPosition; if (el === document) { //scrollingElement返回滚动文档的 Element 对象的引用。在标准模式下,这是文档的根元素, document.documentElement。 newPosition = { left: document.scrollingElement.scrollLeft, top: document.scrollingElement.scrollTop, }; ... } else { newPosition = { left: (el as Element).scrollLeft, top: (el as Element).scrollTop, }; } setPosition(newPosition); }; updatePosition(); el.addEventListener('scroll', updatePosition); ... }, [], target); return position;
如果搞不清楚useCallback的用法,建议不要用useMemoizedFn。
在某些场景中,我们需要使用 useCallback 来记住一个函数,但是在第二个参数 deps 变化时,会重新生成函数,导致函数地址变化。
const [state, setState] = useState('');
// 在 state 变化时,func 地址会变化
const func = useCallback(() => {
console.log(state);
}, [state]);
使用 useMemoizedFn,可以省略第二个参数 deps,同时保证函数地址永远不会变化。
const [state, setState] = useState('');
// func 地址永远不会变化
const func = useMemoizedFn(() => {
console.log(state);
});
import { useMemoizedFn } from 'ahooks'; import { message } from 'antd'; import React, { useCallback, useMemo, useRef, useState } from 'react'; export default () => { const [count, setCount] = useState(0); const callbackFn = useCallback(() => { message.info(`Current count is ${count}`); }, [count]); const memoizedFn = useMemoizedFn(() => { message.info(`Current count is ${count}`); }); const normalFn = () => { message.info(`Current count is ${count}`); } return ( <> <p>count: {count}</p> <button type="button" onClick={() => { setCount((c) => c + 1); }} > Add Count </button> <p>可以单击该按钮以查看子组件渲染的数量</p> <div style={{ marginTop: 32 }}> <h3>具有useCallback函数的组件:</h3> {/* use callback function, ExpensiveTree component will re-render on state change */} {/* { useMemo(()=> <ExpensiveTree showCount={callbackFn} />, [callbackFn]) } */} <ExpensiveTree showCount={callbackFn} /> </div> <div style={{ marginTop: 32 }}> <h3>具有useMemoizedFn函数的组件:</h3> {/* use memoized function, ExpensiveTree component will only render once */} {/* { useMemo(()=> <ExpensiveTree showCount={memoizedFn} />, [memoizedFn]) } */} <ExpensiveTree showCount={memoizedFn} /> </div> <div style={{ marginTop: 32 }}> <h3>普通方法:</h3> {/* { useMemo(()=> <ExpensiveTree showCount={normalFn} />, [normalFn]) } */} <ExpensiveTree showCount={normalFn} /> </div> </> ); }; // some expensive component with React.memo const ExpensiveTree = React.memo(({ showCount }) => { const renderCountRef = useRef(0); renderCountRef.current += 1; return ( <div> <p>子组件渲染次数: {renderCountRef.current}</p> <button type="button" onClick={showCount}> showParentCount </button> </div> ); });
源码就不讲了,比较绕,关键在于fnRef.current.apply(this, args);
方法
从star数量和下载量来看都是react-use占优,以后可以考虑使用react-use。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。