本文是深入浅出 ahooks 源码系列文章的第十三篇,该系列已整理成文档-地址。觉得还不错,给个 star 支持一下哈,Thanks。

本篇文章探讨一下 ahooks 对 DOM 类 Hooks 使用规范,以及源码中是如何去做处理的。

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 是怎么处理这两点的呢?

getTargetElement

获取到对应的 DOM 元素,这一点主要兼容以上第一点的入参规范。

  • 假如是函数,则取执行完后的结果。
  • 假如拥有 current 属性,则取 current 属性的值,兼容 React.MutableRefObject 类型。
  • 最后就是普通的 DOM 元素。
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;
}

useEffectWithTarget

这个方法,主要是为了支持第二点,支持 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 函数:

  • createEffectWithTarget 返回的函数 useEffectWithTarget 接受三个参数,前两个跟 useEffect 一样,第三个就是 target。
  • useEffectType 就是 useEffect 或者 useLayoutEffect。注意这里调用的时候,没传第二个参数,也就是每次都会执行
  • hasInitRef 判断是否已经初始化。lastElementRef 记录的是最后一次 target 元素的列表。lastDepsRef 记录的是最后一次的依赖。unLoadRef 是执行完 effect 函数(对应的就是 useEffect 中的 effect 函数)的返回值,在组件卸载的时候执行。
  • 第一次执行的时候,执行相应的逻辑,并记录下最后一次执行的相应的 target 元素以及依赖。
  • 后面每次执行的时候,都判断目标元素或者依赖是否发生变化,发生变化,则执行对应的 effect 函数。并更新最后一次执行的依赖。
  • 组件卸载的时候,执行 unLoadRef.current?.() 函数,并重置 hasInitRef 为 false。
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;
};

思考与总结

一个优秀的工具库应该有自己的一套输入输出规范,一来能够支持更多的场景,二来可以更好的在内部进行封装处理,三来使用者能够更加快速熟悉和使用相应的功能,能做到举一反三。

本文已收录到个人博客中,欢迎关注~

最新文章

  1. pct xcode7
  2. Linux网络编程(多人在线聊天系统)
  3. BZOJ1040 [ZJOI2008]骑士
  4. iOS9 beta 请求出现App Transport Security has blocked a cleartext HTTP (http://)
  5. svn ubuntu command(转载)
  6. C语言异常与断言接口与实现
  7. QObject::deleteLater()并没有将对象立即销毁,而是向主消息循环发送了一个event,下一次主消息循环收到这个event之后才会销毁对象 good
  8. wordpress可视化编辑器的开启/关闭
  9. 从 SDWebImage 谈如何为开源软件做贡献
  10. ListView优化-getView优化
  11. @SuppressWarnings(unchecked)作用解释
  12. BZOJ2292: 【POJ Challenge 】永远挑战
  13. [Android学习笔记]RelativeLayout的使用
  14. centos6.6-------DHCP服务配置
  15. 大数据学习系列之一 ----- Hadoop环境搭建(单机)
  16. 2016-3-1 安装Hexo过程中遇到的问题
  17. 如何用 Python 模糊搜索文件
  18. “京东金融”主页效果 RecyclerView联动
  19. 学习Angularjs向数据库添加数据
  20. Android之TextView灵活使用

热门文章

  1. 3. Docker应用
  2. JAVA 线程的6种状态
  3. 开发工具-Redis Desktop Manager下载地址
  4. tensorflow版本的bert模型 GPU的占用率为100%而其利用率为0%
  5. 七、服务器硬件及RAID配置实战
  6. 视图模板引擎——Vue【双向绑定】原理剖析
  7. Eclipse拷贝动态的web工程
  8. 使用ventoy制作启动盘
  9. JavaScript进阶知识点——函数和对象详解
  10. 一文解析Pinia和Vuex,带你全面理解这两个Vue状态管理模式