news 2026/10/7 15:06:38

前端通信与React核心:HTTP、WebSocket与状态更新全解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端通信与React核心:HTTP、WebSocket与状态更新全解

如果你写过前端代码,就一定躲不开“前端通信”这四个字。页面要拉数据,表单要提交,日志要上报,服务端还要实时推消息,甚至两个浏览器标签页之间也得互相递个话。而在 React 项目里,这些问题往往还会跟上组件状态、渲染时机、生命周期函数这些概念纠缠在一起,本来以为是网络问题,查到最后发现是 setState 异步更新没搞明白。这篇东西打算把前端通信的主干方案梳理一遍,再随手把 React 里那些面经高频基础题一起讲清楚,适合刚接触 React 的开发者看,也适合准备面试的人集中过一遍底子。

1. 前端通信:先从浏览器里的数据管道说起

1.1 短连接时代的常规操作:HTTP 请求

前端通信里最老牌、使用率最高的就是 HTTP 请求。一个页面从服务端拿数据,最常见的形态就是fetch或者 axios 发一个 GET/POST 请求,服务端返回 JSON,前端拿到之后把它喂给组件去渲染。这个模式非常简单直观:前端发一次请求,等一条响应,整个过程结束。

但越简单的模式,在实际工程里越要抠细节。fetch是浏览器原生 API,第一代用起来并不顺手,比如它默认不会携带 cookie,credentials: 'include'需要手动指定;再比如fetch只有在网络层面报错时才会 reject,服务端返回 500、404 它照样 resolve,你得自己在response.ok里做判断。axios 之所以在社区里流行这么久,一方面是因为它把 XHR 封装得舒服,另一方面是拦截器、超时配置、取消请求这些能力开箱即用。项目里如果你们还在用原生 fetch,建议自己封装一层,统一处理错误码、token、超时这些逻辑。

这里还牵出一个核心知识:HTTP 协议本身的限制。HTTP/1.1 时代,浏览器对同一域名并发连接数限制在 6 条左右,请求多的时候只能排队;队头阻塞问题也一直存在,一个请求卡住会拖累后面排队的请求。HTTP/2 用多路复用解决了连接数和队头阻塞的一部分问题,但 TCP 丢包恢复依然可能造成队头阻塞,所以又过了几年,QUIC + HTTP/3 把传输层改成 UDP,才彻底把这个老问题绕过去。这套演化逻辑可以解释很多前端问题,比如为什么同一个页面里拆多个域名来放静态资源,为什么现在没那么迫切了,本质都是 HTTP 版本在背后发力。

1.2 实时性要求上来之后:短轮询、SSE 与 WebSocket

HTTP 请求是“一锤子买卖”,服务端想主动推数据给浏览器,靠普通 HTTP 请求是做不到的。于是就有了几个经典方案。

最早是短轮询,前端用setInterval每隔几秒发一次请求,问服务端有没有新数据。这个方案实现成本低,但效率也低,几十个客户端轮询一个接口,绝大多数请求都是空转,浪费带宽和服务器资源。长轮询稍微聪明一点:客户端发请求之后,服务端不立刻返回,而是 Hold 住连接,直到有数据才返回,或者超时了再返回空。这样减少了大量无效请求,但连接占用的时间更长,服务器并发连接数压力不小。直到今天,某些低实时性需求场景我仍然会选短轮询,因为它简单、好调试、不容易出幺蛾子,比如几秒钟才刷新一次的系统监控页面,没必要为了“实时”两个字上重武器。

SSE(Server-Sent Events)是一个经常被低估的方案。它基于 HTTP,服务端返回Content-Type: text/event-stream,客户端用EventSource接收。SSE 最核心的特点是单向:服务端可以持续地向浏览器推送消息,浏览器没法通过这条连接回传数据。实现难度很低,断线了浏览器还会自动重连,服务端只需要维护好连接列表就行。适合的场景是系统通知、股票行情、日志流这类“服务端单向下发”的业务。

