本文是深入浅出 ahooks 源码系列文章的第十三篇,该系列已整理成文档-地址。觉得还不错,给个 star 支持一下哈,Thanks。
本篇文章探讨一下 ahooks 对 DOM 类 Hooks 使用规范,以及源码中是如何去做处理的。
这一章节,大部分参考官方文档的 DOM 类 Hooks 使用规范。
第一点,ahooks 大部分 DOM 类 Hooks 都会接收 target 参数,表示要处理的元素。
target 支持三种类型 React.MutableRefObject(通过 useRef 保存的 DOM)、HTMLElement、() => HTMLElement(一般运用于 SSR 场景)。
第二点,DOM 类 Hooks 的 target 是支持动态变化的。如下所示:
export default () => { const [boolean, { toggle }] = useBoolean(); const ref = useRef(null); const ref2 = useRef(null); const isHovering = useHover(boolean ? ref : ref2); return ( <> <div ref={ref}>{isHovering ? 'hover' : 'leaveHover'}</div> <div ref={ref2}>{isHovering ? 'hover' : 'leaveHover'}</div> </> ); };
那 ahooks 是怎么处理这两点的呢?
获取到对应的 DOM 元素,这一点主要兼容以上第一点的入参规范。
React.MutableRefObject
类型。export function getTargetElement<T extends TargetType>(target: BasicTarget<T>, defaultElement?: T) { // 省略部分代码... let targetElement: TargetValue<T>; if (isFunction(target)) { // 支持函数获取 targetElement = target(); // 假如 ref,则返回 current } else if ('current' in target) { targetElement = target.current; // 支持 DOM } else { targetElement = target; } return targetElement; }
这个方法,主要是为了支持第二点,支持 target 动态变化。
其中 packages/hooks/src/utils/useEffectWithTarget.ts
是使用 useEffect。
import { useEffect } from 'react'; import createEffectWithTarget from './createEffectWithTarget'; const useEffectWithTarget = createEffectWithTarget(useEffect); export default useEffectWithTarget;
另外 其中 packages/hooks/src/utils/useLayoutEffectWithTarget.ts
是使用 useLayoutEffect。
import { useLayoutEffect } from 'react'; import createEffectWithTarget from './createEffectWithTarget'; const useEffectWithTarget = createEffectWithTarget(useLayoutEffect); export default useEffectWithTarget;
两者都是调用的 createEffectWithTarget,只是入参不同。
直接重点看这个 createEffectWithTarget 函数:
const createEffectWithTarget = (useEffectType: typeof useEffect | typeof useLayoutEffect) => { /** * @param effect * @param deps * @param target target should compare ref.current vs ref.current, dom vs dom, ()=>dom vs ()=>dom */ const useEffectWithTarget = ( effect: EffectCallback, deps: DependencyList, target: BasicTarget<any> | BasicTarget<any>[], ) => { const hasInitRef = useRef(false); const lastElementRef = useRef<(Element | null)[]>([]); const lastDepsRef = useRef<DependencyList>([]); const unLoadRef = useRef<any>(); // useEffect 或者 useLayoutEffect useEffectType(() => { // 处理 DOM 目标元素 const targets = Array.isArray(target) ? target : [target]; const els = targets.map((item) => getTargetElement(item)); // init run // 首次初始化的时候执行 if (!hasInitRef.current) { hasInitRef.current = true; lastElementRef.current = els; lastDepsRef.current = deps; // 执行回调中的 effect 函数 unLoadRef.current = effect(); return; } // 非首次执行的逻辑 if ( // 目标元素或者依赖发生变化 els.length !== lastElementRef.current.length || !depsAreSame(els, lastElementRef.current) || !depsAreSame(deps, lastDepsRef.current) ) { // 执行上次返回的结果 unLoadRef.current?.(); // 更新 lastElementRef.current = els; lastDepsRef.current = deps; unLoadRef.current = effect(); } }); useUnmount(() => { // 卸载 unLoadRef.current?.(); // for react-refresh hasInitRef.current = false; }); }; return useEffectWithTarget; };
一个优秀的工具库应该有自己的一套输入输出规范,一来能够支持更多的场景,二来可以更好的在内部进行封装处理,三来使用者能够更加快速熟悉和使用相应的功能,能做到举一反三。
本文已收录到个人博客中,欢迎关注~