This article is the thirteenth of a series of articles explaining the source code of ahoos in simple language, which has been organized into document- address . I think it's not bad, give a star to support it, thanks.
This article discusses the use of ahooks for DOM-like Hooks, and how to deal with them in the source code.
DOM class Hooks usage specification
In this chapter, most of the reference to the official document's DOM class Hooks usage specification .
The first point, hooks Most of the DOM class Hooks will receive the target parameter, indicating the element to be processed.
target supports three types React.MutableRefObject (DOM saved by useRef), HTMLElement, () => HTMLElement (generally used in SSR scenarios).
The second point is that the target of DOM class Hooks supports dynamic changes. As follows:
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>
</>
);
};
How does ahooks deal with these two points?
getTargetElement
Obtain the corresponding DOM element, which is mainly compatible with the input specification of the first point above.
- If it is a function, take the result after execution.
- If it has the current attribute, it will take the value of the current attribute, which is compatible with the
React.MutableRefObject
type. - Finally, there are normal DOM elements.
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
This method is mainly to support the second point, to support the dynamic change of target.
Where packages/hooks/src/utils/useEffectWithTarget.ts
is to use useEffect.
import { useEffect } from 'react';
import createEffectWithTarget from './createEffectWithTarget';
const useEffectWithTarget = createEffectWithTarget(useEffect);
export default useEffectWithTarget;
In addition, packages/hooks/src/utils/useLayoutEffectWithTarget.ts
is to use useLayoutEffect.
import { useLayoutEffect } from 'react';
import createEffectWithTarget from './createEffectWithTarget';
const useEffectWithTarget = createEffectWithTarget(useLayoutEffect);
export default useEffectWithTarget;
Both are called createEffectWithTarget, but the input parameters are different.
Focus directly on this createEffectWithTarget function:
- The function useEffectWithTarget returned by createEffectWithTarget accepts three parameters, the first two are the same as useEffect, and the third is target.
- useEffectType is useEffect or useLayoutEffect. Note that when calling here, the second parameter is not passed, that is, it will be executed every time .
- hasInitRef determines whether it has been initialized. lastElementRef records the last list of target elements. lastDepsRef records the last dependency. unLoadRef is the return value after executing the effect function (corresponding to the effect function in useEffect), which is executed when the component is unloaded.
- When it is executed for the first time, the corresponding logic is executed, and the corresponding target element and dependencies of the last execution are recorded.
- Each time it is executed later, it is judged whether the target element or dependency has changed. If the change occurs, the corresponding effect function is executed. and update the last executed dependency.
- When the component is unloaded, execute the unLoadRef.current?.() function and reset hasInitRef to 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;
};
Thinking and Summarizing
An excellent tool library should have its own set of input and output specifications. First, it can support more scenarios. Second, it can better encapsulate it internally. Third, users can quickly familiarize itself with and use the corresponding functions. Can do inferences.
This article has been included in the personal blog , welcome to pay attention~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。