【React】Hooks 解鎖外部狀態安全訂閱 useSyncExternalStore 應用與最佳實踐

一、背景

  1. useSyncExternalStore 是 React 18 引入的一個 Hook;
  2. 用于從外部存儲(例如狀態管理庫、瀏覽器 API 等)獲取狀態并在組件中同步顯示。這對于需要跟蹤外部狀態的應用非常有用。

二、場景

  1. 訂閱外部 store 例如(redux,mobx,Zustand,jotai) vue的 vuex pinia
  2. 訂閱瀏覽器API 例如(online,storage,location, history hash)等
  3. 抽離邏輯,編寫自定義hooks
  4. 服務端渲染支持

三、用法

const state = useSyncExternalStore(subscribe: (onStoreChangeCallback: () => void) => () => void,getSnapshot: () => Snapshot,getServerSnapshot?: () => Snapshot
);
  • subscribe:訂閱函數,接收一個回調函數 onStoreChange,當外部狀態變化時調用該回調。需返回一個清理函數,用于取消訂閱。
  • getSnapshot:獲取當前數據源的快照(當前狀態)。
  • getServerSnapshot(可選):服務端渲染時使用的快照函數,確保客戶端與服務端狀態一致。

返回值:該 res 的當前快照,可以在你的渲染邏輯中使用

const subscribe = (callback: () => void) => {// 訂閱callback() return () => { // 取消訂閱}
}const getSnapshot = () => {return data
}const res = useSyncExternalStore(subscribe, getSnapshot)

四、訂閱瀏覽器 Api 實現自定義hook(useStorage)

  1. 我們實現一個useStorage Hook,用于訂閱 localStorage 數據。這樣做的好處是,我們可以確保組件在 localStorage 數據發生變化時,自動更新同步。
  2. 我們將創建一個 useStorage Hook,能夠存儲數據到 localStorage,并在不同瀏覽器標簽頁之間同步這些狀態
  3. 此 Hook 接收一個鍵值參數用于存儲數據的鍵名,還可以接收一個默認值用于在無數據時的初始化。

在 hooks/useStorage.ts 中定義 useStorage Hook:

