close
點擊下方「前端技術優選」,選擇「設為星標」
第一時間關注技術乾貨!

本文是深入淺出 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 等元素以及其事件名稱和回調參數都做了定義。

functionuseEventListener<KextendskeyofHTMLElementEventMap>(eventName:K,handler:(ev:HTMLElementEventMap[K])=>void,options?:Options<HTMLElement>,):void;functionuseEventListener<KextendskeyofElementEventMap>(eventName:K,handler:(ev:ElementEventMap[K])=>void,options?:Options<Element>,):void;functionuseEventListener<KextendskeyofDocumentEventMap>(eventName:K,handler:(ev:DocumentEventMap[K])=>void,options?:Options<Document>,):void;functionuseEventListener<KextendskeyofWindowEventMap>(eventName:K,handler:(ev:WindowEventMap[K])=>void,options?:Options<Window>,):void;functionuseEventListener(eventName:string,handler:noop,options:Options):void;

內部代碼比較簡單:

判斷是否支持 addEventListener,支持則將參數進行傳遞。可以留意注釋中的幾個參數的作用,當做複習,這裡不展開細說。
useEffect 的返回邏輯,也就是組件卸載的時候,會自動清除事件監聽器,避免產生內存泄露。
functionuseEventListener(//事件名稱eventName:string,//處理函數handler:noop,//設置options:Options={},){consthandlerRef=useLatest(handler);useEffectWithTarget(()=>{consttargetElement=getTargetElement(options.target,window);if(!targetElement?.addEventListener){return;}consteventListener=(event:Event)=>{returnhandlerRef.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,開發者可以自行傳遞並支持數組方式。

exportdefaultfunctionuseClickAway<TextendsEvent=Event>(//觸發函數onClickAway:(event:T)=>void,//DOM節點或者Ref,支持數組target:BasicTarget|BasicTarget[],//指定需要監聽的事件,支持數組eventName:string|string[]='click',){}

然後內部通過 document.addEventListener 監聽事件。組件卸載的時候清除事件監聽。

//事件列表consteventNames=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 函數。

consthandler=(event:any)=>{consttargets=Array.isArray(target)?target:[target];if(//判斷點擊的DOMTarget是否在定義的DOM元素(列表)中targets.some((item)=>{consttargetElement=getTargetElement(item);return!targetElement||targetElement.contains(event.target);})){return;}//觸發點擊事件onClickAwayRef.current(event);};

小結一下,useClickAway 就是使用了事件代理的方式,通過 document 監聽事件,判斷觸發事件的 DOM 元素是否在 target 列表中,從而決定是否要觸發定義好的函數。

useEventTarget

常見表單控件(通過 e.target.value 獲取表單值) 的 onChange 跟 value 邏輯封裝,支持自定義值轉換和重置功能。

直接看代碼,比較簡單,其實就是監聽表單的 onChange 事件,拿到值後更新 value 值,更新的邏輯支持自定義。

functionuseEventTarget<T,U=T>(options?:Options<T,U>){const{initialValue,transformer}=options||{};const[value,setValue]=useState(initialValue);//自定義轉換函數consttransformerRef=useLatest(transformer);constreset=useCallback(()=>setValue(initialValue),[]);constonChange=useCallback((e:EventTarget<U>)=>{//獲取e.target.value的值,並進行設置const_value=e.target.value;if(isFunction(transformerRef.current)){returnsetValue(transformerRef.current(_value));}//notransformer=>UandTshouldbethesamereturnsetValue(_valueasunknownasT);},[]);return[value,{onChange,reset,},]asconst;}useTitle

用於設置頁面標題。

這個頁面標題指的是瀏覽器 Tab 中展示的。通過 document.title 設置。

代碼非常簡單,一看就會:

functionuseTitle(title:string,options:Options=DEFAULT_OPTIONS){consttitleRef=useRef(isBrowser?document.title:'');useEffect(()=>{document.title=title;},[title]);useUnmount(()=>{//組件卸載後,恢復上一次的titleif(options.restoreOnUnmount){document.title=titleRef.current;}});}useFavicon

設置頁面的 favicon。

favicon 指的是頁面 Tab 的這個 ICON。

原理是通過 link 標籤設置 favicon。

constuseFavicon=(href:string)=>{useEffect(()=>{if(!href)return;constcutUrl=href.split('.');constimgSuffix=cutUrl[cutUrl.length-1].toLocaleUpperCase()asImgTypes;constlink:HTMLLinkElement=document.querySelector("link[rel*='icon']")||document.createElement('link');//用於定義鏈接的內容的類型。link.type=ImgTypeMap[imgSuffix];//指定被鏈接資源的URL。link.href=href;//此屬性命名鏈接文檔與當前文檔的關係。link.rel='shortcuticon';document.getElementsByTagName('head')[0].appendChild(link);},[href]);};

在看點這裡
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

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