新闻详情

新闻详情

首页 / 资讯中心 / 详情

正交的 React 组件:用正交性重构组件边界,让取数与 UI 彻底解耦

发布时间:2026/9/30 6:48:51来源:尧图网络
正交的 React 组件:用正交性重构组件边界,让取数与 UI 彻底解耦
文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载本文是前端精读周刊对《The Benefits of Orthogonal React Components》一文的深度解读聚焦正交性Orthogonality这一软件设计原则在 React 组件架构中的落地实践。通过组件与取数逻辑正交组件与滚动监听正交两个完整案例你会掌握如何用 Suspense、自定义 Hooks、Main 组件等手段把 UI 展示与数据逻辑彻底分离最终获得更易维护、易读、可测试的组件体系。引言什么是正交性搭配了合适的设计模式的代码才可拥有良好的可维护性。正交性正是这样一种设计模式所谓正交即模块之间不会相互影响。想象一个音响它的音量旋钮与换台按钮之间如果是正交关系那么调节音量绝不会影响当前频道反之如果控制音量时同时会改变换台这台设备几乎无法正常使用也很难维护。前端代码也一样——UI 与数据处理逻辑分离就是一种符合正交原则的设计它有利于长期代码质量的维护。如果一个组件既负责渲染 UI又负责发请求、管 loading、存状态那么任何一处改动都可能牵动其他逻辑最终演变成改一处、坏一片的维护噩梦。一个正交 React 应用应有的模块划分一个拥有良好正交性的 React App 会按照如下模块分离设计UI 元素展示型组件——只负责长得什么样不关心数据从哪来。取数逻辑fetch library、REST 或 GraphQL 客户端——只负责怎么拿数据。全局状态管理如 redux——只负责共享数据放哪、怎么变。持久化local storage、cookies——只负责数据存哪里、怎么恢复。这四个维度彼此独立、可单独替换换掉取数库不影响 UI改数据结构不影响取数方式。下面通过两个具体例子说明如何实现这种分离。案例一让组件与取数逻辑正交反例一个既渲染又取数的EmployeesPage比如一个展示雇员列表的组件EmployeesPageimport React, { useState } from react; import axios from axios; import EmployeesList from ./EmployeesList; function EmployeesPage() { const [isFetching, setFetching] useState(false); const [employees, setEmployees] useState([]); useEffect(function fetch() { (async function() { setFetching(true); const response await axios.get(/employees); setEmployees(response.data); setFetching(false); })(); }, []); if (isFetching) { return divFetching employees..../div; } return EmployeesList employees{employees} /; }这个组件自己管理isFetching、employees两个状态自己用axios发请求自己处理 loading 分支最后才渲染列表。它把取数逻辑和UI 渲染两条完全独立的维度缝在了一起一旦接口地址、请求参数或 loading 样式变化都要改动这个组件。正解用 Suspense 把 loading 剥离到父级正交的写法如下import React, { Suspense } from react; import EmployeesList from ./EmployeesList; function EmployeesPage({ resource }) { return ( Suspense fallback{h1Fetching employees..../h1} EmployeesFetch resource{resource} / /Suspense ); } function EmployeesFetch({ resource }) { const employees resource.employees.read(); return EmployeesList employees{employees} /; }Suspense将 loading 状态剥离到父级组件因此子组件只需要关心如何用数据不需关心如何取数据以及 loading 态。EmployeesFetch只做一件事拿到resource同步调用.read()读出数据渲染EmployeesList。它不关心请求是否正在发送、什么时候完成、失败了怎么办——这些统统由外层的Suspense负责 Pending/loading接管。这正是本仓库 精读《Suspense 改变开发方式》 中强调的核心机制Suspense 要求代码在数据未就绪时抛出一个可被捕获的 Promise渲染被挂起并冒泡到最近的Suspense边界渲染fallbackPromise 结束后再恢复渲染。因此 UI 组件可以假装同步地消费异步数据loading 完全与 UI 解耦。案例二让组件与滚动监听正交反例按钮与滚动判断混在一个组件里比如一个滚动到一定距离就出现 jump to top 的组件ScrollToTop常见的实现是import React, { useState, useEffect } from react; const DISTANCE 500; function ScrollToTop() { const [crossed, setCrossed] useState(false); useEffect(function() { const handler () setCrossed(window.scrollY DISTANCE); handler(); window.addEventListener(scroll, handler); return () window.removeEventListener(scroll, handler); }, []); function onClick() { window.scrollTo({ top: 0, behavior: smooth }); } if (!crossed) { return null; } return button onClick{onClick}Jump to top/button; }可以看到这个组件里按钮本身与滚动到一定距离的状态判断混合在了一起滚动监听、阈值判断、按钮渲染、点击行为全在同一个函数体内。正解抽象通用组件IfScrollCrossed如果将 滚动到一定距离就渲染 UI 抽象成通用组件IfScrollCrossed滚动逻辑就用自定义 HookuseScrollDistance封装起来import { useState, useEffect } from react; function useScrollDistance(distance) { const [crossed, setCrossed] useState(false); useEffect( function() { const handler () setCrossed(window.scrollY distance); handler(); window.addEventListener(scroll, handler); return () window.removeEventListener(scroll, handler); }, [distance] ); return crossed; } function IfScrollCrossed({ children, distance }) { const isBottom useScrollDistance(distance); return isBottom ? children : null; }有了IfScrollCrossed我们就能专注写 点击按钮跳转到顶部 这个纯 UI 组件function onClick() { window.scrollTo({ top: 0, behavior: smooth }); } function JumpToTop() { return button onClick{onClick}Jump to top/button; }最后将它们拼装在一起import React from react; // ... const DISTANCE 500; function MyComponent() { // ... return ( IfScrollCrossed distance{DISTANCE} JumpToTop / /IfScrollCrossed ); }这样JumpToTop与IfScrollCrossed就构成了正交关系逻辑也更清晰。不仅如此这个抽象让IfScrollCrossed可以被其他场景复用——比如滚动 300px 后弹出订阅表单import React from react; // ... const DISTANCE_NEWSLETTER 300; function OtherComponent() { // ... return ( IfScrollCrossed distance{DISTANCE_NEWSLETTER} SubscribeToNewsletterForm / /IfScrollCrossed ); }注意这里的useScrollDistance自定义 Hook 就是本仓库 精读《React Hooks》 反复强调的抽象单元Hook 把滚动距离状态这类横切逻辑从组件里抽离让 UI 组件保持纯净。而useEffect的依赖数组[distance]保证了阈值变化时自动重新绑定监听这也是 精读《useEffect 完全指南》 中effect 依赖驱动的正确心智模型的直接体现。Main 组件专门负责拼装脏逻辑上面例子中MyComponent就是一个Main 组件。Main 组件封装一些脏逻辑即它要负责不同模块的组装而这些模块之间不需要知道彼此的存在。一个应用会存在多个 Main 组件它们负责拼装各种作用域下的脏逻辑。换句话说正交性不是消灭脏逻辑而是把脏逻辑收敛到少数几个组装点。业务组件保持干净组装责任集中在 Main 层改动影响面因此被限制在固定位置。正交设计的好处容易维护正交组件逻辑相互隔离不用担心连带影响因此可以放心大胆地维护单个组件。易读由于逻辑分离导致了抽象因此每个模块做的事情都相对单一很容易猜测一个组件做的事情——看到JumpToTop就知道它是跳转按钮看到IfScrollCrossed就知道它是滚动到阈值才渲染子内容。可测试由于逻辑分离可以采取逐个击破的思路进行单测useScrollDistance可以独立测滚动判断JumpToTop可以独立测点击行为互不干扰。权衡正交不是越彻底越好如果不采用正交设计模块之间的强关联会让应用最终变得难以维护但如果将正交设计应用到极致可能会产生许多不必要的抽象这些抽象的复用仅此一次反而造成过度设计。正交设计的本质是合理抽象——完全不抽象与过度抽象都是不可取的需要在业务复杂度与抽象成本之间找到平衡点。精读四块最容易需要抽象的要害结合上文正交设计一定程度可以理解为合理抽象本仓库对该主题的延伸思考认为有四个要害区域尤其需要抽象UI 元素、取数逻辑、全局状态管理、持久化。全局状态管理注入而非依赖全局状态管理注入到组件就是一种正交的抽象模式组件不用关心数据从哪来而直接使用数据数据管理完全交由数据流层管理。这与 精读《前端数据流哲学》 的观点一脉相承——状态从组件里长出来变成从数据流里读进来组件只消费、不生产全局状态。取数逻辑最容易被忽略的一环取数逻辑往往是可能被忽略的一环。无论是像原文中直接关心到fetch方法的 UI 组件还是利用取数工具库关心了loading状态都还不够正交。比如用 swr 时最常见的写法import useSWR from swr; function Profile() { const { data, error } useSWR(/api/user, fetcher); if (error) return divfailed to load/div; if (!data) return divloading.../div; return divhello {data.name}!/div; }虽然取数生命周期已被封装进自定义 HookuseSWR但error信息对 UI 组件来说依然是一个脏数据这让这个 UI 组件不仅要渲染数据还要担心取数是否会失败或者是否在 loading 中。组件被迫感知三种状态loading / error / data与渲染数据这一单一职责发生了耦合。Suspense 模式把取数状态彻底交出去好在 Suspense 模式解决了这个问题import { Suspense } from react; import useSWR from swr; function Profile() { const { data } useSWR(/api/user, fetcher, { suspense: true }); return divhello, {data.name}/div; } function App() { return ( Suspense fallback{divloading.../div} Profile / /Suspense ); }这样Profile只要专注于做数据渲染而不用担心useSWR(/api/user, fetcher, { suspense: true })这个取数过程发生了什么、是否取数失败、是否在loading中。因为取数状态由Suspense管理而取数是否意外失败由ErrorBoundary管理。从源码层面看这一机制的实现基础在 精读《Hooks 取数 - swr 源码》 中有完整剖析开启suspense配置后swr 在数据未就绪时会执行throw CONCURRENT_PROMISES[key]把取数的 Promise 直接抛出让 React 的 Suspense 边界捕获并渲染fallback等取数完毕再返回{ error, data, revalidate, isValidating }结构。正因为有了这关键的一行throwUI 组件才得以假装同步地读取data彻底摆脱对 loading 状态的感知。而错误一侧精读《React Error Boundaries》 详细说明了 ErrorBoundary 的边界它可以捕获渲染阶段、生命周期与 Hooks 中抛出的错误通过getDerivedStateFromError更新 state、componentDidCatch做错误上报但无法捕获事件回调、setTimeout/requestAnimationFrame等异步时机中的错误——这正对应了 Suspense 取数失败后错误被抛出到渲染周期、从而可被边界捕获的路径。组合出的效果模块解耦、规模可控合理的抽象使组件逻辑变得更简单从而组件嵌套使用时不用担心额外影响。尤其在大型项目中不要担心正交抽象会使本来就很多的模块数量再次膨胀因为相比于维护 100 个相互影响、内部逻辑复杂的模块维护 200 个职责清晰、相互隔离的模块也许会更轻松。模块数量增加换取的是每模块复杂度的直线下降与可替换性的提升这在长期维护中通常是划算的。总结从正交设计角度来看React 生态已经给出了三条与 UI 分离的成熟路径Hooks解决了状态管理与 UI 分离的问题——状态逻辑被封装进 HookUI 只消费返回值Suspense解决了取数状态与 UI 分离的问题——loading 由边界统一接管组件按同步方式渲染异步数据ErrorBoundary解决了异常与 UI 分离的问题——错误展示与上报集中在边界组件业务组件无需散落 try/catch。这三者共同构成了一套正交 React 组件的完整拼图UI 元素、取数逻辑、全局状态管理、持久化各居其位、互不越界复杂逻辑收敛到 Main 组件与边界组件中统一处理。理解并运用正交性原则是让 React 项目在长期迭代中保持可维护性的关键一步。延伸阅读本文相关主题在本仓库中还有更深入的系列解读可继续阅读 精读《Suspense 改变开发方式》、精读《React Error Boundaries》、精读《Hooks 取数 - swr 源码》、精读《React Hooks》 与 精读《前端数据流哲学》 获取完整的原理细节。赞分享文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载相关推荐终极指南5种实用React组件设计模式从高阶组件到正交设计终极指南5种实用React组件设计模式从高阶组件到正交设计 React组件设计模式是构建可复用、可维护前端应用的核心技能。本文将深入探讨5种最实用的Reac文档技术博客教程Langflow 前端组件重构实战如何从复杂 React 组件中正确提取自定义 HookLangflow 前端组件重构实战如何从复杂 React 组件中正确提取自定义 Hook Langflow 前端是一套 React TypeScript人工智能大模型AI AgentRAG后端前端MCP 服务工作流自动化老Mac重获新生OpenCore Legacy Patcher终极指南老Mac重获新生OpenCore Legacy Patcher终极指南 还在为老款Mac无法升级到最新macOS而烦恼吗您的2007 2017年间的Mac设操作系统固件驱动开发上一篇3步掌握SillyTavern打造智能对话AI的终极实战指南下一篇终极指南5分钟搭建i茅台自动预约系统告别手动抢购烦恼创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