如果数据需要双向流动,比如聊天消息、协同编辑、实时白板,那就得上 WebSocket。WebSocket 在 TCP 上做了一次 HTTP 握手升级,之后两端可以随时互相发数据,是真正的全双工。客户端用法很简单:new WebSocket('ws://...'),然后在onopen、onmessage、onclose、onerror上挂处理函数。但实际工程里,WebSocket 的坑远不止 API 那么简单。部署环境要确认网关和负载均衡是否支持 upgrade 协议;如果用了 HTTPS 页面,WebSocket 必须走wss://,否则浏览器会直接拦截;连接长时间空闲容易被中间设备回收,所以通常需要心跳机制,客户端隔一段时间发一个 ping,服务端回一个 pong,确保连接不会被掐断。这些都是上线以后最容易出问题的点。

1.3 跨窗口与跨标签页通信:postMessage、BroadcastChannel 与 storage 事件

除了“浏览器与服务端通信”,前端通信还有一个大类是“浏览器内不同窗口之间通信”。最常见的场景有两个:一个是页面里嵌了 iframe,父页面跟 iframe 内部应用要同步登录状态或操作指令;另一个是用户开了多个标签页,切换标签页时希望状态能同步。

iframe 场景里最正统的方式是postMessage。发送方调用targetWindow.postMessage(data, targetOrigin),接收方在window.addEventListener('message', handler)中处理。注意这个targetOrigin参数非常重要,它决定了消息哪个源可以收到。如果你传了一个固定 origin,安全性会好很多;如果你图省事传了*,等于打开家门让任何页面都能把消息塞进你的监听器。接收消息的时候也要先判断event.origin是否来自可信来源,不判断就处理数据,跟拿着陌生人递过来的药就吃没什么区别。

同源标签页之间通信,BroadcastChannel是更轻量也更现代的选择。同一个浏览器中的多个标签页,只要满足同源,就可以注册同名 channel,互相广播消息。它比postMessage的写法简单,也比 localStorage 轮询方案实时性好。localStorage 本身也有storage事件,但有个坑:这个事件只会在“其他标签页”修改 localStorage 时触发,当前页面自己改自己是不触发事件的。很多人第一次用它做跨页通信时,明明改数据了却等不到回调,就是因为这个机制没搞清。

1.4 微前端架构里的通信设计

如果你所在的项目用到微前端,主应用和子应用之间的通信同样属于前端通信范畴。常见做法大体分三种:第一种是主应用通过 props 把一些共享方法和数据传给子应用,相当于“依赖注入”;第二种是维护一个全局事件总线,主应用和子应用都来订阅和发布事件;第三种是干脆在内部封装一套基于window的自定义事件机制。

这里要特别留意一件事:不管是事件总线还是自定义事件,通信协议最好统一收敛到一个独立的 SDK 或者工具包里,不要让各个子应用各自定义一个事件名。子应用多了以后,事件命名混乱会直接导致排查困难。你自己写还好,团队十个人每人写一套命名规则,线上出了 Bug 都不知道是谁在发消息。

2. React 应用里的通信组织:从 Props 到 WebSocket

2.1 组件间通信的分层选择

前端通信落到 React 应用里,第一个绕不开的是组件之间的数据传递。

最基础的是父子组件。父组件往子组件传 Props,子组件通过回调函数通知父组件发生事件,这个模式 React 官方也推荐,好处是数据流方向清晰,代码容易追踪。当层级加深,中间层组件只是传递数据但其实根本用不到这些数据时,逐层透传会变得很难看,本地开发时还好,组件一多就成了“props drilling”。遇到这种情况,我优先考虑的是组件组合,把需要共享数据的部分提取到共同父级,或者干脆直接用 children 把结构颠倒一下,让数据消费者尽量靠近数据源。

跨层级组件共享数据,React 原生方案是 Context。Context 的写法很简单,一个 Provider 包起来,多个消费组件就能直接取值。但 Context 有个必须注意的性能点:Provider 的value如果每次渲染都生成新对象,所有消费了该 Context 的组件都会跟着重新渲染,即使其中有些组件根本只取了一部分数据。所以实际使用中,要么用 useMemo 稳定 value,要么把 value 拆成多个小 Context,按需消费。

