当前位置: 首页 > news >正文

学习React-9-useSyncExternalStore

useSyncExternalStore

useSyncExternalStore 是 React 18 新增 的 Hook,专门用来把React 外部的可变数据源安全地同步到组件内部,并且天然支持并发渲染(Concurrent Features)。一句话:只要状态不在 React 内部管理(localStorage、全局变量、Redux store、WebSocket 等),就用它来做订阅-渲染桥梁。

签名三要素
const subscribe = (callback: () => void) => {// 订阅callback() return () => { // 取消订阅}
}
// 返回当前快照
const getSnapshot = () => {return data
}const snapshot = useSyncExternalStore(subscribe,          // (onStoreChange) => () => void   订阅+退订getSnapshot,        // () => Snapshot                  读取当前快照getServerSnapshot?  // () => Snapshot                  SSR 时的初始值
);
  • subscribe:组件挂载时 React 会调用它注册监听器;返回的函数会在卸载时执行做清理
  • getSnapshot:每次渲染前 React 会调用它拿到最新状态,并用 Object.is 与上一次快照比较,决定要不要重新渲染
  • getServerSnapshot(可选):服务端渲染时给 React 一个不会报错的初始值,避免hydration不匹配
对比
场景useState/useEffect 写法useSyncExternalStore 优势
localStorage 主题切换需判断 typeof window,监听 storage 事件,同页修改无事件触发,SSR 报错天生 SSR 安全,同页更新无压力
Redux/Zustand手写 useEffect 订阅、比较引用,并发模式下易出现 tearing(同一时刻不同组件读到不同值)官方保证 tear-free,并发安全
全局变量/WebSocket手动 setState 触发更新,易重复渲染或漏更新精准订阅,按需渲染
案例

1. 订阅浏览器Api 实现自定义hook(useStorage)

 需求:实现一个useStorage Hook,用于订阅 localStorage 数据。作用:确保组件在 localStorage 数据发生变化时,自动更新同步。实现思路:创建一个 useStorage Hook,能够存储数据到 localStorage,并在不同浏览器标签页之间同步这些状态。此 Hook 接收一个键值参数用于存储数据的键名,还可以接收一个默认值用于在无数据时的初始化。

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

import { useSyncExternalStore } from "react"export default function useStorage(key: string, initialValue: any) {// 订阅者const subscribe = (callback: () => void) => {// 订阅浏览器Apiwindow.addEventListener('storage', callback)return () => {// 取消订阅window.removeEventListener('storage', callback)}}// 获取当前数据源的快照const getSnapshot = () => {const item = localStorage.getItem(key)return item ? JSON.parse(item) : initialValue}// useSyncExternalStore(订阅者, 当前数据源的快照)const res = useSyncExternalStore(subscribe, getSnapshot)// 更新缓存const updateStorage = (value: any) => {localStorage.setItem(key, JSON.stringify(value))// 手动触发storage事件window.dispatchEvent(new StorageEvent('storage'))}return [res, updateStorage]
}

在App.tsx中使用:

import useStorage from "./hooks/useStorage"
function App() {const [count, setCount] = useStorage('count', 0)return (<button onClick={() => setCount(count + 1)}> + </button>Index: {count}<button onClick={() => setCount(count - 1)}> - </button></>)
}
export default App

效果展示:请添加图片描述
2. 订阅history实现路由跳转

需求:实现一个简易的useHistory Hook,获取浏览器url信息 + 参数

useHistory.tsx

import { useSyncExternalStore } from "react"export const useHistory = () => {const subscribe = (callback: () => void) => { // 订阅浏览器Api// vue 中路由三种模式, ssr使用、 两种web: history, hash // history 监听url变化用popstate// hash 监听url变化用hashchangewindow.addEventListener('popstate', callback)window.addEventListener('hashchange', callback)return () => {// 取消订阅window.removeEventListener('popstate', callback)window.removeEventListener('hashchange', callback)}// postate 只能监听浏览器前进后退,无法监听pushState和replaceState. 需要手动触发}// 获取快照const getSnapshot = () => {return window.location.href}const url = useSyncExternalStore(subscribe, getSnapshot)const push = (url: string) => {window.history.pushState({}, '', url)window.dispatchEvent(new PopStateEvent('popstate'))}const replace = (url: string) => {window.history.replaceState({}, '', url)window.dispatchEvent(new PopStateEvent('popstate'))}return [url, push, replace] as const // 定义成元组类型  // 注意: [1, '3', false] => 这的值会被推断成联合类型 number | string | boolean. 元组值是元组类型,则不会被推断成联合类型
} 

在App.tsx使用:

import { useHistory } from "./hooks/useHistory"
function App() {const [count, setCount] = useStorage('count', 0)return (<h1>url: {url}</h1><button onClick={() => push('/A')}>push</button><button onClick={() => replace('/B')}>push</button></>)
}
export default App

实现效果:
请添加图片描述

总结
  • React 之外的状态 → useSyncExternalStore
  • React 之内的状态 → useState / useReducer / useContext
http://www.dtcms.com/a/365808.html

相关文章:

  • Ubuntu下把 SD 卡格式化为 FAT32
  • 【工具变量】“国家级大数据综合试验区”试点城市DID(2000-2024年)
  • ArkTS状态管理V1
  • Llama v3 中的低秩自适应 (LoRA)
  • 头歌实训作业答案C++ 01
  • Proteus8 + STM32CubeMX 实现 STM32F103R6 串口通信教程
  • JMeter下载安装及使用入门
  • 常用符号 Emoji 对照表——Unicode UTF-8
  • SQLSERVER临时表
  • 关于专业化与多元化该怎么选?
  • 解决MQ访问不了或者登录不成功问题
  • 卷积神经网络CNN-part2-简单的CNN
  • TypeScript与JavaScript:从动态少年到稳重青年的成长之路
  • RabbitMQ相关知识
  • HTML第七课:发展史
  • Unity:XML笔记(二)——Xml序列化、反序列化、IXmlSerializable接口
  • 裸机程序(1)
  • 【ARM嵌入式汇编基础】-数据处理指令(三)
  • 低成本低功耗认证芯片推荐——LCS4110R
  • 【Luogu】P2398 GCD SUM (容斥原理求gcd为k的数对个数)
  • 鸿蒙NEXT开发实战:图片显示、几何图形与自定义绘制详解
  • GPT4o 提示词 结合 NanoBanbana 会摩擦出什么火花呢?
  • FPGA笔试面试常考问题及答案汇总
  • 入行FPGA选择国企、私企还是外企?
  • 案例演示 切片器悬浮永驻 Power BI VS QuickBI ,不得不说,两个极端了
  • 华勤内推码
  • 智慧交通管理信号灯通信4G工业路由器应用
  • 【机器学习深度学习】LLM:在检索与重排序中的适用场景
  • PS更改图像尺寸
  • 心路历程-初识Linux用户