大家都能看得懂的源碼 - ahooks 是怎麼處理 DOM 的?

来源:https://www.cnblogs.com/gopal/archive/2022/08/27/16629642.html
-Advertisement-
Play Games

本文是深入淺出 ahooks 源碼系列文章的第十三篇,該系列已整理成文檔-地址。覺得還不錯,給個 star 支持一下哈,Thanks。 本篇文章探討一下 ahooks 對 DOM 類 Hooks 使用規範,以及源碼中是如何去做處理的。 DOM 類 Hooks 使用規範 這一章節,大部分參考官方文檔的 ...


本文是深入淺出 ahooks 源碼系列文章的第十三篇,該系列已整理成文檔-地址。覺得還不錯,給個 star 支持一下哈,Thanks。

本篇文章探討一下 ahooks 對 DOM 類 Hooks 使用規範,以及源碼中是如何去做處理的。

DOM 類 Hooks 使用規範

這一章節,大部分參考官方文檔的 DOM 類 Hooks 使用規範

第一點,ahooks 大部分 DOM 類 Hooks 都會接收 target 參數,表示要處理的元素。

target 支持三種類型 React.MutableRefObject(通過 useRef 保存的 DOM)、HTMLElement、() => HTMLElement(一般運用於 SSR 場景)。

第二點,DOM 類 Hooks 的 target 是支持動態變化的。如下所示:

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 是怎麼處理這兩點的呢?

getTargetElement

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

  • 假如是函數,則取執行完後的結果。
  • 假如擁有 current 屬性,則取 current 屬性的值,相容 React.MutableRefObject 類型。
  • 最後就是普通的 DOM 元素。
export function getTargetElement<T extends TargetType>(target: BasicTarget<T>, defaultElement?: T) {
  // 省略部分代碼...
  let targetElement: TargetValue<T>;

  if (isFunction(target)) {
    // 支持函數獲取
    targetElement = target();
    // 假如 ref,則返回 current
  } else if ('current' in target) {
    targetElement = target.current;
    // 支持 DOM
  } else {
    targetElement = target;
  }

  return targetElement;
}

useEffectWithTarget

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

其中 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;

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

直接重點看這個 createEffectWithTarget 函數:

  • createEffectWithTarget 返回的函數 useEffectWithTarget 接受三個參數,前兩個跟 useEffect 一樣,第三個就是 target。
  • useEffectType 就是 useEffect 或者 useLayoutEffect。註意這裡調用的時候,沒傳第二個參數,也就是每次都會執行
  • hasInitRef 判斷是否已經初始化。lastElementRef 記錄的是最後一次 target 元素的列表。lastDepsRef 記錄的是最後一次的依賴。unLoadRef 是執行完 effect 函數(對應的就是 useEffect 中的 effect 函數)的返回值,在組件卸載的時候執行。
  • 第一次執行的時候,執行相應的邏輯,並記錄下最後一次執行的相應的 target 元素以及依賴。
  • 後面每次執行的時候,都判斷目標元素或者依賴是否發生變化,發生變化,則執行對應的 effect 函數。並更新最後一次執行的依賴。
  • 組件卸載的時候,執行 unLoadRef.current?.() 函數,並重置 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 目標元素
      const targets = Array.isArray(target) ? target : [target];
      const els = targets.map((item) => getTargetElement(item));

      // init run
      // 首次初始化的時候執行
      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?.();
      // for react-refresh
      hasInitRef.current = false;
    });
  };

  return useEffectWithTarget;
};

思考與總結

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

本文已收錄到個人博客中,歡迎關註~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ORC文件是以二進位的方式存儲的,不可以直接讀取,但由於ORC的自描述特性,其讀寫不依賴於 Hive Metastore 或任何其他外部元數據。本身存儲了文件數據、數據類型及編碼信息。因為文件是自包含的,所以讀取ORC文件數據無需考慮用戶使用環境。 ...
  • ps:此隨筆基於mysql 5.7.*版本。 已知root賬戶密碼進行登錄 格式:mysql [-h地址] [-p埠] -u用戶名 -p密碼 省略不寫地址或埠則自動使用預設。(地址:localhost;埠:3306) 兩種方式進行登錄。方式1: 方式2: 忘記root賬戶密碼進行登錄(修改r ...
  • 騰訊雲資料庫一直致力於推動資料庫基礎研究創新、資料庫產學研合作生態建設,助力國產資料庫學術人才培養和技術創新生態建設發展。 為讓更多資料庫從業者瞭解資料庫領域的最新研究成果,熟悉更多行業前沿發展趨勢,更好地探索前沿技術創新,8月16日下午,騰訊雲資料庫邀請到華南師範大學二級教授 湯庸、長江學者 毛睿 ...
  • 在7月28日的袋鼠雲2022產品發佈會上,基於對現在與未來的暢想,袋鼠雲產研負責人思樞正式發佈了全新的四大產品體系。 其中的數棧DTinsight,相信大家都很熟悉了,不同於數駒這位新朋友,數棧作為袋鼠雲和大家經常見面的“老朋友”,在保持初心的同時,這次也有了一些不一樣的變化。 作為袋鼠雲打造的一站 ...
  • 本文介紹 Kubernetes 支持資料庫等有狀態應用的常見解決方案:StatefulSet。 在構建機器學習向量管理層時,我們面臨的一個重要問題:如何持久化數據以避免數據丟失? 在閱讀了許多資料庫企業發佈的博客後,我們認為 StatefulSet[1] 是實現這個目標的可行方法。 我們研究了不同的 ...
  • 1. 主從原理 1.1 主從介紹 所謂 mysql 主從就是建立兩個完全一樣的資料庫,其中一個為主要使用的資料庫,另一個為次要的資料庫,一般在企業中,存放比較重要的數據的資料庫伺服器需要配置主從,這樣可以防止因資料庫伺服器宕機導致數據丟失,還能保證業務量太多、數據太多和訪問人數太多時服務的質量(服務 ...
  • HMS Core Discovery第17期直播《音隨我動,秒變音色造型師》,已於8月25日圓滿結束,本期直播我們邀請了HMS Core音頻編輯服務的產品經理、技術專家以及創新娛樂類應用“唱鴨”的創始人做客直播間,分享影音娛樂行業發展的洞見及音頻技術新玩法。一起來回顧本期精彩內容吧! 【精彩回顧】 ...
  • 分析服務 ◆ 留存分析支持¬將流失用戶存為受眾,開發者通過對流失人群的分層以及多維分析,在制定相關用戶召回策略時將更有針對性; ◆ 原“受眾分析”更名為“人群洞察”,下設“用戶分群”和“用戶畫像”模塊。通過用戶分群可實現不同維度的受眾細分,通過用戶畫像則可以進一步分析對應細分群組的人群畫像與屬性等特 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...