再往上就是状态管理库了。我的选型判断标准是:状态是否要被很多模块在组件树之外共享,更新频率高不高。如果你的项目只是在一个页面内共享一些临时数据,useState+useReducer足够;如果有跨页面、跨模块的共享数据,比如用户信息和权限列表,可以考虑 Zustand,它非常轻,store 可以在任何模块里访问,也不依赖 Provider;如果是大团队、多人维护、需要严格的状态流转规范,Redux Toolkit 依然是靠谱选择。Jotai 这种方式也很灵活,原子化设计把细粒度状态分散到各个模块,适合画布类应用里每个节点都有自己的局域状态。

2.2 接口请求到 React 状态的完整链路

组件跟服务端通信,最朴素的写法是在useEffect里发请求。但直接写很容易踩到竞态、内存泄漏、重复请求这几个坑。

典型的第一版代码长这样:

useEffect(() => { fetch('/api/user') .then(res => res.json()) .then(data => setUser(data)) .catch(err => console.error(err)) }, [])

这个写法在组件卸载后,如果请求还没回来,再执行setUser就会触发 React 的更新警告。现代 React 18 虽然不再像旧版本那样在控制台打红色警告,但这依然意味着一次无效的 setState,属于没有必要的浪费。更稳妥的做法是用 AbortController 取消请求:

useEffect(() => { const controller = new AbortController() fetch('/api/user', { signal: controller.signal }) .then(res => res.json()) .then(data => setUser(data)) .catch(err => { if (err.name !== 'AbortError') { console.error(err) } }) return () => controller.abort() }, [])

这样组件卸载时请求被中断,不会再做出无意义的 setState。

但老实说,现在大部分项目我不会手动去写这套逻辑,直接上 React Query 或者 SWR 这种数据请求库更省心。它们把请求状态、缓存、重新验证、取消请求这些底层逻辑都收敛好了,开发时只要声明 key 和 fetcher 函数就能拿到data、isLoading、error。因为它们内部有全局缓存机制,多个组件请求同一个 key 的数据会自动去重,天然避免重复请求。

2.3 实时通信在 React 里的接入实践

WebSocket 和 SSE 属于长连接,接入 React 时要考虑跟组件生命的契合。一般做法是把连接实例放到一个 module 层级的单例或者 Store 中,在某个顶层组件或者入口文件中建立,然后通过 Context 或者状态管理库把消息推给需要的组件。

需要注意 StrictMode 的坑。React 18 开发模式下,组件挂载后 effect 会执行两次,也就是说如果你在useEffect里直接new WebSocket(),代码会建立两个连接。如果服务端没有做好去重,开发环境就可能看到重复消息。解决办法是在 useEffect 里返回一个 cleanup 函数,在卸载时关闭连接。这不仅是 StrictMode 的要求,也是生产环境组件因为路由切换卸载后,避免连接一直挂着的关键。

SSE 的接入思路也类似。EventSource不需要手动管理重连,但要记得在 cleanup 里调用close()。另外,SSE 的消息事件名可以自定义,默认是onmessage,服务端也可以命名event: news,客户端用addEventListener('news', handler)来监听。这个灵活性经常被忽略。

实时消息推送到前端之后,下一个问题是怎么让 UI 响应。我的建议是尽量走函数式更新,比如setMessages(prev => [...prev, newMessage]),想拿最新消息做判断时,最好在更新函数内部处理,避免读到一个过期的 state。

2.4 通信层错误处理与竞态问题

实时通信的错误处理比普通请求复杂。WebSocket 连接不是一次性行为,它可能会在运行中途断开,断线之后客户端要决定是静默重连,还是给用户提示,还是做数据补偿。常见的做法是维护一个连接状态机:connecting、open、reconnecting、closing。状态之间做迁移,比如每次断线重连之前先更新 UI 为“连接中”,让用户知道不是页面卡了。

