当前位置:网站首页>那些关于DOM的常见Hook封装(一)
那些关于DOM的常见Hook封装(一)
2022-08-01 20:58:00 【GopalFeng】
本文是深入浅出 ahooks 源码系列文章的第十四篇,这个系列的目标主要有以下几点:
- 加深对 React hooks 的理解。
- 学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。
- 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。
上一篇我们探讨了 ahooks 对 DOM 类 Hooks 使用规范,以及源码中是如何去做处理的。接下来我们就针对关于 DOM 的各个 Hook 封装进行解读。
useEventListener
优雅的使用 addEventListener。
我们先来看看 addEventListener 的定义,以下来自 MDN 文档:
EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。
这里的 EventTarget 可以是一个文档上的元素 Element,Document和Window 或者任何其他支持事件的对象 (比如 XMLHttpRequest)。
我们看 useEventListener 函数 TypeScript 定义,通过类型重载,它对 Element、Document、Window 等元素以及其事件名称和回调参数都做了定义。
function useEventListener<K extends keyof HTMLElementEventMap>(
eventName: K,
handler: (ev: HTMLElementEventMap[K]) => void,
options?: Options<HTMLElement>,
): void;
function useEventListener<K extends keyof ElementEventMap>(
eventName: K,
handler: (ev: ElementEventMap[K]) => void,
options?: Options<Element>,
): void;
function useEventListener<K extends keyof DocumentEventMap>(
eventName: K,
handler: (ev: DocumentEventMap[K]) => void,
options?: Options<Document>,
): void;
function useEventListener<K extends keyof WindowEventMap>(
eventName: K,
handler: (ev: WindowEventMap[K]) => void,
options?: Options<Window>,
): void;
function useEventListener(eventName: string, handler: noop, options: Options): void;
内部代码比较简单:
- 判断是否支持 addEventListener,支持则将参数进行传递。可以留意注释中的几个参数的作用,当做复习,这里不展开细说。
- useEffect 的返回逻辑,也就是组件卸载的时候,会自动清除事件监听器,避免产生内存泄露。
function useEventListener(
// 事件名称
eventName: string,
// 处理函数
handler: noop,
// 设置
options: Options = {},
) {
const handlerRef = useLatest(handler);
useEffectWithTarget(
() => {
const targetElement = getTargetElement(options.target, window);
if (!targetElement?.addEventListener) {
return;
}
const eventListener = (event: Event) => {
return handlerRef.current(event);
};
// 监听事件
targetElement.addEventListener(eventName, eventListener, {
// listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
capture: options.capture,
// listener 在添加之后最多只调用一次。如果是 true,listener 会在其被调用之后自动移除。
once: options.once,
// 设置为 true 时,表示 listener 永远不会调用 preventDefault() 。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告
passive: options.passive,
});
// 移除事件
return () => {
targetElement.removeEventListener(eventName, eventListener, {
capture: options.capture,
});
};
},
[eventName, options.capture, options.once, options.passive],
options.target,
);
}
useClickAway
监听目标元素外的点击事件。
提到这个的应用场景,应该是模态框,点击外部阴影部分,自动关闭的场景。那这里它是怎么实现的呢?
首先它支持传递 DOM 节点或者 Ref,并且是支持数组方式。事件默认是支持 click,开发者可以自行传递并支持数组方式。
export default function useClickAway<T extends Event = Event>(
// 触发函数
onClickAway: (event: T) => void,
// DOM 节点或者 Ref,支持数组
target: BasicTarget | BasicTarget[],
// 指定需要监听的事件,支持数组
eventName: string | string[] = 'click',
) {
}
然后内部通过 document.addEventListener 监听事件。组件卸载的时候清除事件监听。
// 事件列表
const eventNames = Array.isArray(eventName) ? eventName : [eventName];
// document.addEventListener 监听事件,通过事件代理的方式知道目标节点
eventNames.forEach((event) => document.addEventListener(event, handler));
return () => {
eventNames.forEach((event) => document.removeEventListener(event, handler));
};
最后看 handler 函数,通过 event.target 获取到触发事件的对象 (某个 DOM 元素) 的引用,判断假如不在传入的 target 列表中,则触发定义好的 onClickAway 函数。
const handler = (event: any) => {
const targets = Array.isArray(target) ? target : [target];
if (
// 判断点击的 DOM Target 是否在定义的 DOM 元素(列表)中
targets.some((item) => {
const targetElement = getTargetElement(item);
return !targetElement || targetElement.contains(event.target);
})
) {
return;
}
// 触发点击事件
onClickAwayRef.current(event);
};
小结一下,useClickAway 就是使用了事件代理的方式,通过 document 监听事件,判断触发事件的 DOM 元素是否在 target 列表中,从而决定是否要触发定义好的函数。
useEventTarget
常见表单控件(通过 e.target.value 获取表单值) 的 onChange 跟 value 逻辑封装,支持自定义值转换和重置功能。
直接看代码,比较简单,其实就是监听表单的 onChange 事件,拿到值后更新 value 值,更新的逻辑支持自定义。
function useEventTarget<T, U = T>(options?: Options<T, U>) {
const { initialValue, transformer } = options || {};
const [value, setValue] = useState(initialValue);
// 自定义转换函数
const transformerRef = useLatest(transformer);
const reset = useCallback(() => setValue(initialValue), []);
const onChange = useCallback((e: EventTarget<U>) => {
// 获取 e.target.value 的值,并进行设置
const _value = e.target.value;
if (isFunction(transformerRef.current)) {
return setValue(transformerRef.current(_value));
}
// no transformer => U and T should be the same
return setValue(_value as unknown as T);
}, []);
return [
value,
{
onChange,
reset,
},
] as const;
}
useTitle
用于设置页面标题。
这个页面标题指的是浏览器 Tab 中展示的。通过 document.title 设置。
代码非常简单,一看就会:
function useTitle(title: string, options: Options = DEFAULT_OPTIONS) {
const titleRef = useRef(isBrowser ? document.title : '');
useEffect(() => {
document.title = title;
}, [title]);
useUnmount(() => {
// 组件卸载后,恢复上一次的 title
if (options.restoreOnUnmount) {
document.title = titleRef.current;
}
});
}
useFavicon
设置页面的 favicon。
favicon 指的是页面 Tab 的这个 ICON。
原理是通过 link 标签设置 favicon。
const useFavicon = (href: string) => {
useEffect(() => {
if (!href) return;
const cutUrl = href.split('.');
const imgSuffix = cutUrl[cutUrl.length - 1].toLocaleUpperCase() as ImgTypes;
const link: HTMLLinkElement =
document.querySelector("link[rel*='icon']") || document.createElement('link');
// 用于定义链接的内容的类型。
link.type = ImgTypeMap[imgSuffix];
// 指定被链接资源的URL。
link.href = href;
// 此属性命名链接文档与当前文档的关系。
link.rel = 'shortcut icon';
document.getElementsByTagName('head')[0].appendChild(link);
}, [href]);
};
边栏推荐
- MySQL 中出现的字符编码错误 Incorrect string value: ‘\x\x\x\x‘ for column ‘x‘
- 织梦通过数据库查询调用当前文章的留言
- MySQL Syntax Basics
- 】 【 nn. The Parameter () to generate and why do you want to initialize
- Nacos 配置中心
- 微信小程序云开发|个人博客小程序
- Qt设置应用程序开机自启 解决设置失败原因
- STAHL触摸屏维修一体机显示屏ET-316-TX-TFT常见故障
- LeetCode每日一题(1807. Evaluate the Bracket Pairs of a String)
- 数据库内核面试中我不会的问题(1)
猜你喜欢

