React useSyncExternalStore 实战:订阅浏览器在线状态、媒体查询与 localStorage 跨标签同步
React useSyncExternalStore 实战:订阅浏览器在线状态、媒体查询与 localStorage 跨标签同步你大概写过这样的代码:想在组件里知道当前是不是断网了,于是在useEffect里加了window.addEventListener(online, ...),再用useState存一个isOnline。写多了你会发现三个烦人的问题:首次渲染时isOnline是useState里写死的初值,和真实的navigator.onLine可能对不上,页面会闪一下。同一个「在线状态」逻辑在五个组件里各抄一遍,监听器加了五次。上了 SSR(比如 Next.js),服务端没有navigator,水合(hydration)时客户端和服务端渲染结果不一致,控制台一片红色警告。React 18 给了个专门解决这类问题的 Hook:useSyncExternalStore。它是为「订阅 React 之外的数据源」而生的——浏览器 API、第三方 store、localStorage,都算外部源。这篇我们把它彻底用明白。先看朴素写法的坑// 朴素写法:能跑,但有 hydration 问题和重复监听 function useOnlineStatusNaive() { const [online, setOnline] useState(navigator.onLine); useEffect(() { const up () setOnline(true); const down () setOnline(false); window.addEventListener(online, up); window.addEventListener(offline, down); return () { window.removeEventListener(online, up); window.removeEventListener(offline, down); }; }, []); return online; }问题出在useState(navigator.onLine):SSR 时navigator不存在直接抛错;就算兜住了,服务端和客户端初值也不一致。而且每个用这个 Hook 的组件都会独立注册一对监听器,不共享。useSyncExternalStore 的三个参数它的签名是:const value useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?);subscribe(callback):订阅外部源。数据变了就调callback通知 React 重读。返回一个取消订阅的函数。getSnapshot():读当前值(快照)。React 用它判断值有没有变(用Object.is比较)。getServerSnapshot():可选,SSR 时在服务端读值,专治水合不一致。关键点:React 保证在渲染期间用getSnapshot读到的值是一致的,不会出现「读一半外部源变了」的撕裂(tearing)。这是普通useState useEffect给不了的。正确写法:在线状态 Hookimport { useSyncExternalStore } from react; // subscribe 只关心「怎么订阅」,不关心当前值 function subscribe(callback) { window.addEventListener(online, callback); window.addEventListener(offline, callback); return () { window.removeEventListener(online, callback); window.removeEventListener(offline, callback); }; } export function useOnlineStatus() { return useSyncExternalStore( subscribe, () navigator.onLine, // 客户端读真实值 () true // 服务端假定在线,避免水合闪烁 ); }用起来就一行:function StatusBar() { const online useOnlineStatus(); return div{online ? 在线 : 已断网,内容可能未同步}/div; }注意subscribe定义在组件外面。这很重要:如果把subscribe写成组件内每次渲染都新建的函数,React 会认为订阅目标变了,于是每次渲染都退订再重订,白白抖动。定义在外面保证它引用稳定。进阶一:订阅媒体查询同样的套路能订阅matchMedia,做响应式逻辑(注意不是用来替代 CSS 媒体查询,而是当你需要在 JS 里根据断点分支时):export function useMediaQuery(query) { const mql window.matchMedia(query); const subscribe (callback) { mql.addEventListener(change, callback); return () mql.removeEventListener(change, callback); }; return useSyncExternalStore( subscribe, () mql.matches, () false // 服务端默认不匹配 ); } // 用法 function Nav() { const isMobile useMediaQuery((max-width: 768px)); return isMobile ? Drawer / : Sidebar /; }这里有个坑:window.matchMedia(query)在每次渲染都执行了。如果query是固定字符串没大问题,但更严谨的做法是用useMemo缓存mql,并把subscribe用useCallback绑定,避免 query 变化时的重复创建。进阶二:localStorage 跨标签页同步这是useSyncExternalStore真正发光的场景。原生storage事件有个反直觉的行为:它只在「其他」标签页触发,当前标签页自己改localStorage不会收到通知。所以我们需要在写入时手动派发一个自定义事件,把当前标签页也串起来。import { useSyncExternalStore, useCallback } from react; export function useLocalStorage(key, initialValue) { const subscribe useCallback((callback) { // storage 事件:其他标签页改动 window.addEventListener(storage, callback); // 自定义事件:当前标签页自己改动 window.addEventListener(local-storage, callback); return () { window.removeEventListener(storage, callback); window.removeEventListener(local-storage, callback); }; }, []); const getSnapshot useCallback(() { return localStorage.getItem(key) ?? JSON.stringify(initialValue); }, [key, initialValue]); const store useSyncExternalStore( subscribe, getSnapshot, () JSON.stringify(initialValue) // 服务端快照 ); const setValue useCallback((value) { localStorage.setItem(key, JSON.stringify(value)); // 手动派发,通知本标签页的订阅者 window.dispatchEvent(new Event(local-storage)); }, [key]); return [JSON.parse(store), setValue]; }用法和useState几乎一样,但状态会在所有标签页之间自动同步:function ThemeToggle() { const [theme, setTheme] useLocalStorage(theme, light); return ( button onClick{() setTheme(theme light ? dark : light)} 当前主题:{theme} /button ); }在两个标签页打开这个组件,任一个点按钮,另一个会立刻跟着变——这就是订阅外部源的威力。一个容易踩的性能坑:getSnapshot 每次返回新对象getSnapshot必须返回「稳定」的值。React 用Object.is比较前后两次快照,如果你每次都返回一个新对象,即便内容一样,React 也会认为变了,导致无限重渲染:// ❌ 错误:每次返回新数组,React 认为一直在变 () JSON.parse(localStorage.getItem(list) || []) // ✅ 正确:返回原始字符串这种可比较的原始值,解析放到组件里 () localStorage.getItem(list) || []上面useLocalStorage的实现就是这么做的:快照返回字符串(原始值,可用Object.is稳定比较),JSON.parse放在返回时做。如果你确实要返回对象,得自己在外面缓存,只有内容真变了才换引用。小结useSyncExternalStore专门用来订阅 React 之外的数据源:浏览器 API、第三方 store、localStorage。三个参数各司其职:subscribe管订阅、getSnapshot读值、getServerSnapshot兜 SSR 水合。subscribe要引用稳定(定义在组件外或用useCallback),否则会反复退订重订。getSnapshot必须返回可用Object.is稳定比较的值,别每次返回新对象,否则无限重渲染。localStorage 场景记得手动派发自定义事件,因为原生storage事件不通知当前标签页。一句话记忆:凡是「React 管不着、但组件想跟着变」的外部状态,就交给 useSyncExternalStore,它帮你解决订阅、一致性快照和 SSR 水合三件事。

