React 中hooks之useSyncExternalStore使用總結

1. 基本概念

useSyncExternalStore 是 React 18 引入的一個 Hook,用于訂閱外部數據源,確保在并發渲染下數據的一致性。它主要用于:

  • 訂閱瀏覽器 API(如 window.width)
  • 訂閱第三方狀態管理庫
  • 訂閱任何外部數據源

1.1 基本語法

const state = useSyncExternalStore(subscribe,  // 訂閱函數getSnapshot, // 獲取當前狀態的函數getServerSnapshot // 可選:服務端渲染時獲取狀態的函數
);

2. 基礎示例

2.1 訂閱窗口大小變化

getSnapshot 是一個函數,用于返回當前瀏覽器窗口的寬度和高度。window.innerWidth 和 window.innerHeight 分別獲取瀏覽器窗口的寬度和高度。
該函數返回一個對象,包含 width 和 height 兩個屬性。
subscribe 函數接受一個回調函數 callback,并將其作為事件監聽器綁定到 resize 事件上。
每當瀏覽器窗口的尺寸發生變化時,resize 事件會觸發,進而調用 callback。
subscribe 函數還返回一個清理函數(return () => window.removeEventListener(‘resize’, callback)),用于在組件卸載時移除事件監聽器,防止內存泄漏。
當callback回調觸發的時候就會觸發組件更新

function useWindowSize() {const getSnapshot = () => ({width: window.innerWidth,height: window.innerHeight});const subscribe = (callback) => {window.addEventListener('resize', callback);return () => window.removeEventListener('resize', callback);};return useSyncExternalStore(subscribe, getSnapshot);
}function WindowSizeComponent() {const { width, height } = useWindowSize();return (<div>Window size: {width} x {height}</div>);
}

2.2 訂閱瀏覽器在線狀態

function useOnlineStatus() {const getSnapshot = () => navigator.onLine;const subscribe = (callback) => {window.addEventListener('online', callback);window.addEventListener('offline', callback);return () => {window.removeEventListener('online', callback);window.removeEventListener('offline', callback);};};return useSyncExternalStore(subscribe, getSnapshot);
}function OnlineStatusComponent() {const isOnline = useOnlineStatus();return (<div>Status: {isOnline ? '在線' : '離線'}</div>);
}

3. 進階用法

3.1 創建自定義存儲

useTodoStore 是一個自定義 Hook,它使用了 useSyncExternalStore 來同步外部存儲(即 todoStore)的狀態。
todoStore.subscribe:訂閱狀態更新。每當 todoStore 中的狀態變化時,useSyncExternalStore 會觸發重新渲染。
todoStore.getSnapshot:獲取當前的狀態快照,在此返回的對象包含 todos 和 filter。

function createStore(initialState) {let state = initialState;const listeners = new Set();return {subscribe(listener) {listeners.add(listener);return () => listeners.delete(listener);},getSnapshot() {return state;},setState(newState) {state = newState;listeners.forEach(listener => listener());}};
}const todoStore = createStore({todos: [],filter: 'all'
});function useTodoStore() {return useSyncExternalStore(todoStore.subscribe,todoStore.getSnapshot);
}function TodoList() {const { todos, filter } = useTodoStore();return (<ul>{todos.filter(todo => filter === 'all' || todo.completed === (filter === 'completed')).map(todo => (<li key={todo.id}>{todo.text}</li>))}</ul>);
}

3.2 與服務端渲染集成

function useSharedState(initialState) {const store = useMemo(() => createStore(initialState), [initialState]);// 提供服務端快照const getServerSnapshot = () => initialState;return useSyncExternalStore(store.subscribe,store.getSnapshot,getServerSnapshot);
}

3.3 訂閱 WebSocket 數據