还有一类坑跟竞态有关。用户切换页面或者切换标签时,WebSocket 消息到了,但页面里对应的组件已经卸载,这种消息要么丢弃,要么存到一个全局缓冲里等组件重新挂载时再消费。数据一致性要求高的实时刷新场景,比如在线表格编辑,光靠本地状态管理还不行,可能要引入版本号或者时间戳来做冲突检测。这部分说起来深入,但真正做实时产品的人应该能感受到,连接稳定性只是地基,地基之上还得处理消息时序。

3. 从生命周期到渲染:通信数据如何在 React 中流转

3.1 类组件生命周期函数:三个阶段对应三类通信任务

React 16.3 之前的生命周期函数用久了的人可能还记得那一长串名字:componentWillMount、componentDidMount、componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、componentDidUpdate、componentWillUnmount。新版 React 里,componentWillMount这类方法被标记了不安全,不建议继续用。原因和 React 后来的并发渲染有关:这些函数将来可能被调用多次,期间组件可能又回到了旧的状态,如果它里面包含副作用,比如发起请求或订阅事件,结果很容易错。

最终的类组件生命周期主线其实很清晰:

  • 挂载阶段:constructor→getDerivedStateFromProps→render→componentDidMount
  • 更新阶段:getDerivedStateFromProps→shouldComponentUpdate→render→getSnapshotBeforeUpdate→componentDidUpdate
  • 卸载阶段:componentWillUnmount

放到通信场景里,核心时机就两个:componentDidMount适合发起初次请求、建立 WebSocket、订阅事件;componentWillUnmount负责断开连接、清除定时器、取消订阅。数据到达之后,通过setState触发重新渲染。这个单向链路很直观,理解了它,很多面试问题就都有了抓手。

3.2 Hooks 时代:useEffect 如何接管生命周期

函数组件没有生命周期函数,Hooks 用useEffect把“副作用”收敛到一个统一 API 里。它可以等价替代很多类组件生命周期逻辑:如果依赖数组传空数组,effect 在挂载后执行一次,cleanup 函数在卸载时执行,类比componentDidMount+componentWillUnmount;如果依赖数组里有变量,变量变化时 effect 重新执行,类比组件更新后的通知。

这里有几个高频误用点。

第一,依赖数组不能省略,省略了 effect 每次渲染都执行,如果里面发请求,就变成每次渲染都请求一次,后果大家都懂。第二,不要在 effect 里读旧 state 来做条件判断,依赖数组会把你绕进去。正确的姿势是让状态进入依赖数组,或者使用useReducer把逻辑放在 reducer 里,保证它是纯函数。第三,useEffect在 DOM 绘制完成后异步执行,所以如果你需要在浏览器绘制之前同步更新 DOM 布局,比如测量节点尺寸并调整样式,应该改用useLayoutEffect。这两个 Hook 执行时机的差异,是面试官很喜欢埋伏笔的地方。

3.3 渲染流程与通信数据的联动

通信数据进入 React 之后,会走一个标准化流程:服务端返回数据 → 状态更新 → render 阶段生成新的元素树 → diff 对比 → commit 阶段更新真实 DOM。这套流程里通信数据只是触发源,真正的渲染决策由 React 内部的调和算法完成。

理解这个流程对实际调试非常有用。比如你发现 WebSocket 推送明明到了,但页面没变化,第一反应不应该是怀疑 React 有问题,而是要检查 setState 有没有生效、更新的数据是不是和旧数据完全相同、组件有没有被 memo 包住。如果数据是对象的同一个引用,React 会直接跳过渲染,因为它的比较逻辑是浅比较,prevState.next !== nextState.next判断为相等,自然不会重渲染。所以做实时数据更新时,尽量构造新的数组或对象,不要直接 push 进旧数组然后 setState 同一个引用。

4. React 基础问题解答:面经里高频出现的那几道题

4.1 setState 到底是同步还是异步

