你的框架是可替换的。你的架构不可替换。
Source: Dev.to
框架本质上是短暂的。
它们出现、获得关注、在讨论中占据主导地位数年,随后被取代或从根本上重新构建。这并不是前端开发的缺陷;而是工具、浏览器以及开发者期望快速创新的自然结果。
许多团队犯的错误不是选择了“错误”的框架,而是认为今天选定的框架明天仍会以同样的方式塑造他们的代码库。随时间应保持稳定的是应用程序的业务逻辑:规则、验证、工作流以及代表真实需求的约束。不幸的是,在许多前端项目中,这两个关注点紧密交织在一起。
Source: …
问题:框架耦合
在理论上,框架只是一种工具,业务规则在组件内部实现。起初,这种方式看起来很高效——所有东西都聚集在一起,认知负担低,进展迅速。然而,随着时间的推移,框架不再是可替换的细节,而是开始定义系统的核心工作方式。此时更换框架就不再是一次简单的 UI 调整,而是一次根本性的重写。
暴露出这种耦合的典型场景:
- 迁移到新版本的框架
- 引入第二套 UI(例如移动端或桌面端)
- 在不同环境中复用逻辑
- 以有意义的方式提升测试覆盖率
当业务逻辑与框架概念纠缠在一起时,提取业务逻辑就需要引入组件渲染、生命周期模拟以及大量的 mock。即使底层规则保持不变,细微的改动也会在 UI 层产生连锁反应。
指导原则:按变化速率分离
一种有用的架构原则是根据预期的变化速率来划分关注点:
- 框架 变化迅速。
- UI 范式 发展演变。
Guideline: UI 层负责协调,领域层负责决策。
- UI 层 – 处理用户交互和呈现。它收集输入,调用应用逻辑,并渲染结果。它 不应 包含验证规则、业务决策或副作用。
- 领域层 – 包含核心业务规则和工作流,独立于任何 UI 技术。
示例:UI 与领域代码
以 UI 为中心的实现(框架代码)
// UI component (framework code)
component CheckoutForm {
state email = ""
onSubmit() {
if (!email.includes("@")) {
showError("Invalid email")
return
}
const response = http.post("/checkout", { email })
if (response.ok) {
navigate("/success")
} else {
showError("Checkout failed")
}
}
render() { /* ... */ }
}
问题:
- 验证和结账工作流被锁定在组件生命周期、框架状态管理以及 UI 级别的错误处理中。
- 复用或测试该逻辑需要框架的存在。
重构后的分离(纯领域代码)
// Domain / application layer (plain code, no framework)
function validateEmail(email) {
if (!email) return { error: "Email is required" }
if (!email.includes("@")) return { error: "Invalid email" }
return { ok: true }
}
async function submitCheckout(email, httpClient) {
const validation = validateEmail(email)
if (validation.error) return validation
const response = await httpClient.post("/checkout", { email })
if (!response.ok) return { error: "Checkout failed" }
return { ok: true }
}
// UI component (framework code) using the domain layer
component CheckoutForm {
state email = ""
state error = null
state loading = false
async onSubmit() {
loading = true
error = null
const result = await submitCheckout(email, http)
loading = false
if (result.error) {
error = result.error
} else {
navigate("/success")
}
}
render() { /* ... */ }
}
好处:
- 领域函数可以从任何地方调用(UI、CLI、后台任务、测试)。
- 它们可以在不渲染组件的情况下进行测试。
- 即使 UI 框架更换,它们仍然可复用,使 UI 保持轻量且可替换。
为什么这超越可测试性的重要性
- 风险降低: 当框架演进时,降低迁移风险和重构成本。
- 持久性: 代码库拥有更长的可用寿命。
- 清晰度: 业务行为集中在一个位置,独立于 UI 机制,从而提升整体代码质量。
框架升级常被认为是渐进式的,但许多框架会显著改变其思维模型。因此,架构更多是关于风险管理,而非仅仅追求优雅。
实践反思
如果在五年后你被迫更换前端框架,请自问:
- 系统的哪些部分可以保持不变?
- 哪些部分需要从头重写?
如果答案是“几乎所有东西都需要重写”,那么该框架已经成为你的架构——这值得重新考虑。