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

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的持久化存储

相关文章:

React 中hooks之useSyncExternalStore使用总结

1. 基本概念 useSyncExternalStore 是 React 18 引入的一个 Hook&#xff0c;用于订阅外部数据源&#xff0c;确保在并发渲染下数据的一致性。它主要用于&#xff1a; 订阅浏览器 API&#xff08;如 window.width&#xff09;订阅第三方状态管理库订阅任何外部数据源 1.1 基…...

C++11新特性之decltype

1.decltype的作用 decltype是C11新增的一个关键字&#xff0c;与auto的功能一样&#xff0c;都是在编译期间推导变量类型的。不了解auto的可以转到——C11新特性之auto。 为什么引入decltype&#xff1f;看过上边那篇博客的读者应该知道auto在有些场景中并不适用,所以引入declt…...

二叉树相关oj题 1. 检查两颗树是否相同。

二叉树相关oj题 检查两颗树是否相同。OJ链接 另一颗树的子树。OJ链接 if(rootnull)易漏掉 会导致空指针异常翻转二叉树。OJ链接...

element tbas增加下拉框

使用Tabs 标签页的label插槽&#xff0c;嵌入Dropdown 下拉菜单&#xff0c;实现Tabs 标签页增加下拉切换功能 Tabs 标签页 tab-click"事件"&#xff08;这个事件当中到拥有下拉框的tab里时&#xff0c;可以存一下Dropdown 第一个菜单的id&#xff0c;实现点击到拥有…...

新浪安卓(Android)开发面试题及参考答案(68道题,9道手撕题)

链表判环,找入口 思路: 判断是否有环:使用快慢指针,快指针每次走两步,慢指针每次走一步,如果它们相遇,说明有环。找出环入口:当判断出有环后,将慢指针重新指向头节点,然后快慢指针同时以相同速度移动,再次相遇的节点就是环的入口。以下是判断链表是否有环以及找出环…...

Zbrush导入笔刷

Zbrush笔刷目录: ...\Zbrush\ZStartup\BrushPresets...

实战演示:利用ChatGPT高效撰写论文

在当今学术界&#xff0c;撰写论文是一项必不可少的技能。然而&#xff0c;许多研究人员和学生在写作过程中常常感到困惑和压力。幸运的是&#xff0c;人工智能的快速发展为我们提供了新的工具&#xff0c;其中ChatGPT便是一个优秀的选择。本文将通过易创AI创作平台&#xff0c…...

大数据学习之SCALA分布式语言三

7.集合类 111.可变set一 112.可变set二 113.不可变MAP集合一 114.不可变MAP集合二 115.不可变MAP集合三 116.可变map一 package com . itbaizhan . chapter07 //TODO 2. 使用 mutable.Map 前导入如下包 import scala . collection . mutable // 可变 Map 集合 object Ma…...

k8s简介,k8s环境搭建

目录 K8s简介环境搭建和准备工作修改主机名&#xff08;所有节点&#xff09;配置静态IP&#xff08;所有节点&#xff09;关闭防火墙和seLinux&#xff0c;清除iptables规则&#xff08;所有节点&#xff09;关闭交换分区&#xff08;所有节点&#xff09;修改/etc/hosts文件&…...

深入理解MySQL事务(万字详)

文章目录 什么是事务为什么会出现事务事务的版本支持事务的提交方式事务常见操作方式正常演示 - 证明事务的开始与回滚非正常演示1 - 证明未commit&#xff0c;客户端崩溃&#xff0c;MySQL自动会回滚&#xff08;隔离级别设置为读未提交&#xff09;非正常演示2 - 证明commit了…...

微信小程序使用picker根据接口给的省市区的数据实现省市区三级联动或者省市区街道等多级联动

接口数据如上图 省市区多级联动&#xff0c;都是使用的一个接口通过传参父类的code。返回我们想要的数据 比如获取省就直接不要参数。市就把省得code传给接口&#xff0c;区就把市的code作为参数。 <picker mode"multiSelector" :range"mulSelect1" …...

Go Fx 框架使用指南:深入理解 Provide 和 Invoke 的区别

1. 什么是 Fx 框架&#xff1f; Fx 是一个基于 Go 语言的依赖注入框架&#xff0c;专注于简化应用程序的生命周期管理和依赖的构建。在复杂的应用程序中&#xff0c;Fx 通过模块化的设计方式将组件连接起来&#xff0c;使开发者能够更高效地管理依赖关系。 Fx 的核心理念是&a…...

VSCode+Continue实现AI辅助编程

Continue是一款功能强大的AI辅助编程插件&#xff0c;可连接多种大模型&#xff0c;支持代码设计优化、错误修正、自动补全、注释编写等功能&#xff0c;助力开发人员提高工作效率与代码质量。以下是其安装和使用方法&#xff1a; 一、安装VSCode 参见&#xff1a; vscode安…...

阿里云服务器在Ubuntu上安装redis并使用

1、redis安装 sudo apt install lsb-release curl gpgcurl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpgecho "deb [signed-by/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.…...

Blazor-Blazor呈现概念

静态和交互式呈现概念 在Blazor开发中&#xff0c;Razor 组件具备两种重要的呈现方式&#xff0c;分别是静态呈现和交互式呈现。 静态呈现 也被称为静态渲染&#xff0c;是一种典型的服务器端方案。在这种模式下&#xff0c;组件呈现时&#xff0c;用户与.NET/C# 代码之间缺…...

14-6-2C++的list

(一&#xff09;list对象的带参数构造 1.list&#xff08;elem);//构造函数将n个elem拷贝给本身 #include <iostream> #include <list> using namespace std; int main() { list<int> lst(3,7); list<int>::iterator it; for(itlst.begi…...

StarRocks常用命令

目录 1、StarRocks 集群管理&配置命令 2、StarRocks 常用操作命令 3、StarRocks 数据导入和导出 1、StarRocks 集群管理&配置命令 查询 FE 节点信息 SHOW frontends; SHOW PROC /frontends; mysql -h192.168.1.250 -P9030 -uroot -p -e "SHOW PROC /dbs;"…...

激光雷达和相机早期融合

通过外参和内参的标定将激光雷达的点云投影到图像上。 • 传感器标定 首先需要对激光雷达和相机&#xff08;用于获取 2D 图像&#xff09;进行外参和内参标定。这是为了确定激光雷达坐标系和相机坐标系之间的转换关系&#xff0c;包括旋转和平移。通常采用棋盘格等标定工具&…...

PMP–一、二、三模–分类–12.采购管理

文章目录 技巧十二、采购管理 一模12.采购管理--3.控制采购--输出--风险登记册--每个被选中的卖方都会带来特殊的风险。随着早期风险的过时以及新风险的出现&#xff0c;在项目执行期间对风险登记册进行变更。 供应商还未开始做&#xff0c;是一个风险&#xff0c;当做风险进行…...

C++ 标准模板库 (STL, Standard Template Library)

声明&#xff1a;大佬们~这是Tubishu在追寻stl过程中偶然得到了“颢天”大佬的笔记&#xff0c;shushu感觉非常有帮助&#x1f525;又颢天佬未曾来过CSDN&#xff0c;索性在此传达颢天大佬的功德&#x1f9ce; 传送门在此➡️颢天笔记✨✨ C 标准模板库 (STL, Standard Templa…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

django blank 与 null的区别

1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是&#xff0c;要注意以下几点&#xff1a; Django的表单验证与null无关&#xff1a;null参数控制的是数据库层面字段是否可以为NULL&#xff0c;而blank参数控制的是Django表单验证时字…...

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…...