这道题几乎每场 React 面试都会碰到。直接回答“是异步的”其实不够严谨,严格来说 React 18 中是自动批处理,语义更接近“离散更新”。

在 React 18 之前,事件处理函数里的 setState 会被批量合并,表现为异步;而在setTimeout、原生事件、Promise 回调里,React 16 和 17 会同步处理,setState 之后立刻能读到新值。这个不一致让很多人困惑,但 React 18 改了规则,几乎所有场景都会自动批量更新,setTimeout 里也一样。这么做的好处是性能更优,多个 setState 合并成一次渲染。

但“自动批处理”带来的一个副作用是,你调用 setState 之后立刻读取这个 state,读到的还是旧值。如果你需要基于最新 state 做下一步操作,正确做法是使用函数式更新,setCount(prev => prev + 1),或者把依赖放在useEffect里等 state 更新后再执行。这个细节是高频考点,也是写代码时天天遇到的坑。

4.2 key 的作用到底是什么

key 是 React 做列表 diff 的关键属性,它让 React 可以识别同一次渲染中哪些元素是新增、哪些被删除、哪些保持原地复用。没有 key 或 key 使用不当,React 只能按索引顺序对位比较,很容易出现节点复用错误,导致组件内部状态串位。

最常见的反面教材是用数组 index 作为 key。列表头部插入一条数据时,index 后面的所有元素 key 都变了,React 会判定它们都不是同一个节点,然后整个重建。如果每个列表项是简单文本,重建问题也许不明显;但如果列表项内部是一个带输入框的组件,用户在这个输入框里输入了一半内容,此时在列表头部插一条数据,你会发现输入框里的内容错乱到别的行去了。原因就是节点被重建后,React 把原始 DOM 移给了另一个 key,state 却没有跟着走。

所以写列表时尽量用业务数据里稳定的 id 作为 key。如果是分页加载,还不能只靠服务端给的 index,页面翻回去列表重排,index 毫无意义。这个问题的核心是:key 的使命是给 React 提示“哪些节点在前后两次渲染中是同一个对象”,优先级比“展示的内容是否相似”高得多。

4.3 受控组件与非受控组件的选择

受控组件的模式是value+onChange,组件的值由 React state 全程管理,用户改输入框会触发 onChange 事件,然后你再决定要不要更新 state。非受控组件则不同,它把值留在 DOM 自己手里,React 只是渲染一个初始值,之后通过ref去读 DOM 的真实值。

受控组件的好处是单数据源,校验、联动、格式化都方便,是绝大多数表单场景的正确选择。非受控组件适合读取一次就能解决的场景,比如文件上传的input,你不需要实时监听选择文件这个动作,只要在提交时读一下ref.current.files就行。另外有一点要注意,不要在同一表单里混用受控和非受控,否则会出现某个字段被 React 接管、另一个字段完全交给 DOM 管理的割裂状态,排查起来非常费劲。

4.4 合成事件机制与原生事件的区别

React 自己封装了一套事件系统,叫合成事件。React 17 之前,所有事件都委托到 document 上;React 17 之后,改为委托到根容器上,这样可以让多个 React 应用共存于同一页面而不互相干扰。合成事件的目的一个是统一浏览器差异,避免频繁判断addEventListener的兼容性;另一个是性能优化,只需要在根容器上挂一次监听器,不必为每个节点都绑定事件。

合成事件一个容易踩的坑是它和原生事件的stopPropagation效果不一致。你在 React 的 onClick 里调用e.stopPropagation(),只能阻止同层级的 React 合成事件继续冒泡,但如果有人在原生 DOM 节点上绑了addEventListener('click', ...),这个原生监听器应用到 document 或者父节点上时,React 合成事件的停止并不能阻止它。反过来,原生事件里调e.stopPropagation(),同样不能阻止 React 合成事件向父组件传递。所以如果在项目里混用原生事件和合成事件,一定要清楚它们各自的事件流边界。

5. React 周边热点的一次集中解读

5.1 React 图表怎么选:Canvas、SVG 还是 WebGL

