React 推广了基于组件的 UI、声明式渲染与以状态驱动的视图——把团队从以页面为中心的代码方式,带向可复用的系统与模式。

React 不只是引入了一个新库——它改变了团队在说“前端架构”时实际指的东西。实操上,前端架构是一组能让 UI 代码库在规模上仍然可理解的决策:你如何把 UI 拆成部分,数据如何在这些部分之间流动,状态放在哪儿,如何处理副作用(比如获取数据),以及如何让结果在团队内可测试且一致。
组件思维是把每一块 UI 当成一个小而可复用的单元,它拥有自己的渲染,并能与其他单元组合以构建整页。
在 React 流行之前,很多项目是围绕页面和 DOM 操作组织的:“找到这个元素,改它的文本,切换这个类名”。React 把团队推向了一个不同的默认:
这些理念改变了日常工作。代码审查开始问“这个状态应该放在哪?”而不是“你用了哪个选择器?”设计师与工程师可以围绕共同的组件词汇达成一致,团队可以在不重写整页的前提下构建 UI 组件库。
即便团队后来迁移到其它框架,很多受 React 影响的习惯仍会保留:基于组件的架构、声明式渲染、可预测的数据流,以及偏好可复用的设计系统组件而非一次性页面代码。React 让这些模式变得司空见惯,并影响了更广泛的前端生态。
在 React 出现之前,许多团队围绕页面构建界面,而不是可复用的 UI 单元。常见做法是服务器渲染模板(PHP、Rails、Django、JSP 等)生成 HTML,然后用 jQuery 加上一些脚本增强交互性。
你会渲染一个页面,然后用脚本“激活”它:日期选择器、模态插件、表单校验、轮播——每个插件对标记有自己的期待和事件钩子。
代码常常看起来像:找到某个 DOM 节点、附加处理器、变更 DOM,然后希望别的东西别被破坏。随着 UI 变大,“真实来源”悄悄变成了 DOM 本身。
UI 行为很少集中在一个地方。它分散在:
单个小部件(比如结算摘要)可能部分在服务器构建、部分通过 AJAX 更新、部分由插件控制。
这种方法对小型增强有效,但会产生反复出现的问题:
像 Backbone、AngularJS、Ember 这样的框架试图用模型、视图和路由带来结构——这通常是巨大的改进。但许多团队仍混合多种模式,留下了对一种更简单、以可重复单元构建 UI 的方法的需求。
React 最重要的转变简单说明却在实践中异常强大:UI 是状态的函数。与其把 DOM 当作“真实来源”并手动保持同步,不如把数据当作真实来源,让 UI 成为其结果。
状态就是屏幕依赖的当前数据:菜单是否打开、表单中输入了什么、列表中有哪些项、哪个过滤器被选中。
当状态改变时,你不需要在页面中到处去更新 DOM 节点;你更新状态,UI 根据它重新渲染以匹配。
传统以 DOM 为先的代码常常出现分散的更新逻辑:
在 React 模型下,这些“更新”变成渲染输出中的条件。屏幕成为对给定状态下应该可见内容的可读描述。这样做的架构收益是:数据形状与 UI 结构对齐,使屏幕更容易推理、测试和演进。
function ShoppingList() {
const [items, setItems] = useState([]);
const [text, setText] = useState("");
const add = () => setItems([...items, text.trim()]).then(() => setText(""));
return (
<section>
<form onSubmit={(e) => { e.preventDefault(); add(); }}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button disabled={!text.trim()}>Add</button>
</form>
{items.length === 0 ? <p>No items yet.</p> : (
<ul>{items.map((x, i) => <li key={i}>{x}</li>)}</ul>
)}
</section>
);
}
注意空消息、按钮禁用状态和列表内容都来自于 items 和 text。这就是架构上的回报:数据形状与 UI 结构一致,使屏幕更易推理、测试与演化。
React 把“组件”变成默认的 UI 工作单元:一个小的、可复用的片段,将标记、行为和样式钩子封装在一个清晰接口后面。
不再需要把 HTML 模板、事件监听和 CSS 选择器散落在无关文件中;组件把这些移动部件聚拢在一起。这并不意味着一切都必须放在同一个文件,但意味着代码围绕“用户看到和做什么”来组织,而不是围绕 DOM API。
一个实用的组件通常包含:
重要的转变是:你不再以“更新这个 div”为中心,而是以“在它被禁用时渲染这个 Button”为思路。
当组件暴露少量 props(输入)和事件/回调(输出)时,就更容易在不破坏应用其他部分的情况下改变其内部实现。团队可以拥有特定组件或文件夹(例如“结算 UI”),并自信地改进它们。
封装也减少了意外耦合:更少的全局选择器、更少的跨文件副作用、更少“为什么这个点击处理器停止工作了?”的惊讶。
组件成为主要构建单元后,代码开始反映产品:
这样的映射使 UI 讨论更容易:设计师、产品经理和工程师可以谈论相同的“事物”。
组件思维促使许多代码库按功能或领域组织(例如 /checkout/components/CheckoutForm)并建立共享 UI 库(通常位于 /ui/Button)。当功能增长时,这种结构比仅按页面的文件夹更易扩展,也为后续的设计系统奠定了基础。
React 的渲染风格通常被称为“声明式”:就是说你描述某种情况下 UI 应该是什么样子,React 会负责让浏览器匹配它。
在旧的以 DOM 为先的方法里,你通常写出一步步的操作:
在声明式渲染中,你更倾向于表达最终结果:
如果用户已登录,显示他们的名字;否则显示“登录”按钮。
这种转变重要,因为它减少了大量的“UI 簿记”。你不需要不断跟踪哪些元素存在、哪些需要更新——你关注应用可能处于的状态。
JSX 本质上是把控制逻辑附近的 UI 结构写得更方便。与其把“模板文件”和“逻辑文件”分开并来回跳转,你可以把相关部分放在一起:类 HTML 的结构、条件、小的格式处理和事件处理器。
这种共置是 React 组件模型显得实用的一个重要原因。组件不只是 HTML 的一块或 JavaScript 的一捆,它还是一个 UI 行为单元。
一个常见顾虑是 JSX 混合了 HTML 和 JavaScript,听起来像是倒退。但 JSX 并非真正的 HTML——它是会生成 JavaScript 调用的语法。更重要的是,React 并不是把不同技术混到一起,而是把“会一起变化的东西”放在一起。
当逻辑与 UI 结构紧密关联时(例如“仅在校验失败时显示错误信息”),把它们放在一起通常比散落在多个文件中更清晰。
JSX 让 React 更易上手,但底层概念超越了 JSX。你可以不用 JSX 写 React,其他框架也用不同的模板语法实现声明式渲染。
持久影响是这种心态:把 UI 当作状态的函数,让框架处理保持屏幕同步的细节。
在 React 之前,常见的 bug 来源很简单:数据变了,但 UI 没有变。开发者会拿到新数据,然后去手动找到对应的 DOM 节点、更新文本、切换类名、增删元素,并在各种边缘情况下保持一致。随着时间推进,“更新逻辑”往往比 UI 本身更复杂。
React 的大工作流程转变是:你不再“指示”浏览器如何一步步改变页面,而是描述在某一状态下 UI 应该是什么样子,React 来决定如何更新真实 DOM 以匹配它。
调和是 React 比较你上一次渲染和这次渲染,然后把最小集合的改变应用到浏览器 DOM 的过程。
重要的不是 React 使用“虚拟 DOM”作为神奇的性能手段,而是 React 给出一个可预测的模型:
这种可预测性改进了开发者的工作流:更少的手工 DOM 更新、更少的不一致状态、并且 UI 更新在整个应用中遵循相同规则。
在渲染列表时,React 需要一种稳定的方式在调和期间把“旧项”与“新项”匹配起来。这就是 key 的用途。
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
使用稳定且唯一的 key(比如 ID)。当项可能被重排序、插入或删除时,避免使用数组索引,否则 React 可能重用错误的组件实例,导致意外的 UI 行为(例如输入框保留了错误的值)。
React 最大的架构转变之一是数据单向流动:从父组件流向子组件。相较于让任意 UI 部分“伸手去”修改其他部分并共享状态,React 鼓励把更新视为向上触发的显式事件,而结果数据再向下流动。
function Parent() {
const [count, setCount] = React.useState(0);
return (
<Counter
value={count}
onIncrement={() => setCount(c => c + 1)}
/>
);
}
function Counter({ value, onIncrement }) {
return (
<button onClick={onIncrement}>
Clicks: {value}
</button>
);
}
注意没有发生的事:Counter 并不会直接修改 count。它接收 value(数据)和 onIncrement(请求变更的方式)。这种分离是心智模型的核心。
这个模式让边界变得明显:“谁拥有这份数据?”通常可以回答为“最近的共同父组件”。当某件事意外改变时,你只需到状态所在之处去追踪,而不是穿越一张隐藏突变的网。
这种区分帮助团队决定逻辑归属并防止意外耦合。
依赖 props 的组件更容易复用,因为它们不依赖全局变量或 DOM 查询。它们也更容易测试:可以用特定的 props 渲染并断言输出,而有状态的行为在状态管理所在的地方测试。
React 把团队从“为 UI 建类继承体系”推动到通过组合小而专注的片段来组装屏幕。与其把基类 Button 扩展出十个变体,更常见的是通过组合组件来构建行为与视觉。
一个常见模式是构建不关心数据的布局组件:
PageShell 用于头部/侧栏/页脚Stack / Grid 用于间距与对齐Card 用于一致的框架这些组件接受 children,使页面决定内部内容,而非布局决定。你也会看到轻量级的包装器如 RequireAuth 或 ErrorBoundary,它们在不改变被包装组件内部的情况下为其添加关注点。
当需要比 children 更多控制时,团队通常通过 props 提供“插槽式”方案:
Modal 有 title、footer 和 childrenTable 有 renderRow 或 emptyState这让组件灵活同时不会让 API 爆炸。
深层继承树通常出于良好初衷(“我们会复用基类”),但难以维护因为:
Hooks 使得组合更实用。像 useDebouncedValue 或 usePermissions 这样的自定义 Hook,让多个功能组件共享逻辑而不共享 UI。配合共享的 UI 原语(按钮、输入、排版)和功能组件(CheckoutSummary、InviteUserForm),你能在应用增长时保持可理解的复用。
React 让从本地组件状态开始变得自然:表单字段值、下拉是否打开、加载指示器。这很好——直到应用变大,多处 UI 需要保持同步。
随着功能扩展,状态经常需要被并非直接父子关系的组件读取或更新。“就传 props 吧”会变成长长的 props 链,通过那些其实不关心数据的组件,增加重构的风险和样板代码,并可能引发两个地方不一致地表示“相同”状态的混乱 bug。
将状态移动到最近的共同父组件并向下传递 props。这通常是最简单的选项并保持依赖显式,但过度使用会产生“上帝组件”。
React Context 在很多组件需要相同值(主题、语言、当前用户)时很有用,减少了 props 的层层传递。但如果把频繁变化的数据放在 context 中,会让更新与性能更难推理。
随着 React 应用变大,生态出现了像 Redux 这样的库来集中化状态更新,通常配合 actions 与 selectors 的约定,这在大规模项目中能提高可预测性。
默认优先本地状态;当兄弟组件需要协调时提升状态;对于跨切关注点使用 context;当很多分散组件依赖相同数据并且团队需要更清晰的更新规则时考虑外部存储。正确的选择更多取决于应用复杂度、团队规模和需求变化频率,而非潮流。
React 不仅引入了一种新的写 UI 的方式,它推动团队走向以组件为驱动的工作流:代码、样式与行为作为小而可测试的单元开发。这一转变影响了前端项目的构建、验证、文档化和发布方式。
当 UI 由组件组成时,自然会“从边缘向内构建”:先做按钮,再做表单,然后做页面。团队开始把组件当作产品来看待:清晰的 API(props)、可预测的状态(加载、空、错误)、可复用的样式规则。
一个实用的变化是:设计师和开发者可以围绕共享的组件清单对齐,在隔离环境里审查行为,减少在页面层面出现的临时问题。
React 的流行帮助标准化了许多团队现在认为是基本的现代工具链:
即便你不选择完全相同的工具,期望仍然存在:一个 React 应用应当有防线来尽早捕捉 UI 回归。
作为“以工作流为先”心态的延伸,一些团队也使用类似 Koder.ai 的平台从聊天驱动的规划流程中搭建 React 前端(及围绕它的后端)——当你想在投入数周手工搭建之前,快速验证组件结构、状态所有权和功能边界时,这类工具很有用。
React 团队普及了组件探索器的概念:一个专门的环境,用来在不同状态下渲染组件、附加说明并共享一致的使用指南。
这种“Storybook 风格”的思维改变了协作:你可以在组件连接到页面之前审查其行为,刻意验证边缘情况,而不是指望它们在手动 QA 中被发现。
如果你要构建可复用库,这与设计系统方法天然契合——参见 /blog/design-systems-basics。
基于组件的工具鼓励更小的 PR、更清晰的视觉审查和更安全的重构。随着时间推移,团队更快地发布 UI 改动,因为他们在小而明确的片段上迭代,而不是在纠结缠绕的页面级 DOM 代码中摸索。
实操意义上的设计系统由两部分组成:一组可复用的 UI 组件(按钮、表单、模态、导航)和解释如何及何时使用它们的指南(间距、排版、语气、可访问性规则、交互模式)。
React 让这种方法自然可行,因为“组件”已经是 UI 的核心单元。团队可以把 <Button />、<TextField /> 或 <Dialog /> 发布一次并在各处复用——同时通过 props 提供受控定制。
React 组件是自包含的:它们可以把结构、行为和样式封装在稳定接口后面。这使得构建一个组件库变得容易,使其变得:
如果从零开始,一个简单的清单能防止“组件堆”变成一团不一致的乱象:/blog/component-library-checklist。
设计系统不仅仅是视觉一致——还是行为一致。当模态总是正确地陷入焦点或下拉菜单总是支持键盘导航时,可访问性从后期补救变为默认。
主题化也更简单:可以集中 tokens(颜色、间距、排版),让组件消费这些 token,从而品牌变更不需要触及每个屏幕。
对是否值得投入共享组件的评估,往往与规模和维护成本有关;一些组织把这类评估和平台计划(如 /pricing)关联起来。
React 不仅改变了我们如何构建 UI——还改变了我们如何评估质量。一旦应用由带有清晰输入(props)和输出(渲染 UI)的组件组成,测试与性能就变成了架构决策,而不是临时修补。
组件边界让你在两个有用层面测试:
这在组件有明确所有权时效果最好:一个地方拥有状态,子组件主要展示数据并发出事件。
React 应用之所以常常感觉流畅,是因为团队把性能当作结构性问题来规划:
一个有用的规则是:优化“昂贵”的部分——大列表、复杂计算和频繁重渲染区域——而不是追逐微小收益。
随着时间推移,团队可能会陷入常见问题:过度组件化(太多微小但目的不明的组件)、prop drilling(数据通过许多不关心它的层传递)以及没有人明确知道哪个组件“拥有”某块状态的模糊边界。
当你以快速方式(尤其是自动生成或脚手架化代码)推进时,这些陷阱会更快显现:组件膨胀、所有权变得模糊。无论是手工编码还是使用像 Koder.ai 这样的工具生成 React 前端及后端(例如 Go + PostgreSQL),防护准则相同:明确状态所有权,保持组件 API 简洁,并向清晰的功能边界重构。
服务端组件、元框架和更好的工具会持续演进 React 应用的交付方式。但持久的教训不变:围绕状态、所有权和可组合的 UI 构建块来设计,然后让测试与性能自然而然跟随。
有关更深入的结构决策,请参见 /blog/state-management-react。
React 将前端架构重新 framing 为几个核心决策:
实际效果是减少手动对 DOM 的维护工作,并为团队与工具创建更清晰的边界。
组件思维意味着把每个 UI 单元当作一个小而可复用的模块,它负责自己的渲染并能组合成更大的界面。实际上,一个组件通常包含:
这会把工作从“去更新某个 DOM 节点”转换为“在某种状态下渲染这个组件”。
在以 DOM 为中心的代码中,DOM 常常被当作真实来源,于是你需要手动让多个元素保持同步。React 中你更新状态并以状态为依据渲染,因此像加载指示、禁用按钮、空状态等条件会自然保持一致。
一个简单的判断:如果你在写很多“查找元素并切换类名”的步骤,说明你在对抗模型;如果 UI 脱离了状态,通常是状态所有权的问题。
在 React 出现前,很多应用以页面为中心:服务器渲染模板加上 jQuery 和各种插件。行为分散在服务器视图、HTML 属性和 JS 初始化代码之间。
常见问题包括:
React 推动团队转向可复用组件和可预测的更新机制,从而缓解这些痛点。
声明式渲染是指描述给定状态下 UI 应该是什么样子,而不是一步步写出如何去修改 DOM。
不再是:
而是:在渲染输出中表达条件(例如“如果已登录则显示姓名,否则显示登录按钮”),由框架去处理如何把实际 DOM 与期望匹配。
这减少了大量的“UI 记账”工作,让你把注意力放在应用可能的状态上。
JSX 使得将 UI 结构与控制该结构的逻辑(条件、格式、事件处理)放在同一处变得方便。与其在模板和逻辑文件间来回切换,JSX 让相关内容共置在组件里:这就是组织上的好处。
JSX 本质上并不是 HTML,而是生成 JavaScript 的语法。关键在于把“会一起变化的东西”分组在一起(UI + 行为),这通常比把规则散落在多个文件里更易维护。
调和(reconciliation)是 React 比较上一次渲染和本次渲染,然后对真实 DOM 应用最小变更集的过程。
重要的并不是“虚拟 DOM”作为魔法性能技巧,而是 React 提供了一个可预测的模型:
关于列表渲染的实际建议:React 需要稳定的方法把“旧项目”与“新项目”匹配起来,这就是 key 的作用。
单向数据流意味着数据从父组件流向子组件(通过 props),而子组件通过回调来请求变更。
这让边界更清晰:谁拥有数据通常由“最近的共同父组件”决定。出问题时,你会去找状态所在的位置,而不是去追踪一堆隐藏的突变。
概念区分:
这种分离有助于决定逻辑放在哪里,并避免意外耦合。
组合(composition)意味着通过把小而专注的组件组合在一起来构建界面,而不是使用类继承的层级结构。
常见实践:
随着应用增长,通常会经历这样的状态管理演进:
选择应基于应用复杂度、团队规模和需求变化频率,而不是跟风。通常优先使用本地状态,只有在明确需要时再引入更复杂的共享方案。
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
使用稳定且唯一的 key(如 ID)。当项目会被重排序、插入或删除时,避免用数组索引,否则 React 可能重用错误的组件实例,导致输入保持了错误的值等奇怪行为。
children 的布局组件(如 PageShell、Stack、Grid、Card)RequireAuth 或 ErrorBoundary 这样的包装器来为被包裹内容添加关注点children 不够用时,用插槽式 props(如 title、footer、renderRow、emptyState)保持灵活继承常常带来问题:行为分散在多个层级、基类的改动会波及无关屏幕、覆盖层层叠加导致基类变得混乱。
Hooks(自定义 Hook)让共享逻辑变得更容易,例如 useDebouncedValue 或 usePermissions,它们可以在不共享 UI 的情况下复用逻辑。