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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…