为什么在学过 React 之后前端开发依然困难:状态、效应、边界条件与 AI 工具
React让UI的开发变得更简单,但并未让设计变得更轻松。真正的前端工作在于状态管理、复杂的交互效果、系统边界以及AI生成的代码。
有些前端开发人员能在20分钟内搭建出一个React组件,随后却要花3个小时去弄清楚为何这个组件后来变得难以维护。问题并不出在JSX上,也不是useState,更不是React本身。让前端工作变得艰难的,其实是框架之外的诸多因素:需要明确产品应有的行为方式、决定状态应存储在哪里、协调异步调用、界定组件的边界、应对浏览器的各种特性、确保界面既易用又快速,还要写出半年后依然易于理解的代码。
React确实让界面开发变得更简单了,但它从未让前端工程变得轻松,而这个领域也花了太长时间误以为它做到了。
React解决了什么问题,又忽略了什么
在 React 出现之前,面临的难题是另一类的。开发者需要手动编辑 DOM 并逐一连接事件处理程序,同时还要努力让界面与应用程序数据保持同步。随着应用程序的规模扩大,要理清用户操作、数据与渲染元素之间的关联变得越来越困难。
React 提供了一种实用的思想模型:用户界面是状态的函数。无需为每次 DOM 变更都向浏览器发出指令,只需描述在特定状态下界面的样子即可。组件负责界定边界,属性提供明确的输入,状态则表示会随时间变化的数据,而声明式渲染则消除了许多不必要的复杂性。这确实是一次重大的进步。
不过,框架只能解决它被设计来解决的那些问题。React 对于一个应用需要五个全局状态还是五十个全局状态并无意见。它不会判断某个组件是否承担了过多功能,不会评价你的 API 设计是否合理,不会判断加载指示器是否放置得当,也不会判断结账流程对消费者来说是否合理。当然,它更无法决定在请求过程中网络断开时产品应该如何处理——这些都是前端问题。
普通功能面对现实
以一个简单的控制面板为例。用户打开它,应用获取数据,用户修改筛选条件后结果会刷新,用户选中某个项目后会出现模态框,用户编辑并保存后,服务器会响应并更新控制面板。这些步骤听起来并没有什么难度。
现在来描述实际发生的情况。第一个请求会失败,因为在之前的请求仍在处理中,过滤器就已经发生了变化,结果旧的响应在新的响应之后才送达。模态框是通过深度链接打开的,页面在编辑过程中被刷新,同时打开了两个标签页,会话也到期了。API返回了部分数据,服务器虽然接受了数据变更,但客户端却从未收到回复,而且用户还点击了两次保存按钮。列表中有10,000行数据,网络连接缓慢且为移动网络,界面必须通过键盘操作,同时错误信息还需要告知用户下一步该做什么。
在那种情况下,React代码反而是最不重要的部分。所有这些功能都可以用React来实现,但实现方式也可能很糟糕。许多开发者没有意识到这种差异。
任务在于管理复杂性
前端开发人员的工作并非仅仅是编写组件,更重要的是要处理各种复杂性,以便用户看到简洁的界面,而这其实要困难得多。一个设计良好的组件可能只有五十行代码,但要控制代码量在五十行而非五百行,需要做出一系列决策:
- 数据来自何处、由谁负责管理、数据的有效期以及谁有权修改它
- 请求失败时该如何处理
- 状态是否需要在页面跳转或刷新后依然保留
- 该数据应存储在URL中、组件本地状态中还是服务器缓存中
- 两个组件是应该共享数据本身,还是各自独立计算相似的值
React 并无法解决这些问题。开发者必须自行解决,而且这些解决方案会不断累积。一个糟糕的决策很少只停留在某个组件内部,它会演变成一种模式,其他开发者也会模仿它,很快整个应用就会形成一种没人当初选定的统一架构。这就是前端代码库变得难以维护的原因:决策不断积累,而 React 的复杂性其实与此关系不大。
状态作为建模问题
判断前端设计是否不成熟的一个快速方法就是查看其状态管理方式。大量使用 useState 本身并无错处。问题在于将状态视为存储数值的场所,而非需要建模的对象。开发者看到数值发生变化后,就会立刻使用相应的钩子函数。
const [value, setValue] = useState(...)
它看起来已经完成了,但如何存储该值并非真正的问题。真正的问题在于这个值是否应该存在。看看这组元素:
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [fullName, setFullName] = useState("");
目前有三个状态存储着本可从两个状态中推导出的信息。这并非 React 的错,而是思维模型的问题。
在更大规模的情况下也会出现同样的情况。团队们会将 API 响应、经过过滤的响应副本、加载标志、计算出的数值、选中的记录、模态框标志,以及同一实体的多个副本分散存储在多个地方。久而久之,就没人能确定哪个副本是权威的,于是错误便随之产生——一个组件修改了某个副本,而另一个组件却读取了不同的副本。同步机制试图让它们保持一致,而其他机制又会对此作出反应。突然之间,浏览器里就出现了一个小型分布式系统。构建这种架构的不是 React,而是编写代码的人。
效应暴露了设计缺陷
如果要说哪个 React 特性承担了最多的指责,那非 useEffect 莫属,而这些批评往往也有道理。但实际上,架构问题通常是在效应中显现出来,而非由此产生。
想象一个包含多个效应的组件:一个用于获取数据,一个用于同步状态,一个用于响应属性变化,一个用于写入本地存储,一个用于发送分析数据,还有一个在条件改变时重置某些内容。单独看每个效应似乎都很合理,但合在一起就会形成一张难以理解的依赖关系图。开发者会不断添加依赖项,直到代码检查工具不再报错;或者因为效应触发过于频繁而删除某些依赖;又或者添加一个布尔值引用来避免重复执行。最终,该组件在测试场景下能够正常工作,但没人能确切解释其中原因。
这并非 React 的缺陷,而是职责划分不明确以及副作用相互纠缠,而 React 只是为这些问题提供了存在的空间而已。
浏览器只是更大系统中的一个节点
前端的工作范畴不断扩展。现代应用程序需要与API和数据库、认证系统及分析工具交互,还要使用功能开关、支付服务提供商以及其他第三方服务。此外还有边缘函数、服务器组件、后台任务,以及越来越多的人工智能支持服务。浏览器仅仅是这个庞大系统中的一个参与者,因此前端开发者不仅要掌握渲染技术,还需了解请求在离开页面之前和之后所发生的一切。
这并不意味着每个人都必须成为后端专家。它只是说明传统的前端/后端分工正逐渐失去意义。不懂HTTP的人最终会在缓存问题上遇到困难,不熟悉数据库的人难以理解数据一致性,而不了解认证机制的人则会在会话处理上犯错。对于没有分布式系统背景的开发者来说,竞态条件和重试机制会让他们感到困惑;而那些不理解用户需求的人则只会打造出没人愿意使用的精美界面。框架其实只有一层结构。
AI改变的是成本结构,而非技术难点
AI编码助手改变了前端工作的成本结构。组件、表单、设计转JSX、钩子函数以及测试的生成成本都降低了。这听起来可能像是一种威胁,但实际上应视为对工作价值所在点的检验。如果你的主要贡献只是快速输入JSX代码,那么AI就会带来问题;但如果你擅长理解系统、架构、产品行为、各种权衡因素以及用户需求,AI反而会优化你的工作流程,让那些困难的部分依然存在,甚至可能提升其重要性。
助手可以在几秒钟内为某个功能草拟出三个合理的版本。在这些版本中做选择属于架构层面的决策,而非语法层面的问题。它能够设计状态管理方案,但该状态是否需要是全局的?它可以编写乐观更新逻辑,但应用真的需要以乐观的方式运行吗?它可以添加缓存机制,但失效策略又该如何制定?它可以编写效应函数,但这个效应函数真的有必要存在吗?代码出现得越快,就越需要审慎判断,而这很可能会决定未来几年前端开发的方向。
优秀的前端开发者往往较少考虑 React 本身,而更多关注系统的边界。他们会思考责任应归属何处,留意某个组件何时已演变成一个小应用。他们会解决状态重复的问题,在正常流程之外提前规划故障处理方案,清楚 API 实际能提供什么保障,同时关注用户可能出现的意外行为。他们能够判断抽象层何时真正有帮助,何时只是在掩盖复杂性。他们了解浏览器的工作原理,能够优化性能而不至于过早地过度优化一切,最重要的是,他们能够解释系统为何会以当前方式运行。这种技能远比掌握最新的 React API 更难自动化,其价值也远超短期知识。
框架知识会过时
开发者们往往忽视这一点:框架知识也有其有效期。五年前学的 React API 已不能完全适用于如今的环境。最佳实践在变化,库在消失,构建工具在更新,渲染策略在发展,服务器功能在提升,新的框架也在不断出现。语法在变化,但那些底层的工程问题却始终基本稳定:
- 状态建模以及数据在系统中的传递
- 错误处理与异步任务的管控
- 避免不必要的耦合
- 让系统可观测、接口可访问
- 控制系统的复杂性
这些问题在 jQuery、Angular 和 Backbone 时代依然存在,也在早期的 React 生态系统中留存了下来。无论未来出现什么,它们都会继续存在。因此,仅通过框架来学习前端是一种不稳妥的做法。要学习框架,但不要将其误认为是整个领域的核心知识。
React有朝一日可能不再成为众人关注的焦点。或许会有其他技术取而代之,也有可能多种技术共存,又或者它仍会保持主导地位超过预期时间。但这些其实并不重要。那些将自身价值建立在React API之上的开发者将不得不重新构建技术体系,而那些真正理解界面运作原理的人则能够顺利适应变化。
React只是工具,而非核心技能
前端开发从本质上讲从来都与React无关。React确实是表达前端应用的一种非常出色的工具,但它终究只是工具而已。真正的难点在于决定要表达什么:各个组件之间如何交互、出现故障时该如何处理、状态应存储在何处、哪些代码在浏览器中运行、哪些在服务器上运行、系统如何随着产品的发展而扩展,以及如何向使用者隐藏所有这些复杂细节。这才是核心任务,而JSX只不过是实现其中部分功能时的编码方式罢了。
随着工具在生成代码方面的能力不断提升,编写代码的价值也在下降。但这并不会终结软件开发,只是改变了开发者需要关注的重点。能够快速生成组件的人固然有用,但能判断该组件是否有必要存在的人更为重要。API集成也是如此:生成API固然有用,但发现其接口定义存在错误的价值更高。能够让AI协助重构代码库的开发者很有价值,而能判断所提出的重构方案是否真正能提升系统性能的开发者则更具价值。
因此,不必为React变得更简单而担忧。这正是它的目标,因为框架的存在就是为了减少不必要的工作,经验丰富的开发者会将节省下来的时间用于处理真正重要的问题。
相关阅读
- 在LangChain中构建ReAct智能体:执行器、临时笔记板及其局限性 — 构建一个会使用工具的ReAct智能体,了解智能体与AgentExecutor如何分工协作,同时理解为何LangGraph在处理控制流、循环和状态方面更出色。
- 当React记忆化机制适得其反:为何减少渲染反而让应用变慢 — 探讨memo、useMemo和useCallback如何在大型React页面中增加成本并引发错误,以及为何将状态移出组件并分析整个交互流程能够解决这些问题。