相关新闻

GPT-5.6模型家族解析:Luna、Terra、Sol的定位、差异与应用选型

GPT-5.6模型家族解析:Luna、Terra、Sol的定位、差异与应用选型

1. 项目概述:GPT-5.6家族的三位新成员最近,关于下一代大语言模型GPT-5.6的讨论在技术社区里热度不减,尤其是其内部代号为“Luna”、“Terra”和“Sol”的三个不同版本。这可不是简单的版本迭代,而是一个清晰的家族化产品战略。简单…

2026/8/2 23:02:12阅读更多 →
OptiScaler配置指南:3步解决游戏画面模糊与帧率卡顿问题

OptiScaler配置指南:3步解决游戏画面模糊与帧率卡顿问题

OptiScaler配置指南:3步解决游戏画面模糊与帧率卡顿问题 【免费下载链接】OptiScaler OptiScaler bridges upscaling/frame gen across GPUs. Supports DLSS2/XeSS/FSR2 inputs, replaces native upscalers, enables FSR-FG/XeFG on non-FG titles. Supports Nukem …

2026/8/2 23:02:12阅读更多 →
php-vips源码解析:从VipsObject到Image类的底层实现原理

php-vips源码解析:从VipsObject到Image类的底层实现原理

php-vips源码解析:从VipsObject到Image类的底层实现原理 【免费下载链接】php-vips php binding for libvips 项目地址: https://gitcode.com/gh_mirrors/ph/php-vips php-vips是一个高效的PHP图像处理库,作为libvips的PHP绑定,它提供…

2026/8/2 23:00:11阅读更多 →
MobaXterm中文版:5分钟掌握远程服务器管理的终极方案

MobaXterm中文版:5分钟掌握远程服务器管理的终极方案

MobaXterm中文版:5分钟掌握远程服务器管理的终极方案 【免费下载链接】Mobaxterm-Chinese Mobaxterm simplified Chinese version. Mobaxterm 的简体中文版. 项目地址: https://gitcode.com/gh_mirrors/mo/Mobaxterm-Chinese MobaXterm中文版是一款专为中文用…

2026/8/3 0:16:38阅读更多 →
ESXi Unlocker终极指南:5分钟解锁macOS虚拟化,打造完美跨平台环境

ESXi Unlocker终极指南:5分钟解锁macOS虚拟化,打造完美跨平台环境

ESXi Unlocker终极指南:5分钟解锁macOS虚拟化,打造完美跨平台环境 【免费下载链接】esxi-unlocker VMware ESXi macOS 项目地址: https://gitcode.com/gh_mirrors/es/esxi-unlocker 还在为VMware ESXi无法运行macOS虚拟机而烦恼吗?ESX…

2026/8/3 0:16:38阅读更多 →
【单片机毕设案例分享】基于单片机传感器阵列的水质安全检测设备开发 基于 STC89C52 的多按键水质参数调控装置实现(018101)

【单片机毕设案例分享】基于单片机传感器阵列的水质安全检测设备开发 基于 STC89C52 的多按键水质参数调控装置实现(018101)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/8/3 0:16:38阅读更多 →
【图像去噪】基于自适应中值滤波实现图像去噪matlab代码