JAVA中初学者易混的length,length(),size() 2026/9/30 7:45:25

JAVA中初学者易混的length,length(),size()

length是属性,length()和size()是方法1.length -> 数组属性返回数组长度int[] arr new int[5]; System.out.println(arr.length); // 52.length() -> 字符串方法返回字符串的字符个数String s "java"; System.out.println(s.length()); // 43.siz…

阅读更多 →
智能温控器如何接入楼宇自控:WT303/WT304从接线到Modbus调试的实战指南 2026/9/30 7:45:17

智能温控器如何接入楼宇自控:WT303/WT304从接线到Modbus调试的实战指南

从“墙上的温度旋钮”到“楼宇系统的一个节点”:为什么我会研究星纵物联WT303/WT304 做智能化集成这些年,被甲方问得最多的一个问题不是“你家摄像头多清晰”,而是“我墙上那个调温度的能不能联网、能不能远程改温度”。一问才发现&#xff0…

阅读更多 →
降AI率解读:为什么理工科硕士论文降AI率比文科更难2026深度分析 2026/9/30 7:45:03

降AI率解读:为什么理工科硕士论文降AI率比文科更难2026深度分析

降AI率解读:为什么理工科硕士论文降AI率比文科更难2026深度分析 理工科降AI率比文科难解读背后的机制,很多人说不清楚。这篇梳理清楚降AI率核心逻辑,以及针对性的解决方案。 主推嘎嘎降AI(www.aigcleaner.com)&#…

