当前位置:网站首页>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
边栏推荐
- Acrel-5010重点用能单位能耗在线监测系统在湖南三立集团的应用
- 微信小程序云开发|个人博客小程序
- 数字孪生北京故宫,元宇宙推进旅游业进程
- 360借条安全专家:陌生微信好友不要轻易加贷款推广多是诈骗
- SkiaSharp 之 WPF 自绘 五环弹动球(案例版)
- 虚拟机的IP地址自动变为127.0.0.1
- 函数(二)
- 【Kaggle】House Prices
- [Energy Conservation Institute] Ankerui Food and Beverage Fume Monitoring Cloud Platform Helps Fight Air Pollution
- Pytorch框架学校记录11——搭建小实战完整细节
猜你喜欢

MongoDB快速上手

Hiking, cured my mental internal friction

KDD2022 | Self-Supervised Hypergraph Transformer Recommendation System

Convolutional Neural Network (CNN) mnist Digit Recognition - Tensorflow

写给刚进互联网圈子的人,不管你是开发,测试,产品,运维都适用

Imitation cattle forum project

4.1 配置Mysql与注册登录模块

Application of Acrel-5010 online monitoring system for key energy consumption unit energy consumption in Hunan Sanli Group

Little data on how to learn?Jida latest small learning data review, 26 PDF page covers the 269 - page document small data learning theory, method and application are expounded

Remove 360's detection and modification of the default browser
随机推荐
Pytorch框架学习记录12——完整的模型训练套路
虚拟机的IP地址自动变为127.0.0.1
tiup mirror
"Torch" tensor multiplication: matmul, einsum
tiup mirror clone
system collection
Pytorch框架学习记录9——非线性激活
useful website
Go 语言中常见的坑
【Kaggle】Classify Leaves
线程池处理异常的方法
任务调度线程池基本介绍
仿牛客论坛项目
Pytorch框架学校记录11——搭建小实战完整细节
excel高级绘图技巧100讲(二十二)-如何对不规则数据进行分列
What is the difference between a utility model patent and an invention patent?Understand in seconds!
任务调度线程池-应用定时任务
数据库内核面试中我不会的问题(1)
MySQL Syntax Basics
Failed to re-init queues : Illegal queue capacity setting (abs-capacity=0.6) > (abs-maximum-capacity