React 生态里做图表,方案多得让人眼花缭乱。核心要分清底层渲染技术:SVG、Canvas、WebGL 三种,它们的性能特征完全不同。

SVG 用 DOM 节点描述图形,优点是交互能力强,可以直接用 CSS 样式、绑定 React 事件、实现无障碍阅读。缺点是节点数量超过一定层级之后性能急剧下降,比如折线图几万个点全部渲染成<path>就很吃力。Canvas 用 JavaScript 绘制像素,适合大数据量,几万甚至几十万个点都能扛住,但你要自己实现拾取、tooltip 这类交互逻辑。WebGL 则利用 GPU 渲染,适合 3D 图表、实时大数据仪表盘,开发复杂度明显更高,对 WebGL 概念不熟的人不建议直接上手。

业务上我的推荐顺序是:交互复杂、数据量中等的后台管理系统,直接上 ECharts,它底层会根据数据量自动选择渲染方式,配置项也非常全;React 天然组件化程度高,可以考虑 Recharts 和 visx,它们都是基于 SVG 的。如果数据量非常大,比如绘制高频实时曲线,uPlot 这类轻量 Canvas 图表库更合适。另外一个画布场景也值得提:如果做流程编排、节点编辑器,react-flow 这个库对自定义节点支持得很好,可以快速搭出类似工作流编排器那样的界面,它内部也是用 React 渲染节点,非常适合需要深度定制节点的产品。

5.2 React Native 启动白屏怎么排查

React Native 应用启动白屏,是社区里吐槽很多的问题。最常见的原因是 JS Bundle 加载慢。应用启动后,原生端要先加载 JS 代码,这意味着要么从本地读取 bundle,要么从远程服务器拉 bundle,如果 bundle 很大或者设备性能弱,白屏时间会非常明显。

排查白屏,我一般按顺序走:先看原生端日志,确认原生容器有没有启动成功、有没有 JS 加载错误;如果原生没问题,再看 JS 层入口组件有没有报错,用 React Native Debugger 或者 Metro 的日志输出定位;还有一种比较隐性的是 SplashScreen 没有在 JS 挂载完成后主动关闭,导致原生启动画面盖在界面之上,用户看到的就是一片空白。另外入口组件里如果有某些全局副作用在异步执行,比如等待登录态检查,也会出现首屏长时间空白。把这些地方挨个排查,白屏的根因基本都能找到。

5.3 React 与 AI Agent:状态驱动模式的联想

最近很多人聊“基于 React 模式构建能思考与行动的 AI 智能体”,乍一听以为是 React 框架做 AI,其实这里说的 React 是 AI Agent 领域里的 ReAct 范式,指“推理 + 行动”循环,跟 UI 库 React 只是同名。

不过把一个 Ui 软件框架的思想用到 Agent 构建上,确实有相通之处。Agent 需要感知输入、思考计划、调用工具、观察结果然后决定下一步行动,这个循环本质上是一个状态机。我用 React 构建 Agent 前端时,最容易落地的思路是:把 Agent 的对话、工具调用结果、执行日志全部建模成不可变状态,用状态变化来驱动 UI 更新。而 Agent 执行过程中的计划拆解和工具调用关系,很适合用流程画布图来可视化,这样一来前面的 react-flow 就派上用场了。整体思路还是 React 的那套规律:搞清楚状态在哪里产生、在哪里消费,用一个统一的 Store 管理起来,视图自然就稳定。

6. 通信落地过程中的常见问题与排查技巧实录

6.1 高频问题速查

日常开发里,我积累了一张问题速查清单。下面这些场景几乎每周都能碰到,按“现象、原因、处理方向”三列整理如下:

