JavaScript中ahooks?處理?DOM?的方法
前言
目標(biāo)主要有以下幾點(diǎn):
- 加深對 React hooks 的理解。
- 學(xué)習(xí)如何抽象自定義 hooks。構(gòu)建屬于自己的 React hooks 工具庫。
- 培養(yǎng)閱讀學(xué)習(xí)源碼的習(xí)慣,工具庫是一個對源碼閱讀不錯的選擇。
本篇文章探討一下 ahooks 對 DOM 類 Hooks 使用規(guī)范,以及源碼中是如何去做處理的。
DOM 類 Hooks 使用規(guī)范
這一章節(jié),大部分參考官方文檔的 DOM 類 Hooks 使用規(guī)范。
第一點(diǎn),ahooks 大部分 DOM 類 Hooks 都會接收 target 參數(shù),表示要處理的元素。
target 支持三種類型 React.MutableRefObject(通過 useRef 保存的 DOM)、HTMLElement、() => HTMLElement(一般運(yùn)用于 SSR 場景)。
第二點(diǎn),DOM 類 Hooks 的 target 是支持動態(tài)變化的。
如下所示:
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> </> ); };
那 ahooks 是怎么處理這兩點(diǎn)的呢?
getTargetElement
獲取到對應(yīng)的 DOM 元素,這一點(diǎn)主要兼容以上第一點(diǎn)的入?yún)⒁?guī)范。
- 假如是函數(shù),則取執(zhí)行完后的結(jié)果。
- 假如擁有 current 屬性,則取 current 屬性的值,兼容
React.MutableRefObject
類型。 - 最后就是普通的 DOM 元素。
export function getTargetElement<T extends TargetType>(target: BasicTarget<T>, defaultElement?: T) { // 省略部分代碼... let targetElement: TargetValue<T>; if (isFunction(target)) { // 支持函數(shù)獲取 targetElement = target(); // 假如 ref,則返回 current } else if ('current' in target) { targetElement = target.current; // 支持 DOM } else { targetElement = target; } return targetElement; }
useEffectWithTarget
這個方法,主要是為了支持第二點(diǎn),支持 target 動態(tài)變化。
其中 packages/hooks/src/utils/useEffectWithTarget.ts
是使用 useEffect。
import { useEffect } from 'react'; import createEffectWithTarget from './createEffectWithTarget'; const useEffectWithTarget = createEffectWithTarget(useEffect); export default useEffectWithTarget;
另外 其中 packages/hooks/src/utils/useLayoutEffectWithTarget.ts
是使用 useLayoutEffect。
import { useLayoutEffect } from 'react'; import createEffectWithTarget from './createEffectWithTarget'; const useEffectWithTarget = createEffectWithTarget(useLayoutEffect); export default useEffectWithTarget;
兩者都是調(diào)用的 createEffectWithTarget,只是入?yún)⒉煌?/p>
直接重點(diǎn)看這個 createEffectWithTarget 函數(shù):
- createEffectWithTarget 返回的函數(shù) useEffectWithTarget 接受三個參數(shù),前兩個跟 useEffect 一樣,第三個就是 target。
- useEffectType 就是 useEffect 或者 useLayoutEffect。注意這里調(diào)用的時候,沒傳第二個參數(shù),也就是每次都會執(zhí)行。
- hasInitRef 判斷是否已經(jīng)初始化。lastElementRef 記錄的是最后一次 target 元素的列表。lastDepsRef 記錄的是最后一次的依賴。unLoadRef 是執(zhí)行完 effect 函數(shù)(對應(yīng)的就是 useEffect 中的 effect 函數(shù))的返回值,在組件卸載的時候執(zhí)行。
- 第一次執(zhí)行的時候,執(zhí)行相應(yīng)的邏輯,并記錄下最后一次執(zhí)行的相應(yīng)的 target 元素以及依賴。
- 后面每次執(zhí)行的時候,都判斷目標(biāo)元素或者依賴是否發(fā)生變化,發(fā)生變化,則執(zhí)行對應(yīng)的 effect 函數(shù)。并更新最后一次執(zhí)行的依賴。
- 組件卸載的時候,執(zhí)行 unLoadRef.current?.() 函數(shù),并重置 hasInitRef 為 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 目標(biāo)元素 const targets = Array.isArray(target) ? target : [target]; const els = targets.map((item) => getTargetElement(item)); // init run // 首次初始化的時候執(zhí)行 if (!hasInitRef.current) { hasInitRef.current = true; lastElementRef.current = els; lastDepsRef.current = deps; // 執(zhí)行回調(diào)中的 effect 函數(shù) unLoadRef.current = effect(); return; } // 非首次執(zhí)行的邏輯 if ( // 目標(biāo)元素或者依賴發(fā)生變化 els.length !== lastElementRef.current.length || !depsAreSame(els, lastElementRef.current) || !depsAreSame(deps, lastDepsRef.current) ) { // 執(zhí)行上次返回的結(jié)果 unLoadRef.current?.(); // 更新 lastElementRef.current = els; lastDepsRef.current = deps; unLoadRef.current = effect(); } }); useUnmount(() => { // 卸載 unLoadRef.current?.(); // for react-refresh hasInitRef.current = false; }); }; return useEffectWithTarget; };
思考與總結(jié)
一個優(yōu)秀的工具庫應(yīng)該有自己的一套輸入輸出規(guī)范,一來能夠支持更多的場景,二來可以更好的在內(nèi)部進(jìn)行封裝處理,三來使用者能夠更加快速熟悉和使用相應(yīng)的功能,能做到舉一反三。
到此這篇關(guān)于JavaScript中ahooks 處理 DOM 的方法的文章就介紹到這了,更多相關(guān)ahooks 處理 DOM內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js中動態(tài)創(chuàng)建json,動態(tài)為json添加屬性、屬性值的實(shí)例
下面小編就為大家?guī)硪黄猨s中動態(tài)創(chuàng)建json,動態(tài)為json添加屬性、屬性值的實(shí)例。小編覺的挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12javascript+css 網(wǎng)頁每次加載不同樣式的實(shí)現(xiàn)方法
用戶每次訪問時隨機(jī)載入樣式,讓微博在視覺上保持新鮮感。雖然思路與實(shí)現(xiàn)都比較簡單,但還是想記錄下來,與大家分享。2009-12-12bootstrap模態(tài)框嵌套、tabindex屬性、去除陰影的示例代碼
這篇文章主要介紹了bootstrap模態(tài)框嵌套、tabindex屬性、去除陰影,需要的朋友可以參考下2017-10-10javascript + jquery實(shí)現(xiàn)定時修改文章標(biāo)題
用javascript+jquery寫的一個定時器,定時修改文章標(biāo)題,需要的朋友可以參考下2014-03-03