function useWebSocketData(url) {const [store] = useState(() => {let data = null;const listeners = new Set();const ws = new WebSocket(url);ws.onmessage = (event) => {data = JSON.parse(event.data);listeners.forEach(listener => listener());};return {subscribe(listener) {listeners.add(listener);return () => {listeners.delete(listener);if (listeners.size === 0) {ws.close();}};},getSnapshot() {return data;}};});return useSyncExternalStore(store.subscribe, store.getSnapshot);
}function LiveDataComponent() {const data = useWebSocketData('wss://api.example.com/live');if (!data) return <div>Loading...</div>;return (<div><h2>實時數據</h2><pre>{JSON.stringify(data, null, 2)}</pre></div>);
}

4. 性能優化

4.1 選擇性訂閱

function useStoreSelector(selector) {const store = useContext(StoreContext);const getSnapshot = useCallback(() => {return selector(store.getSnapshot());}, [store, selector]);return useSyncExternalStore(store.subscribe,getSnapshot);
}// 使用示例
function TodoCounter() {const count = useStoreSelector(state => state.todos.length);return <div>Total todos: {count}</div>;
}

4.2 避免不必要的更新

function createStoreWithSelector(initialState) {let state = initialState;const listeners = new Map();return {subscribe(listener, selector) {const wrappedListener = () => {const newSelectedValue = selector(state);if (newSelectedValue !== selector(previousState)) {listener();}};listeners.set(listener, wrappedListener);return () => listeners.delete(listener);},getSnapshot() {return state;},setState(newState) {const previousState = state;state = newState;listeners.forEach(listener => listener());}};
}

5. 實際應用場景

5.1 主題切換系統

function createThemeStore() {let theme = 'light';const listeners = new Set();return {subscribe(listener) {listeners.add(listener);return () => listeners.delete(listener);},getSnapshot() {return theme;},toggleTheme() {theme = theme === 'light' ? 'dark' : 'light';listeners.forEach(listener => listener());}};
}const themeStore = createThemeStore();function useTheme() {return useSyncExternalStore(themeStore.subscribe,themeStore.getSnapshot);
}function ThemeToggle() {const theme = useTheme();return (<button onClick={() => themeStore.toggleTheme()}>Current theme: {theme}</button>);
}

5.2 表單狀態管理

function createFormStore(initialValues) {let values = initialValues;const listeners = new Set();return {subscribe(listener) {listeners.add(listener);return () => listeners.delete(listener);},getSnapshot() {return values;},updateField(field, value) {values = { ...values, [field]: value };listeners.forEach(listener => listener());},reset() {values = initialValues;listeners.forEach(listener => listener());}};
}function useForm(initialValues) {const [store] = useState(() => createFormStore(initialValues));return useSyncExternalStore(store.subscribe,store.getSnapshot);
}function Form() {const formData = useForm({ name: '', email: '' });return (<form><inputvalue={formData.name}onChange={e => formStore.updateField('name', e.target.value)}/><inputvalue={formData.email}onChange={e => formStore.updateField('email', e.target.value)}/></form>);
}

6. 注意事項

  1. 保持一致性

    • subscribe 函數應該返回清理函數
    • getSnapshot 應該返回不可變的數據
  2. 避免頻繁更新

    • 考慮使用節流或防抖
    • 實現選擇性訂閱機制
  3. 服務端渲染

    • 提供 getServerSnapshot
    • 確保服務端和客戶端狀態同步
  4. 內存管理

    • 及時清理訂閱
    • 避免內存泄漏

通過合理使用 useSyncExternalStore,我們可以安全地訂閱外部數據源,并確保在 React 并發渲染下的數據一致性。這個 Hook 特別適合需要與外部系統集成的場景。 還可以用來實現瀏覽器localStrorage的持久化存儲

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

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

相關文章

激光雷達和相機早期融合

通過外參和內參的標定將激光雷達的點云投影到圖像上。 ? 傳感器標定 首先需要對激光雷達和相機&#xff08;用于獲取 2D 圖像&#xff09;進行外參和內參標定。這是為了確定激光雷達坐標系和相機坐標系之間的轉換關系&#xff0c;包括旋轉和平移。通常采用棋盤格等標定工具&…

