This article is the fourteenth in a series of in-depth ahoos source code articles, which have been organized into document- address . I think it's not bad, give a star to support it, thanks.
In the last article, we discussed the use of ahooks for DOM class Hooks and how to deal with them in the source code. Next, we will interpret each Hook package about DOM.
useEventListener
Use addEventListener gracefully.
Let's first take a look at the definition of addEventListener, the following is from the MDN documentation:
The EventTarget.addEventListener() method registers the specified listener with the EventTarget. When the object triggers the specified event, the specified callback function will be executed.
The EventTarget here can be an element Element, Document and Window on a document or any other object that supports events (eg XMLHttpRequest).
Let's look at the TypeScript definition of the useEventListener function. Through type overloading, it defines elements such as Element, Document, and Window, as well as their event names and callback parameters.
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;
The internal code is relatively simple:
- Determine whether addEventListener is supported, and pass the parameters if it is supported. You can pay attention to the functions of several parameters in the comments. As a review, I will not elaborate here.
- The return logic of useEffect, that is, when the component is unloaded, will automatically clear the event listener to avoid memory leaks.
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
Listen for click events outside the target element.
The application scenario that mentions this should be a modal box, click on the outer shadow part, and automatically close the scene. So how is it achieved here?
First of all, it supports passing DOM nodes or Ref, and supports array mode.
The default event is to support click, and developers can pass it by themselves and support the array method.
export default function useClickAway<T extends Event = Event>(
// 触发函数
onClickAway: (event: T) => void,
// DOM 节点或者 Ref,支持数组
target: BasicTarget | BasicTarget[],
// 指定需要监听的事件,支持数组
eventName: string | string[] = 'click',
) {
}
Then internally listen for events through document.addEventListener. Clear the event listener when the component is unloaded.
// 事件列表
const eventNames = Array.isArray(eventName) ? eventName : [eventName];
// document.addEventListener 监听事件,通过事件代理的方式知道目标节点
eventNames.forEach((event) => document.addEventListener(event, handler));
return () => {
eventNames.forEach((event) => document.removeEventListener(event, handler));
};
Finally, look at the handler function, obtain the reference of the object that triggered the event (a DOM element) through event.target, and judge that if it is not in the incoming target list, the defined onClickAway function will be triggered.
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);
};
To sum up, useClickAway uses the method of event proxy, listens for events through document, determines whether the DOM element that triggers the event is in the target list, and then decides whether to trigger the defined function.
useEventTarget
Common form controls (get form values through e.target.value) encapsulate onChange and value logic, and support custom value conversion and reset functions.
Looking at the code directly, it is relatively simple. In fact, it is to listen to the onChange event of the form, update the value value after getting the value, and the updated logic supports customization.
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
Used to set the page title.
This page title refers to what is displayed in the browser tab. Set by document.title.
The code is very simple, just look at it and you will:
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
Set the favicon of the page.
favicon refers to this ICON of the page Tab.
The principle is to set the favicon through the link tag.
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]);
};
This article has been included in the personal blog , welcome to pay attention~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。