当前位置:网站首页>ahooks 是怎么处理 DOM 的?
ahooks 是怎么处理 DOM 的?
2022-08-01 20:58:00 【GopalFeng】
本文是深入浅出 ahooks 源码系列文章的第十三篇,这个系列的目标主要有以下几点:
- 加深对 React hooks 的理解。
- 学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。
- 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。
本篇文章探讨一下 ahooks 对 DOM 类 Hooks 使用规范,以及源码中是如何去做处理的。
DOM 类 Hooks 使用规范
这一章节,大部分参考官方文档的 DOM 类 Hooks 使用规范[1]。
第一点,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]
DOM 类 Hooks 使用规范: https://ahooks.js.org/zh-CN/guide/dom
边栏推荐
- excel高级绘图技巧100讲(二十二)-如何对不规则数据进行分列
- 【Kaggle】Classify Leaves
- 【Kaggle】House Prices
- 【Social Media Marketing】How to know if your WhatsApp is blocked?
- 【个人作品】记之-串口日志记录工具
- LeetCode每日一题(1807. Evaluate the Bracket Pairs of a String)
- MySQL语法基础
- KDD2022 | Self-Supervised Hypergraph Transformer Recommendation System
- STAHL touch screen repair all-in-one display screen ET-316-TX-TFT common faults
- Interview Blitz 70: What are sticky packs and half packs?How to deal with it?
猜你喜欢
【Kaggle】Classify Leaves
WhatsApp group sending actual combat sharing - WhatsApp Business API account
响应式织梦模板美容整形类网站
[Multi-task learning] Modeling Task Relationships in Multi-task Learning with Multi-gate Mixture-of-Experts KDD18
C语言之字符串函数二
30+的女性测试人面试经验分享
Imitation cattle forum project
微信小程序云开发|个人博客小程序
网络安全与基础设施安全局(CISA):两国将在网络安全方面扩大合作
Fork/Join线程池
随机推荐
Where should I prepare for the PMP exam in September?
STAHL触摸屏维修一体机显示屏ET-316-TX-TFT常见故障
【Untitled】
使用百度EasyDL实现厂区工人抽烟行为识别
Multithreaded producers and consumers
Digital twin Beijing the imperial palace, yuan universe is the process of tourism
Failed to re-init queues : Illegal queue capacity setting (abs-capacity=0.6) > (abs-maximum-capacity
有用的网站
织梦模板加入php代码
1374. 生成每种字符都是奇数个的字符串 : 简单构造模拟题
Excel advanced drawing techniques, 100 (22) - how to respectively the irregular data
用户身份标识与账号体系实践
Use WeChat official account to send information to designated WeChat users
网红驼背矫正产品真的管用吗?如何预防驼背?医生说要这样做
【Kaggle】Classify Leaves
30+的女性测试人面试经验分享
OSG Notes: Set DO_NOT_COMPUTE_NEAR_FAR to manually calculate far and near planes
Pytorch学习记录(八):生成对抗网络GAN
AQS原理和介绍
tiup mirror clone