Linux--權限

Linux系統的權限管理是保障系統安全的重要機制&#xff0c;以下詳細講解權限相關概念及操作指令&#xff1a; 一、基礎權限機制 1. 權限的三元組&#xff0c;讀&#xff08;r&#xff09;、寫&#xff08;w&#xff09;、執行&#xff08;x&#xff09; 每個文件或目錄有三組…

iic、spi以及uart

何為總線&#xff1f; 連接多個部件的信息傳輸線&#xff0c;是部件共享的傳輸介質 總線的作用&#xff1f; 實現數據傳輸&#xff0c;即模塊之間的通信 總線如何分類&#xff1f; 根據總線連接的外設屬于內部外設還是外部外設將總線可以分為片內總線和片外總線 可分為數…

“破冰”探索兩周年,AI和媒體碰撞出了什么火花?

2022年末&#xff0c;大模型浪潮席卷而來。在“所有行業都值得用AI重塑”的氛圍下&#xff0c;各個行業都受到了影響和沖擊。 其中新聞媒體可以說是受影響最為劇烈的行業。 因為內容的生產方式被重新定義&#xff0c;媒體從業者普遍存在焦慮情緒&#xff1a;擔心錯過新一輪的…

DeepSeek明確學術研究方向效果如何?

明確學術研究方向 在學術寫作中&#xff0c;選擇一個出色的研究主題至關重要&#xff0c;因為它直接關系到論文是否能登上高級別的學術期刊。不少學者在這個過程中走入了誤區&#xff0c;他們往往將大把的時間花在寫作本身&#xff0c;而忽略了對選題的深入思考&#xff0c;這…

WPF實戰案例 | C# WPF實現大學選課系統

WPF實戰案例 | C# WPF實現大學選課系統 一、設計來源1.1 主界面1.2 登錄界面1.3 新增課程界面1.4 修改密碼界面 二、效果和源碼2.1 界面設計&#xff08;XAML&#xff09;2.2 代碼邏輯&#xff08;C#&#xff09; 源碼下載更多優質源碼分享 作者&#xff1a;xcLeigh 文章地址&a…

《 C++ 點滴漫談: 二十四 》深入 C++ 變量與類型的世界:高性能編程的根基

摘要 本文深入探討了 C 中變量與類型的方方面面&#xff0c;包括變量的基本概念、基本與復合數據類型、動態類型與內存管理、類型推導與模板支持&#xff0c;以及類型系統的高級特性。通過全面的理論講解與實際案例分析&#xff0c;展示了 C 類型系統的強大靈活性與實踐價值。…

STM32 GPIO配置 點亮LED燈

本次是基于STM32F407ZET6做一個GPIO配置&#xff0c;實現點燈實驗。 新建文件 LED.c、LED.h文件&#xff0c;將其封裝到Driver文件中。 雙擊Driver文件將LED.c添加進來 編寫頭文件&#xff0c;這里注意需要將Driver頭文件聲明一下。 在LED.c、main.c里面引入頭文件LED.h LED初…

window保存好看的桌面壁紙

1、按下【WINR】快捷鍵調出“運行”窗口&#xff0c;輸入以下命令后回車。 %localappdata%\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets 2、依次點擊【查看】【顯示】&#xff0c;勾選【隱藏的項目】&#xff0c;然后按【CtrlA】全部…

TCP 三次握手四次揮手