Internet使用的网络协议是什么

Excel advanced drawing techniques, 100 (22) - how to respectively the irregular data

外骨骼机器人(七):标准步态数据库

vant实现Select效果--单选和多选

仿牛客论坛项目

Remove 360's detection and modification of the default browser

Imitation cattle forum project

【nn.Parameter()】生成和为什么要初始化

Digital twin Beijing the imperial palace, yuan universe is the process of tourism

C语言之字符串函数二
随机推荐
【Dart】dart之mixin探究
响应式织梦模板清洁服务类网站
Imitation cattle forum project
Based on FPGA in any number of bytes (single-byte or multibyte) serial port (UART) to send (including source engineering)
写给刚进互联网圈子的人,不管你是开发,测试,产品,运维都适用
Zheng Xiangling, Chairman of Tide Pharmaceuticals, won the "2022 Outstanding Influential Entrepreneur Award" Tide Pharmaceuticals won the "Corporate Social Responsibility Model Award"
360借条安全专家:陌生微信好友不要轻易加贷款推广多是诈骗
Simple test of the use of iptables
人工智能可信安全与评测
LTE time domain and frequency domain resources
Batch get protein .pdb files based on Uniprot ID/PDB ID
win10版本1803无法升级1903系统如何解决
用户身份标识与账号体系实践
算法---解码方法(Kotlin)
[Multi-task learning] Modeling Task Relationships in Multi-task Learning with Multi-gate Mixture-of-Experts KDD18
vant实现Select效果--单选和多选
98. Embedded controller EC actual combat EC development board development completed
myid file is missing
技能大赛训练:A部分加固题目
The configuration manual for the secondary development of the XE training system of the missing moment document system