【图像去噪】基于自适应中值滤波实现图像去噪matlab代码

1 简介由于图像噪声会对后续的图像处理结果产生影响,所以在对图像进行其他处理前应先对图像去噪.针对传统中值滤波器在去除均匀分布椒盐噪声时效果并不理想,设计出一种自适应阈值中值滤波器.分别用两种滤波器进行图像去噪实验,通过对比去噪后图像的信噪比,峰值信噪比以及视觉效…

2026/8/3 0:16:38阅读更多 →
Windows 11 LTSC 24H2一键恢复微软商店终极指南

Windows 11 LTSC 24H2一键恢复微软商店终极指南

Windows 11 LTSC 24H2一键恢复微软商店终极指南 【免费下载链接】LTSC-Add-MicrosoftStore Add Windows Store to Windows 11 24H2 LTSC 项目地址: https://gitcode.com/gh_mirrors/ltscad/LTSC-Add-MicrosoftStore 你是否正在使用Windows 11 LTSC企业版,却发…

2026/8/3 0:16:37阅读更多 →
网盘文件直链获取终极指南:告别限速烦恼的完整解决方案

网盘文件直链获取终极指南:告别限速烦恼的完整解决方案

网盘文件直链获取终极指南:告别限速烦恼的完整解决方案 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼…

2026/8/3 0:14:37阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/2 0:00:10阅读更多 →
限时公开!某头部SaaS公司内部AI模板工厂架构文档(含5类行业模板源码+性能压测报告)

限时公开!某头部SaaS公司内部AI模板工厂架构文档(含5类行业模板源码+性能压测报告)

更多请点击: https://intelliparadigm.com 第一章:AI模板批量生成的核心价值与落地全景 AI模板批量生成正从实验性工具演进为现代软件工程的关键基础设施。它通过语义理解、上下文感知与结构化约束,将重复性高、模式明确的代码/文档/配置生成…

2026/8/2 0:00:12阅读更多 →
如何快速找回消失的网页:Web Archives浏览器扩展终极指南

如何快速找回消失的网页:Web Archives浏览器扩展终极指南

如何快速找回消失的网页:Web Archives浏览器扩展终极指南 【免费下载链接】web-archives Browser extension for viewing archived and cached versions of web pages, available for Chrome, Edge and Safari 项目地址: https://gitcode.com/gh_mirrors/we/web-a…

2026/8/2 0:00:13阅读更多 →
3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片,PDF文档识别,排除水印/页眉页脚,扫描/生成二维码。…

2026/8/3 0:00:32阅读更多 →
[具身智能-181]:PC+服务器+具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构

[具身智能-181]:PC+服务器+具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构

PC服务器具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构一、前言:具身智能需要“混合算力闭环系统”传统人工智能依赖云端静态数据集训练,不具备物理交互能力,无法适应真实世界的不确定性。具身智能(Embodied…

2026/8/3 0:00:32阅读更多 →
[具身智能-181]:大分布式通信模型对比:看懂为什么 DDS 是 ROS2 底层通信最优解

[具身智能-181]:大分布式通信模型对比:看懂为什么 DDS 是 ROS2 底层通信最优解

前言构建机器人、具身智能这类分布式实时系统,通信底座直接决定整套系统的实时性、容错性、组网能力。分布式领域长期存在 4 类经典通信架构:点对点模式、Broker 中间代理模式、广播模式、以数据为中心(DDS)模式。很多开发者疑惑&…

2026/8/3 0:00:32阅读更多 →
无损视频剪辑终极指南:如何实现快速高效的多媒体处理

无损视频剪辑终极指南:如何实现快速高效的多媒体处理

无损视频剪辑终极指南:如何实现快速高效的多媒体处理 【免费下载链接】lossless-cut The swiss army knife of lossless video/audio editing 项目地址: https://gitcode.com/gh_mirrors/lo/lossless-cut 在数字媒体创作领域,视频编辑处理的质量损…

2026/8/2 1:29:34阅读更多 →
AI辅助本科论文写作:8大工具评测与高效使用指南

AI辅助本科论文写作:8大工具评测与高效使用指南

1. 本科生论文写作的AI辅助现状本科毕业论文是每个大学生必须跨越的一道坎。记得我当年写论文时,光是文献检索就花了整整两周时间,打印的参考文献堆满了半个书桌。如今AI技术的发展为学术写作带来了革命性变化,合理使用这些工具可以节省80%以…

2026/8/2 2:32:55阅读更多 →
如何快速配置大麦自动抢票系统:从零开始搭建Python抢票助手

如何快速配置大麦自动抢票系统:从零开始搭建Python抢票助手

如何快速配置大麦自动抢票系统:从零开始搭建Python抢票助手 【免费下载链接】ticket-purchase 大麦自动抢票,支持人员、城市、日期场次、价格选择 项目地址: https://gitcode.com/GitHub_Trending/ti/ticket-purchase 还在为抢不到热门演唱会门票…

2026/8/2 2:09:20阅读更多 →