close

本文是深入淺出 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 是支持動態變化的。如下所示:

exportdefault()=>{const[boolean,{toggle}]=useBoolean();constref=useRef(null);constref2=useRef(null);constisHovering=useHover(boolean?ref:ref2);return(<><divref={ref}>{isHovering?'hover':'leaveHover'}</div><divref={ref2}>{isHovering?'hover':'leaveHover'}</div></>);};

那 ahooks 是怎麼處理這兩點的呢?

getTargetElement

獲取到對應的 DOM 元素,這一點主要兼容以上第一點的入參規範。

假如是函數,則取執行完後的結果。
假如擁有 current 屬性,則取 current 屬性的值,兼容 React.MutableRefObject 類型。
最後就是普通的 DOM 元素。
exportfunctiongetTargetElement<TextendsTargetType>(target:BasicTarget<T>,defaultElement?:T){//省略部分代碼...lettargetElement:TargetValue<T>;if(isFunction(target)){//支持函數獲取targetElement=target();//假如ref,則返回current}elseif('current'intarget){targetElement=target.current;//支持DOM}else{targetElement=target;}returntargetElement;}useEffectWithTarget

這個方法,主要是為了支持第二點,支持 target 動態變化。

其中 packages/hooks/src/utils/useEffectWithTarget.ts 是使用 useEffect。

import{useEffect}from'react';importcreateEffectWithTargetfrom'./createEffectWithTarget';constuseEffectWithTarget=createEffectWithTarget(useEffect);exportdefaultuseEffectWithTarget;

另外 其中 packages/hooks/src/utils/useLayoutEffectWithTarget.ts 是使用 useLayoutEffect。

import{useLayoutEffect}from'react';importcreateEffectWithTargetfrom'./createEffectWithTarget';constuseEffectWithTarget=createEffectWithTarget(useLayoutEffect);exportdefaultuseEffectWithTarget;

兩者都是調用的 createEffectWithTarget,只是入參不同。

直接重點看這個 createEffectWithTarget 函數:

createEffectWithTarget 返回的函數 useEffectWithTarget 接受三個參數,前兩個跟 useEffect 一樣,第三個就是 target。
useEffectType 就是 useEffect 或者 useLayoutEffect。注意這裡調用的時候,沒傳第二個參數,也就是每次都會執行。
hasInitRef 判斷是否已經初始化。lastElementRef 記錄的是最後一次 target 元素的列表。lastDepsRef 記錄的是最後一次的依賴。unLoadRef 是執行完 effect 函數(對應的就是 useEffect 中的 effect 函數)的返回值,在組件卸載的時候執行。
第一次執行的時候,執行相應的邏輯,並記錄下最後一次執行的相應的 target 元素以及依賴。
後面每次執行的時候,都判斷目標元素或者依賴是否發生變化,發生變化,則執行對應的 effect 函數。並更新最後一次執行的依賴。
組件卸載的時候,執行 unLoadRef.current?.() 函數,並重置 hasInitRef 為 false。
constcreateEffectWithTarget=(useEffectType:typeofuseEffect|typeofuseLayoutEffect)=>{/***@parameffect*@paramdeps*@paramtargettargetshouldcompareref.currentvsref.current,domvsdom,()=>domvs()=>dom*/constuseEffectWithTarget=(effect:EffectCallback,deps:DependencyList,target:BasicTarget<any>|BasicTarget<any>[],)=>{consthasInitRef=useRef(false);constlastElementRef=useRef<(Element|null)[]>([]);constlastDepsRef=useRef<DependencyList>([]);constunLoadRef=useRef<any>();//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;//執行回調中的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?.();//forreact-refreshhasInitRef.current=false;});};returnuseEffectWithTarget;};思考與總結

一個優秀的工具庫應該有自己的一套輸入輸出規範,一來能夠支持更多的場景,二來可以更好的在內部進行封裝處理,三來使用者能夠更加快速熟悉和使用相應的功能,能做到舉一反三。

參考資料
[1]

DOM 類 Hooks 使用規範: https://ahooks.js.org/zh-CN/guide/dom

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()