现象可能原因排查思路
WebSocket 反复断开重连服务端空闲超时、中间代理回收连接抓包看 close code,确认是否缺少心跳机制
SSE 频繁自动重连服务端响应格式不对、网络不稳定检查 Content-Type 是否为 text/event-stream,观察重连间隔
postMessage 收不到消息targetOrigin 不匹配或监听时机过晚检查消息源,先 addEventListener 再发送
storage 事件一直不触发当前标签页修改 localStorage 本身不触发换不同标签页验证,或改用 BroadcastChannel
跨域请求被浏览器拦截CORS 头缺失或预检请求未通过观察 OPTIONS 请求响应,确认允许的 Origin、Method、Headers
React 中 setState 后立刻读不到新值自动批处理导致更新还未生效改用函数式更新,或放到 effect 中读取
组件卸载后仍收到消息长连接未在 cleanup 中关闭确保 useEffect 返回清理函数,AbortController 取消请求
开发环境请求出现两次React 18 StrictMode 下 effect 双执行关注 cleanup 函数,连接销毁后重建

6.2 几个我踩过坑之后的实操心得

第一个心得是,实时通信的方案尽量提前定下来,不要在开发中切来切去。我见过一个项目先用了短轮询,后来发现实时性不够,改成 SSE,后来又要求双向通信,不得不把 SSE 换成 WebSocket。每换一次,后端接口、前端状态管理、数据库策略都要跟着调整,成本非常大。接手任何新项目,第一件事永远是跟产品确认实时性要求,再定方案,而不是想当然地认为 WebSocket 一定比轮询好。

第二个心得是,WebSocket 重连不要无脑重试。服务端如果因为发布或者故障重启,短时间内会有一大批客户端同时断线,如果每个客户端都立刻重连,会造成服务端压力瞬间飙升。稳妥一点的做法是带退避策略:第一次断线等 1 秒,第二次等 2 秒,第三次等 4 秒,最多等 30 秒。这样既不会让用户等太久,又能把重连风暴对服务端的冲击降到最低。我自己的项目里还加了navigator.onLine检测,如果浏览器本身离线,连重连都不用触发。

第三个想说的是,React 应用里做通信不妨先“画数据流图”。通信本质上是数据在不同模块之间流动,先把数据源、消费方、生命周期铺在一张图上,写代码时就不会在 useEffect 里绕来绕去。我每次做实时协作类项目,都会先把 WebSocket 的消息类型列成一张表,标明每条消息由谁产生、谁消费、消费完是否要更新某个存储字段,然后才动手写代码。这套习惯帮我避掉了很多“消息到了但页面不更新”的玄学问题,也让我在看 React 面经题时不再觉得那是一堆零散知识点,因为它们的底层逻辑就是代码里每天都在发生的那点事。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 15:06:19

功能盘点|AI论文写作工具推荐,论文写作与修改之选

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:06:16

pstack原则09类型系统纪律:如何让非法状态不可表示

pstack原则09类型系统纪律&#xff1a;如何让非法状态不可表示 【免费下载链接】pstack-claude Claude Code, Codex, Copilot, Pi, OpenCode, Gemini, and Prime Agent versions of Potetos pstack. Rigorous agent workflows with Cursor primitives translated for other har…

作者头像 李华
网站建设 2026/10/7 15:06:15

HarmonyOS 7 ImageAnimator:长动图帧时序修正与前后台恢复

一、那张“偶尔加速”的动图没有坏 FramePulseLab 原本只是一个动效素材验收页&#xff1a;设计同学把 metro_signals.gif 放进 resources/rawfile&#xff0c;开发侧显示帧数、原始时长和当前生命周期&#xff0c;确认无误后再把素材交给业务页面。文件是 720720、48 帧&#…

作者头像 李华
网站建设 2026/10/7 15:05:35

UR3与Realsense L515手眼标定实战:从AX=XB到抓取精度校准

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:05:22

Linux 内存回收机制:水位线、kswapd 与 Direct Reclaim

Linux 内存回收机制&#xff1a;水位线、kswapd 与 Direct Reclaim源码基线&#xff1a;Linux v6.18.9&#xff08;mm/page_alloc.c / mm/vmscan.c&#xff09;。 现象&#xff1a;内存明明还有&#xff08;free 数 GB&#xff09;&#xff0c;进程却偶发卡顿几百毫秒&#xff…

作者头像 李华