阅读更多 →
AgentScope 2.0 多智能体框架实战:4 个任务节点把终端代码助手交付成 IM 多租户客服服务 2026/9/30 7:45:02

AgentScope 2.0 多智能体框架实战:4 个任务节点把终端代码助手交付成 IM 多租户客服服务

AgentScope 2.0 多智能体框架实战:4 个任务节点把终端代码助手交付成 IM 多租户客服服务 【免费下载链接】agentscope Build and run agents you can see, understand and trust. 项目地址: https://gitcode.com/GitHub_Trending/ag/agentscope AgentScope 2…

阅读更多 →
Unity拖尾效果实战:组件原理、自定义网格与性能优化 2026/9/30 7:44:56

Unity拖尾效果实战:组件原理、自定义网格与性能优化

简介:面向Unity开发者的武器拖尾特效实现资料,聚焦PocketRPG Trails插件的实战应用。内容涵盖插件导入、空物体构建、Mesh Renderer与WeaponTrail组件挂载、材质球选择等完整流程,并整理了一段可直接参考的C#脚本框架,包括weaponT…

阅读更多 →
Git本地基础操作实战:从安装配置到分支合并与远程推送 2026/9/30 7:44:55

Git本地基础操作实战:从安装配置到分支合并与远程推送

刚从 SVN 或者干脆是“用U盘拷代码”时代转型过来的朋友,通常接触 Git 的第一周都会有点懵。网上教程铺天盖地,但要么只讲命令不解释为什么,要么一上来就扔出一堆分支模型把人绕晕。这篇东西不整虚的,就围绕“git本地基础操作”这…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