本文是深入淺出 ahooks 源碼系列文章的第十四篇,這個系列的目標主要有以下幾點:
上一篇我們探討了 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;內部代碼比較簡單:
監聽目標元素外的點擊事件。
提到這個的應用場景,應該是模態框,點擊外部陰影部分,自動關閉的場景。那這裡它是怎麼實現的呢?
首先它支持傳遞 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]);};