目錄 TCP 三次握手 1. SYN (Synchronize&#xff1a;同步) 2. SYN-ACK (Synchronize Acknowledge&#xff1a;同步確認) 3. ACK (Acknowledge&#xff1a;確認) 為什么是三次而不是兩次或四次&#xff1f; 三次握手的作用 TCP 四次揮手 第一次揮手&#xff1a;客戶端發送 FIN …

C語言初階牛客網刷題—— HJ34 圖片整理【難度:中等】

1. 題目描述 牛客網在線OJ鏈接 Lily上課時使用字母數字圖片教小朋友們學習英語單詞&#xff0c;每次都需要把這些圖片按照大小&#xff08;ASCII碼值從小到大&#xff09;排列收好。請大家給Lily幫忙&#xff0c;通過C語言解決。 輸入描述&#xff1a;Lily使用的圖片包括 “A…

MVCC底層原理實現

MVCC的實現原理 了解實現原理之前&#xff0c;先理解下面幾個組件的內容 1、 當前讀和快照讀 先普及一下什么是當前讀和快照讀。 當前讀&#xff1a;讀取數據的最新版本&#xff0c;并對數據進行加鎖。 例如&#xff1a;insert、update、delete、select for update、 sele…

python實現http文件服務器訪問下載

//1.py import http.server import socketserver import os import threading import sys# 獲取當前腳本所在的目錄 DIRECTORY os.path.dirname(os.path.abspath(__file__))# 設置服務器的端口 PORT 8000# 自定義Handler&#xff0c;將根目錄設置為腳本所在目錄 class MyHTT…

Cpp::靜態 動態的類型轉換全解析(36)

文章目錄 前言一、C語言中的類型轉換二、為什么C會有四種類型轉換&#xff1f;內置類型 -> 自定義類型自定義類型 -> 內置類型自定義類型 -> 自定義類型隱式類型轉換的坑 三、C強制類型轉換static_castreinterpret_castconst_castdynamic_cast 四、RTTI總結 前言 Hell…

2024年終總結:技術成長與突破之路

文章目錄 前言一、技術成長&#xff1a;菜鳥成長之路1. 學習與實踐的結合2. 技術分享與社區交流 二、生活與事業的平衡&#xff1a;技術之外的思考1. 時間管理與效率提升2. 技術對生活的積極影響 三、突破與展望&#xff1a;未來之路1. 技術領域的突破2. 未來規劃與目標 四、結…

ComfyUI實現老照片修復——AI修復老照片(ComfyUI-ReActor / ReSwapper)解決天坑問題及加速pip下載

AI修復老照片&#xff0c;試試吧&#xff0c;不一定好~~哈哈 2023年4月曾用過ComfyUI&#xff0c;當時就感慨這個工具和虛幻的藍圖很像&#xff0c;以后肯定是專業人玩的。 2024年我寫代碼去了&#xff0c;AI做圖沒太關注&#xff0c;沒想到&#xff0c;現在ComfyUI真的變成了工…

思科交換機telnet配置案例

目錄 1.telnet簡述2.網絡拓撲3.設備說明4.網絡配置4.1 電腦PC ip設置4.2 網絡交換機telnet配置 5.小結 1.telnet簡述 Telnet是遠程登錄服務的一個協議&#xff0c;該協議定義了遠程登錄用戶與服務器交互的方式。它允許用戶在一臺聯網的計算機上登錄到一個遠程分時系統中&#…

WPS數據分析000006

一、排序 開始→ 排序 同文件→選項→自定義序列→輸入序列 二、篩選 高級篩選 條件區域要與列表區域一樣。 三、條件格式

vofa++使用方法

控件區可以添加控件用來啊多樣顯示 點擊一個控件然后右鍵可以選擇要添加顯示的數據&#xff0c;點all表全部顯示&#xff0c; 點auto可以自動布局 要用控件需要選擇協議&#xff0c;不知道協議具體格式可以點擊問號看看&#xff0c;并且最好用printf重定義來實現 比如我要實現F…

達夢拷貝DM_HOME的復制安裝

近期一個項目需求&#xff0c;需要在沒有安裝包的情況下&#xff0c;將達夢數據庫安裝到虛機上&#xff08;生產機上安裝了達夢&#xff09;&#xff0c;故采用直接打包生產機DM_HOME的方式拷貝至虛機&#xff0c;再依次執行達夢的部分指令完成安裝。以下為驗證的步驟&#xff…