当前位置: 首页 > Web前端 > JavaScript

大家都能看懂的源码——ahooks是如何处理DOM的?

时间:2023-03-26 22:27:22 JavaScript

本文是深入浅出ahooks源码系列文章的第十三篇。本系列已整理成文档地址。觉得还不错,给个star支持一下,谢谢。本文讨论ahooks如何使用DOMHooks以及如何在源代码中处理它们。在DOM类Hooks使用规范章节中,大部分参考了官方文档中的DOM类Hooks使用规范。一、ahooks大多数DOM类Hooks都会接收一个target参数,表示要处理的元素。target支持三种类型:React.MutableRefObject(useRef保存的DOM),HTMLElement,()=>HTMLElement(一般用于SSR场景)。第二点是DOMHooks的target支持动态变化。如下:exportdefault()=>{const[boolean,{toggle}]=useBoolean();constref=useRef(null);constref2=useRef(空);constisHovering=useHover(boolean?ref:ref2);返回(<>{isHovering?'hover':'leaveHover'}

{isHovering?'hover':'leaveHover'}
);};那么ahooks是如何处理这两点的呢?getTargetElement获取对应的DOM元素,主要是兼容上面第一点的输入规范。如果是函数,则取执行后的结果。如果它有当前属性,则取当前属性的值,它与React.MutableRefObject类型兼容。最后,还有普通的DOM元素。exportfunctiongetTargetElement(target:BasicTarget,defaultElement?:T){//省略一些代码...lettargetElement:TargetValue;if(isFunction(target)){//支持函数GettargetElement=target();//ifref,returncurrent}elseif('current'intarget){targetElement=target.current;//支持DOM}else{targetElement=target;}returntargetElement;}useEffectWithTargetthis方法主要是为了支持第二点,支持target的动态变化。其中packages/hooks/src/utils/useEffectWithTarget.ts就是使用useEffect。import{useEffect}from'react';importcreateEffectWithTargetfrom'./createEffectWithTarget';constuseEffectWithTarget=createEffectWithTarget(useEffect);exportdefaultuseEffectWithTarget;另外使用packages/hooks/src/utils/useLayoutEffectWithTarget。从'react'导入{useLayoutEffect};从'./createEffectWithTarget'导入createEffectWithTarget;constuseEffectWithTarget=createEffectWithTarget(useLayoutEffect);导出默认useEffectWithTarget;都调用createEffectWithTarget,只是入参不同。直接关注这个createEffectWithTarget函数:createEffectWithTarget返回的函数useEffectWithTarget接受三个参数,前两个和useEffect一样,第三个是target。useEffectType是useEffect或useLayoutEffect。注意这里调用的时候不传第二个参数,即每次都会执行。hasInitRef判断是否已经初始化。lastElementRef记录最后一个目标元素的列表。lastDepsRef记录最后的依赖关系。unLoadRef是effect函数(对应useEffect中的effect函数)执行后的返回值,在组件卸载时执行。第一次执行时,执行对应的逻辑,记录上次执行对应的目标元素和依赖。后面每次执行时,都会判断目标元素或依赖是否发生变化,如果发生变化,则执行相应的效果函数。并更新上次执行的依赖。当组件卸载时,执行unLoadRef.current?.()函数,将hasInitRef重置为false。constcreateEffectWithTarget=(useEffectType:typeofuseEffect|typeofuseLayoutEffect)=>{/***@parameffect*@paramdeps*@paramtarget目标应该比较ref.current与ref.current,dom与dom,()=>domvs()=>dom*/constuseEffectWithTarget=(effect:EffectCallback,deps:DependencyList,target:BasicTarget|BasicTarget[],)=>{consthasInitRef=useRef(false);constlastElementRef=useRef<(Element|null)[]>([]);constlastDepsRef=useRef([]);constunLoadRef=useRef<任何>();//useEffect或useLayoutEffectuseEffectType(()=>{//处理DOM目标元素consttargets=Array.isArray(target)?target:[target];constels=targets.map((item)=>getTargetElement(item));//initrun//第一次开始化的时执行if(!hasInitRef.current){hasInitRef.current=true;lastElementRef.current=els;lastDepsRef.current=deps;//在回调中执行效果函数unLoadRef.current=effect();返回;}//非先执行逻辑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?.();//用于react-refreshhasInitRef.current=false;});};returnuseEffectWithTarget;};思考总结一个优秀的工具库应该有自己的一套输入输出规范,一是可以支持更多的场景,二是内部可以更好的封装,三是以后用户可以熟悉并能更快地使用相应的功能,并能举一反三。本文已收录在我的个人博客中,欢迎关注~