import { useSyncExternalStore } from "react";/*** 自定義 Hook,用于在 React 組件中同步 localStorage 狀態* @param key - localStorage 存儲的鍵名* @param initValue - 初始值,當 localStorage 中不存在對應鍵值時使用* @returns [存儲的值, 更新函數] - 返回一個數組,包含當前值和更新函數*/
export const useStroage = (key: string, initValue: any) => {/*** 訂閱者: 訂閱 storage 變化* @param callback - storage 變化時的回調函數* @returns 取消訂閱的函數*/const subscribe = (callback: () => void) => {// 訂閱瀏覽器的 storage 事件window.addEventListener('storage', callback);return () => {// 取消訂閱window.removeEventListener('storage', callback);};};/*** 獲取當前 localStorage 中存儲的值* @returns 當前存儲的值或初始值*/const getSnapshot = () => {const storedValue = localStorage.getItem(key);return storedValue ? JSON.parse(storedValue) : initValue;};// 使用 React 的 useSyncExternalStore 同步外部狀態const value = useSyncExternalStore(subscribe, getSnapshot);/*** 更新 localStorage 中的值* @param value - 要存儲的新值*/const updateStorage = (value: any) => {// 將值存儲到 localStoragelocalStorage.setItem(key, JSON.stringify(value));// 觸發 storage 事件,通知其他訂閱者window.dispatchEvent(new StorageEvent('storage', { key }));};return [value, updateStorage];
};

測試使用 自定義 hooks

import { useStroage } from './hooks/useStrage'function App() {// 使用 useStroage hook 管理計數狀態,初始值為1// count: 當前計數值// setCount: 更新計數的函數const [count, setCount] = useStroage('count', 1);return (<>{/* 顯示當前計數值 */}<div>{count}</div>{/* 增加按鈕 - 點擊時計數值加1 */}<button onClick={() => setCount(count + 1)}>Add</button>{/* 減少按鈕 - 點擊時計數值減1 */}<button onClick={() => setCount(count - 1)}>Sub</button></>);
};export default App;

五、獲取瀏覽器url信息 + 參數

實現一個簡易的useHistory Hook,獲取瀏覽器url信息 + 參數

讓我們在組件中使用這個 useHistory Hook,實現基本的前進、后退操作以及程序化導航。

效果演示

  1. history:這是 useHistory 返回的當前路徑值。每次 URL 變化時,useSyncExternalStore 會自動觸發更新,使 history 始終保持最新路徑。
  2. push 和 replace:
    1. 點擊“跳轉”按鈕調用 push(“/push”),會將 /push推入歷史記錄;
    2. 點擊“替換”按鈕調用 replace(“/replace”),則會將當前路徑替換為 /replace。
import { useSyncExternalStore } from "react";/*** 自定義 Hook,用于在 React 組件中同步和管理瀏覽器歷史記錄狀態* @returns [當前URL, push方法, replace方法] - 返回一個元組,包含當前URL和兩個導航方法*/
export const useHistory = () => {/*** 訂閱瀏覽器歷史記錄變化* @param callback - 歷史記錄變化時的回調函數* @returns 取消訂閱的函數*/const subscribe = (callback: () => void) => {// 監聽 popstate 事件 - 用于捕獲瀏覽器前進/后退按鈕的操作, // history 底層監聽的是 popstate 事件window.addEventListener('popstate', callback);// 監聽 hashchange 事件 - 用于捕獲 URL hash 部分的變化 // hash 底層監聽的是 hashchange 事件  window.addEventListener('hashchange', callback);// 返回清理函數return () => {window.removeEventListener('popstate', callback);window.removeEventListener('hashchange', callback);};};/*** 獲取當前瀏覽器 URL* @returns 當前完整的 URL 字符串* 如果 getSnapshot 返回值和上一次不同時,React 會重新渲染組件。* 如果總是返回一個不同的值,會進入到一個無限循環,并產生這個報錯。* - 比如數組對象這中引用類型。getSnapshot 返回值和上一次不同時,React 會重新渲染組件。* - 解決方式需要我們手動去比對更新。*/const getSnapshot = () => {return window.location.href;};// 使用 React 的 useSyncExternalStore 同步 URL 狀態const url = useSyncExternalStore(subscribe, getSnapshot);/*** 向歷史記錄棧中推入新的記錄* @param url - 目標 URL*/const push = (url: string) => {window.history.pushState(null, '', url);// 手動觸發 popstate 事件,因為 pushState 不會自動觸發window.dispatchEvent(new PopStateEvent('popstate'));};/*** 替換當前的歷史記錄* @param url - 目標 URL*/const replace = (url: string) => {window.history.replaceState(null, '', url);// 手動觸發 popstate 事件,因為 replaceState 不會自動觸發window.dispatchEvent(new PopStateEvent('popstate'));};return [url, push, replace] as const;
};

使用

import { useHistory } from './hooks/useHistory'/*** App 組件 - 演示 useHistory 自定義 Hook 的使用* @returns React 組件*/
function App() {// 使用 useHistory hook 獲取當前 URL 和導航方法const [url, push, replace] = useHistory();return (<>{/* 顯示當前 URL */}<div>{url}</div>{/* 使用 push 方法導航到 /push 路徑 */}<button onClick={() => push('/push')}>/push</button>{/* 使用 replace 方法替換當前路徑為 /replace */}  <button onClick={() => replace('/replace')}>/replace</button></>);
};export default App;

六、注意事項

  1. 避免條件渲染:不應基于 useSyncExternalStore 返回的狀態值進行條件渲染(如動態加載懶加載組件),因為外部狀態變化無法被標記為非阻塞更新,可能觸發 Suspense 后備方案,導致用戶體驗不佳。

  2. 不可變快照getSnapshot 返回的快照必須是不可變的。若底層狀態變化,需返回新的不可變快照。

  3. 清理訂閱subscribe 函數需返回清理函數,確保組件卸載時取消訂閱,防止內存泄漏。

  4. 如果 getSnapshot 返回值和上一次不同時,React 會重新渲染組件。如果總是返回一個不同的值,會進入到一個無限循環,并產生這個報錯。

    Uncaught (in promise) Error: Maximum update depth exceeded. 
    This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
    React limits the number of nested updates to prevent infinite loops.
    

七、對比 useEffect + useState

  1. 并發渲染安全性useEffect + useState 在并發模式下可能導致舊值問題,而 useSyncExternalStore 通過同步讀取快照確保狀態一致性。
  2. 適用場景useSyncExternalStore 更適合需要安全訂閱外部狀態源的場景,而 useEffect + useState 適用于簡單的狀態管理。

八、總結

  • useSyncExternalStore 是 React 18 為并發渲染設計的核心 Hook,通過安全訂閱外部狀態源,解決了狀態與 UI 不一致的問題。
  • 它適用于需要與第三方狀態管理庫或瀏覽器 API 集成的場景,確保組件在并發渲染模式下仍能正確響應狀態變化。并在不同瀏覽器標簽頁之間同步這些狀態

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/79496.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/79496.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/79496.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Dify框架面試內容整理-如何評估基于Dify開發的AI應用的效果?

評估基于 Dify 開發的 AI 應用效果,需要從 用戶體驗、技術性能 與 業務價值 三個層面綜合衡量。以下是詳細的評估框架,涵蓋三個關鍵點: 用戶反饋與滿意度

Linux 系統下VS Code python環境配置!

Anaconda安裝&#xff1a; 在 Linux 系統中安裝下載好的 Anaconda3-2024.10-1-Linux-x86_64.sh&#xff0c;可按以下步驟操作&#xff1a; 1. 賦予安裝腳本執行權限 打開終端&#xff0c;切換到安裝包所在目錄&#xff08;假設在 software 文件夾中&#xff09;&#xff0c;…

項目實戰-基于信號處理與SVM機器學習的聲音情感識別系統

目錄 一.背景描述 二.理論部分 三.程序設計 編程思路 流程圖 1.信號部分 創建數據 generate_samples.py 頭文件 生成函數 generate_emotion_sample 傳入參數 存儲路徑 生成參數 創建基礎正弦波信號 調制基礎正弦波 對于憤怒可以增加噪聲 歸一化信號 存儲 主函…

虛幻引擎作者采訪

1萬小時編程_嗶哩嗶哩_bilibili https://www.youtube.com/watch?v477qF6QNSvc 提姆斯溫尼是一位傳奇性的視頻游戲程序員&#xff0c;Epic Games 的創始人兼首席執行官。 該公司開發了虛幻引擎、堡壘之夜、戰爭機器、虛幻競技場等許多開創性和有影響力的視頻游戲。 他哥哥…

如何限制pod 進程/線程數量?

在 Kubernetes 中限制 Pod 的 進程數&#xff08;PID 數量&#xff09; 和 線程數&#xff0c;需要結合 Linux cgroup 控制 和 容器運行時配置。以下是具體方法和示例&#xff1a; 一、限制進程數&#xff08;PID 數量&#xff09; 1. 通過 pids cgroup 控制器限制 原理&…

使用 Hugging Face 鏡像站快速下載大模型

在國內使用 Hugging Face 下載模型時&#xff0c;經常遇到連接慢、斷點續傳失敗等問題。本文記錄一個穩定、快速下載模型的命令行腳本&#xff0c;并支持設置模型緩存路徑和目標目錄&#xff0c;方便后續統一管理。 1. 設置 Hugging Face 鏡像站 為了提升國內訪問速度&#xf…

原語的使用

1、什么是原語&#xff1f;&#xff1f; 原語&#xff08; primitive &#xff09;&#xff0c;是FPGA開發環境所提供的一系列邏輯功能單元。往往與FPGA芯片的廠家精密相連&#xff0c;不同廠家的原語往往不能通用。 2、需要使用原語的情況 一般來說&#xff0c;在進行HDL cod…

大模型核心技術及架構解析

大模型核心技術及架構解析 大語言模型(Large Language Models, LLMs)已成為當前AI領域最重要的技術突破之一。以下是其核心技術和架構的全面分析&#xff1a; 一、核心技術組成 1. 基礎架構技術 技術說明代表應用Transformer自注意力機制基礎架構GPT, BERTMoE架構混合專家模…

ES6/ES11知識點 續三

rest參數 Rest 參數&#xff08;Rest Parameters&#xff09;是 ES6 引入的一個非常實用的特性。它允許函數接受不定數量的參數&#xff0c;并將這些參數作為一個數組存儲&#xff0c;從而簡化了處理可變參數的代碼。 Rest 參數語法 Rest 參數使用 … 語法&#xff0c;緊跟著…

記憶翻牌游戲:認知科學與狀態機的交響曲

目錄 記憶翻牌游戲:認知科學與狀態機的交響曲引言第一章 網格空間拓撲學1.1 自適應網格算法1.2 卡片排布原理第二章 狀態機設計2.1 狀態躍遷矩陣2.2 時空關聯模型第三章 記憶強化機制3.1 認知衰減曲線3.2 注意力熱力圖第四章 動畫引擎設計4.1 翻牌運動方程4.2 粒子反饋系統第五…

STM32外設-GPIO輸出(不含復用)

STM32外設-GPIO輸出&#xff08;不含復用&#xff09; 一&#xff0c;GPIO模式簡介1&#xff0c;輸入模式2&#xff0c;輸出模式3&#xff0c;模擬模式4&#xff0c;復用模式 二&#xff0c;輸出模式詳解1&#xff0c; 輸出類型1&#xff0c;推挽輸出&#xff1a;2&#xff0c;…

58認知干貨:創業經驗分享及企業形式的匯總

機會永遠都是留給有眼光、能發現機會的人,而不是留給有準備的人!往往機會就在身邊,普羅大眾卻無法發現,而真正適合創業的人,天然具備這方面的能力。 當然后天的補足也未嘗不可:“故常有欲以觀其微,常無欲以觀其妙。””引用《道德經》 讀懂這句話自然便會擁有對商業和…

修復筆記:獲取 torch._dynamo 的詳細日志信息

一、問題描述 在運行項目時&#xff0c;遇到與 torch._dynamo 相關的報錯&#xff0c;并且希望獲取更詳細的日志信息以便于進一步診斷問題。 二、相關環境變量設置 通過設置環境變量&#xff0c;可以獲得更詳細的日志信息&#xff1a; set TORCH_LOGSdynamo set TORCHDYNAM…

Spark,Idea中編寫Spark程序 2

Idea中編寫Spark程序 一、修改pom.xml文件 <build><sourceDirectory>src/main/scala</sourceDirectory><testSourceDirectory>src/test/scala</testSourceDirectory> <!-- 添加必要的插件以打包scala程序--><plugins><plu…

【AI提示詞】黑天鵝模型專家

提示說明 詳細解釋黑天鵝模型的理論背景、定義、分類及其在不同領域的應用。 提示詞 # Role: 黑天鵝模型專家## Profile - language: 中文 - description: 詳細解釋黑天鵝模型的理論背景、定義、分類及其在不同領域的應用 - background: 黑天鵝模型是尼爾斯莫爾提出的理論&a…

ARM Linux 設備樹

Linux 設備驅動開發詳解&#xff1a;基于最新的Linux 4.0內核, 機械工業出版社, 宋寶華, 2015 1. 設備樹的起源 ? 背景: ARM架構中大量板級代碼冗余&#xff0c;硬編碼在mach-xxx目錄&#xff0c;設備樹&#xff08;Device Tree&#xff09;引入結構化描述硬件。 ? 目的: 減…

每日c/c++題 備戰藍橋杯(洛谷P1015 [NOIP 1999 普及組] 回文數)

洛谷P1015 [NOIP 1999 普及組] 回文數 題解 題目描述 P1015 回文數 是NOIP 1999普及組的經典模擬題。題目要求如下&#xff1a; 給定一個數N&#xff08;十進制&#xff09;和進制K&#xff08;2≤K≤16&#xff09;&#xff0c;將N轉換為K進制表示后&#xff0c;通過以下操…

Linux線程深度解析:從基礎到實踐

Linux線程深度解析&#xff1a;從基礎到實踐 一、線程基礎概念 1. 進程與線程定義 進程&#xff1a;一個正在運行的程序&#xff0c;是操作系統資源分配的最小單位&#xff08;擁有獨立的地址空間、文件描述符等資源&#xff09;&#xff0c;狀態包括就緒、運行、阻塞。線程…

php學習筆記(全面且適合新手)

以下是專為 PHP 7.4 初學者設計的全面學習文檔&#xff0c;涵蓋基礎語法、細節語法和進階語法&#xff0c;結合 PHP 7.4 新特性與實戰案例&#xff0c;幫助系統掌握 PHP 開發&#xff1a; 為什么特地做7.4的筆記而不做8的&#xff1f;因為公司用的7.4&#xff0c;哈哈 一、基…

開源分布式數據庫(TiDB)

TiDB是由PingCAP 開發的開源分布式數據庫&#xff0c;兼容 MySQL 協議&#xff0c;集成了 HTAP&#xff08;混合事務和分析處理&#xff09;的能力&#xff0c;能夠同時處理在線事務和實時分析任務。 2015 年&#xff0c;TiDB 在 GitHub 創建&#xff0c;2025 年&#xff0c;Ti…