当 Frontend 开始和自己的 Framework 对抗
为什么命令式 OOP Thinking 在 Backend 中往往很好用,却会在现代 Frontend 中逐渐和 Angular、React、Vue 的运行模型对着干。

项目使用 Angular。
至少 package.json 里是这么写的。
表面上该有的都有:Component、Template、Service、Routing、Form、HTTP Call。技术上看,是一个现代 Frontend。
但从架构上看,完全不是同一回事。
Routing 并没有真正按照 Angular Router 的模型来设计,而是被一个自建 Routing System 补充甚至替代。Validation 也没有通过 Forms、Validator 或清晰的 ViewState Model 表达,而是另建一套验证逻辑。Change Detection 没有真正被理解,而是靠自己的 UI Synchronization Mechanism 绕过去。State Management 也不是围绕 State、Event 与 Derivation 来建模,而是通过自建 Cache Class 来实现。
这听起来很尖锐。
但我不是在指责开发者。
每一个局部决定,当时大概率都有理由。Team 想解决问题,想获得控制,想把 Feature 交付出去。没人早上起床时会想:“今天我要在 Angular 里面再造一个 Framework。”
也正因为如此,这种模式才危险。
它并不来自愚蠢。
它往往来自在另一个 Context 中非常成熟的能力。
很多优秀 Developer 多年来接受的是 OOP、Service、Method、Transaction 与线性 Flow 的训练。来自大学、经典软件工程、Backend Project 和 Enterprise System。在这些环境中,一个 Request 开始、被处理、然后结束。
这种思维本身没有错。
它只是属于另一个 Home Port。
为什么 Backend Thinking 很自然
Section titled “为什么 Backend Thinking 很自然”典型 Backend Flow 经常是:
Request 进来。 Validation 执行。 加载 Data。 检查 Rule。 保存 Data。 返回 Response。
这非常适合命令式思维。
核心问题是:
下一步执行什么?
也适合经典 OOP Thinking。
核心问题是:
哪个 Object 负责?应该调用哪个 Method?
在很多 Backend Context 中,这完全合理。Use Case 编排一段流程,Service 封装业务 Logic,Repository 加载 Data,Transaction 限定边界,最后得到一个 Result。
流程有开始。
也有结束。
中间有明确顺序。
Frontend 一开始看起来也类似:用户点击,执行 Method,调用 Service,Data 返回,UI 更新。
于是很自然地把熟悉的模型搬过来。
问题是:UI 不是一个单独 Request。
Backend 经常处理一个过程;Frontend 则要让一个持续存在的情境保持一致。
思维就在这里开始发生偏移。
什么叫命令式
Section titled “什么叫命令式”命令式意味着:Code 明确描述哪些步骤应该按什么顺序执行。
例如:
async save(): Promise<void> { this.loading = true; this.error = null;
try { const result = await this.api.save(this.formValue); this.items = await this.api.loadItems(); this.toast.success('Gespeichert'); this.router.navigate(['/items', result.id]); } catch (error) { this.error = 'Speichern fehlgeschlagen'; this.toast.error('Speichern fehlgeschlagen'); } finally { this.loading = false; }}这很容易理解。
线性。 看起来可控。 Happy Path 下有效。 只要同时发生的事情还不多,也很好 Debug。
Click → Save → Success → Toast → Navigate。
对来自 Backend、OOP 或经典 Application Development 的 Developer 来说,这种写法很熟悉。
需要明确的是:命令式 Code 并不天然糟糕。
Frontend 中当然存在命令式位置。Button Click 是 Event,Navigation 是 Effect,HTTP Request 需要被触发,Dialog 需要打开。
问题并不从“出现了一段命令式代码”开始。
而是从整个 UI 都被当作一条需要手工控制的步骤链开始。
那时每一种状态都会变成 Flag,每一个特殊情况都会变成 Sequence,每一次 UI Reaction 都会变成 Method Call,每一次不一致都会再长出一个 Helper。
最后,Screen 不再由一个清晰 Model 描述。
它变成了 Flow Coordination。
经典 OOP Thinking 在 Frontend 中会发生什么
Section titled “经典 OOP Thinking 在 Frontend 中会发生什么”OOP 往往围绕 Object、Responsibility 与 Method 来思考。
Object 封装 Data 与 Behavior。 Service 执行 Action。 Controller 编排 Flow。 Use Case 描述 Process。
在 Backend 中这非常有帮助。很多流程都有明确边界:一个 Request、一条 Message、一个 Job 或一次 Transaction。可以划分 Responsibility、注入 Dependency、封装 Behavior。
但搬到 Frontend 后,这种思维很容易落在错误的位置。
Component 会逐渐变成小 Controller:加载 Data,管理 Cache State,做 Validation,决定 Visibility,同步 Template 与 Model,响应 Route Change,维护 Loading State,显示 Toast,执行 Navigation,处理 Error,触发 Reload。
最后,它已经不只是 Component。
它同时是 Controller、Process Manager、Cache Manager、Validator 与 Render Coordinator。
Angular Component 不是一个挂着 Template 的小型 Backend Controller。
React Component 也不是带 JSX 的小 Controller。
Vue Component 也不是带 Template Syntax 的 Flow Script。
现代 Frontend Component 属于一个响应式 Runtime Model。
这个模型必须被认真对待。
什么叫响应式
Section titled “什么叫响应式”“响应式”听起来经常比实际需要的更宏大。
不需要把它变成学术哲学讨论。
对日常开发而言,先抓住一个简单思想就够了:
响应式并不是每一步都由代码手工推动,而是显式建模 State、Event 与 Derivation。
不是:
现在显示 Spinner。
而是:
Status 是
loading,所以 UI 显示 Spinner。
不是:
隐藏 Error、重新 Render List,然后检查 Empty State。
而是:
Error、List、Empty State 或 Loading State 是否可见,由 State 与 ViewModel 推导出来。
也不是:
这个事情发生后,Set 一个 Flag、清空一个 Array、调用 Reload,然后再手工 Trigger UI。
而是:
一个 Intent 改变状态,下一次 Projection 从这个状态中产生。
响应式首先问的不是:
我现在要执行什么?
而是:
当前是什么情境——从这个情境中应该得到什么?
更短一点:
命令式问:下一步发生什么?响应式问:从当前状态能推导出什么?
这是一个很大的思维转变。
不是因为“响应式”听起来更现代。
而是因为 UI 会长期处于运行中:用户继续交互,Request 在后台执行,Route 改变,Component 创建与销毁,Data 延迟返回,Validation 随输入变化。
UI 更像一个持续存在的 Projection。
而不是一次结束的 Flow。
UI 不是步骤链。UI 是 Projection。
命令式、OOP 与响应式
Section titled “命令式、OOP 与响应式”
可以粗略这样区分:
| 思维模型 | 核心思想 | 常见问题 | 优势 | Frontend 中的风险 |
|---|---|---|---|---|
| 命令式 | Flow、Sequence、Method Call | 下一步做什么? | 顺序清楚 | 手工同步 UI |
| OOP | Object、Method、Responsibility、Encapsulation | 谁负责? | 职责与封装 | Component 变成 Controller |
| 响应式 | State、Event、Derivation、Projection | 从状态中得到什么? | UI 与 State 保持一致 | 初期思维不习惯 |
这些模型没有哪个天然错误。
真正的问题是:当前运行在哪一种 Runtime Model 里?
Backend 中,线性流程经常很自然。
Frontend 中,线性流程通常只是 Happy Path。
真实 UI Complexity 一旦出现,多种 State 会开始重叠:
用户重复点击。 Request 并行。 Route 改变。 Component 创建与销毁。 Validation 在输入过程中变化。 Permission 生效。 Loading、Error、Empty 与 Success State 彼此交叠。 Browser History 与 Deep Link 存在。 Rendering 会反复发生。
因此,现代 Screen 更像持续运行的 State Machine。
而不是简单 Flowchart。
为什么短期内它看起来非常好用
Section titled “为什么短期内它看起来非常好用”命令式 Frontend Code 在早期通常出奇地有效。
Happy Path 很线性:
Click → Save → Success → Toast → Navigate。
写得快。 容易理解。 符合成熟 OOP Reflex。 给人一种很强的控制感。
项目早期尤其如此。Feature 很快出现,UI 会响应,Customer 可以点击。
哪里不对,再加一个 Flag。
isLoading。
isSaving。
hasError。
showEmptyState。
isInitialized。
reloadRequired。
formWasTouched。
manualRefreshNeeded。
一开始也都能工作。
直到多个事情同时发生。
真正的 Complexity 从这时开始:哪些 Flag 互斥?哪个 Request 可以覆盖哪个 State?用户 Save 过程中离开 Route 会怎样?谁 Reset Error?Cache 什么时候 Stale?Spinner 为什么不消失?List 为什么没更新?Toast 为什么出现两次?
原来的控制感变成了 Coordination。
而 Coordination 很贵。
为什么长期看它会和 Framework 对抗
Section titled “为什么长期看它会和 Framework 对抗”Angular、React 与 Vue 的 API、Philosophy 和 Ecosystem 有明显差异。
但它们共享一个核心思想:
Rendering 从 State 产生。
React 通过 State 与 Props 思考 UI;State 变化后产生新的 Render。Vue 使用 Reactive Data、Computed Value 与 Template Binding。Angular 也越来越围绕 Signals、Computed Value、Template Binding、Router State、Forms State、Resource、Store 与清晰的 Change Model 来表达 UI。
API 不同。
Paradigm 却很接近:
UI 是 State 的 Projection。
这些 Framework 并不希望你持续手工同步 Screen。
它们希望你建模 State、表达 Dependency、描述 Derivation。
如果把所有事情都自己协调,就会逐渐绕开 Framework。
常见表现是:
Router 不再被理解为 Navigation Model,而被当成一个需要绕开的麻烦机制。 Forms 不再被理解为 State Model,而只是输入框加上一套自己的 Error Map。 Change Detection 不再被理解为 Rendering Model,而变成需要手工“戳一下”的东西。 State Management 也不再是 Event 与 State 的 Projection,而是一些 Global Cache Class。
于是 Angular、React 或 Vue 最后只剩 Render Layer。
真正的 Runtime Model 在自己的代码里。
Framework 里的“反 Framework”
Section titled “Framework 里的“反 Framework””
危险的往往不是第一个 Helper。
Helper 没什么。 一个 Workaround 可以合理。 一个小型自定义 Abstraction 甚至非常有价值。
但 Workaround 会有自己的职业生涯。
今天解决一个局部问题。 明天它变成 Internal Pattern。 后天第二个 Team 开始依赖它。 然后出现 Convention。 接着需要 Documentation。 最后只剩一个人真正理解 Special Case。 再后来 Framework Upgrade 变难,因为没人知道哪些 Responsibility 还属于 Framework,哪些已经被 Eigenlösung 接管。
如果 Framework Router 被自己的 Router 替代,Forms 与 Validation 被自己的 Validation 替代,Change Detection 被自己的 Render Synchronization 替代,State Management 被 Cache Class 替代,那么你已经不再把 Angular 当作 Architecture Model 使用。
Angular 只是自建 Framework 外面的一层 Render Engine。
Angular 不是问题。真正的问题,是自己的代码不再允许 Angular 按 Angular 的方式工作。
这才是核心。
Framework Concept 当然不是完美的。不是每个 API 都漂亮,也不是每项 Recommendation 都适合每个项目。
但只要替代 Framework Core Concept,就同时接管了它的全部 Responsibility。
而这个 Responsibility 往往比第一眼看起来大得多。
自建 Router
Section titled “自建 Router”Routing 不只是:
哪个页面现在可见?
Routing 还包含 Deep Link、Browser History、Guard、Lazy Loading、Redirect、Parameter、Reload Behavior、Back Button、Navigation State、Error Case,以及经常还包括 Permission。
自建 Router 往往只接管了其中一部分。
第一次点击没问题。 Happy Path 也没问题。
但到了 Reload、Browser History、Nested Route、Guard 或 Deep Link 时,才会发现 Routing 本身就是 Runtime Model。
自建 Router 很少只是一个 Router。它经常是另一个 Runtime Model 的起点。
当然,也存在合理的 Routing Abstraction,例如封装业务 Navigation 或保持 URL Stability。
但好的 Abstraction 应该尊重 Router,而不是替代它。
自建 Validation
Section titled “自建 Validation”Validation 也不只是:
字段空不空?
它连接 User Input、Dirty / Touched State、Submit Capability、Backend Error、Translation、Accessibility 与展示。
一旦自己做的 Validation Logic 与 Form State 并存,就很容易产生 Double Truth。
Form 说 Valid。 Error Map 说 Invalid。 Template 说也许。 Submit Button 按自己的 Logic 决定。 Backend 再给出第五种判断。
然后开始 Synchronization。
而 Synchronization 经常意味着 Model 切错了。
Validation 不应该只是某个 Helper Class 随机地把 String 映射到 Field。
它需要有意识的 Model:什么来自 User Input?什么来自 Backend?什么属于展示?什么是业务 Rule?什么只是 Technical Format?
只有这样,Validation 才真正可测试、可理解。
自建 Change Detection
Section titled “自建 Change Detection”自己维护 Change Detection 是非常强烈的 Warning Sign。
当然存在特殊情况:Performance Optimization、Zoneless Application、Manual Boundary、巨大 List、External API。
但如果普通 Feature Development 中经常需要手工让 Screen “终于更新”,真正的问题通常不在 Rendering。
更常见的是 State 建模错误。
或者 State 绕过 Framework 被修改。
或者系统里存在多个 Truth。
或者 Side Effect 出现在本应是 Derivation 的地方。
自建 Change Detection 很少只是 Performance Feature。它经常是 Architecture Symptom。
可以优化 Rendering,也应该理解并有意识地控制 Change Detection。
但不要拿它给不清晰的 State Model 打补丁。
自建 Cache Class
Section titled “自建 Cache Class”很多项目嘴上说 Cache,实际上做的是 State Management。
一开始很无害:
private items = new Map<string, Item>();然后加入 Selection State。 再加入 Loading State。 Error State。 Invalidation。 Reload。 Event。 Side Effect。 最后是 Global Mutable State。
某一天,这个 Class 同时成了 Store、Repository、Event Bus、Cache 与 Process Manager。
只是没人这么叫它。
真正的 Cache 需要明确 Invalidation Model:Data 什么时候有效?谁可以覆盖?Parallel Request 怎么处理?Logout 呢?Tenant Change 呢?Mutation 后呢?Error 时呢?
没有这些答案,它就不是 Cache。
没有清晰 Invalidation Model 的 Cache,不是 Cache。它只是一个带 Map Interface 的希望。
自定义 Abstraction 当然可以有价值。
但如果它已经替代 State Management,就应该按 State Management 的标准设计、测试与维护。
Migration 是最诚实的时刻
Section titled “Migration 是最诚实的时刻”这类替代系统的价格很少在第一个 Feature 上体现。
它更常出现在以后:
Major Upgrade。 新 Framework Paradigm。 新 Team Member。 Test。 Refactoring。 Security Requirement。 Performance Problem。 Migration。
在一次具体的项目诊断里,2026 年一个系统仍停留在 Angular 17。评估结果大致是:付出很高成本,大约 60+ Point,也许还能 Upgrade 到 Angular 19。再往后可能就很难继续,因为自建 Runtime Mechanism 已经和 Framework 的 Modernization Direction 越来越不匹配。
这不是一般规律。
只是一个项目诊断。
但它揭示了一个常见 Pattern。
如果 Framework 只剩 Render Layer,就会慢慢失去跟随 Framework Evolution 的能力。State 真正住在 Mutable Cache Class 时,Signals 帮助有限;Validation 完全在另一套系统里时,更好的 Forms API 也帮助有限;Navigation 根本不经过 Framework Router 时,Router Improvement 同样没有价值。
反 Framework 最贵的部分不是第一次实现。是后面的 Migration。
学习曲线与组织责任
Section titled “学习曲线与组织责任”响应式 Thinking 不可能在一个 Sprint 里学会。
这一点很重要。
一个人如果多年都在 OOP、Service、Method、Control Flow 与 Transaction 中工作,不会因为看完 Framework Tutorial 就立即换掉整个思维模型。
当然可以使用 Signal API。 也可以 Subscribe Observable。 也可以写 React Hook。
但那还不等于已经在响应式思考。
真正的转变发生在:开始把 State、Event 与 Derivation 当作 Architecture Model,不再首先问“我现在调用哪个 Method?”,而是问“我们现在究竟在建模什么情境?”
从实践经验看,在有指导的情况下,往往也需要数月时间,Developer 才会开始真正理解这些 Pattern,而不是机械套 API。
这不是弱点。
这是学习。
很多组织却不给这段时间。
Team 要构建现代 Frontend,却没有时间理解 Paradigm。Framework 是新的,Architecture Thinking 却还是旧的。Ticket、Deadline 与 Delivery Pressure 照常存在。
于是 Developer 回到自己最熟悉的东西。
Workaround 出现。 Workaround 变 Pattern。 Pattern 变 Architecture。 Architecture 最后开始阻碍 Product。
这很少只是个人失败。
它经常是组织问题。
如果企业真的希望拥有现代 Frontend,就必须认真对待这种 Paradigm Shift。
不是把它当“Frontend 小玩意”的 Training Bonus。
而是 Architecture Foundation。
不是每个自定义 Abstraction 都错
Section titled “不是每个自定义 Abstraction 都错”
当然,不是每个自定义 Abstraction 都有问题。
恰恰相反,好的 Architecture 需要 Abstraction。
当 Abstraction 保护 Domain Language、封装 External System 或表达重复 Use Case 时,它们很有价值。
好的 Abstraction:
保护业务语言。 封装 Infrastructure。 尊重 Framework Paradigm。 减少 Coupling。 让 Behavior 更容易 Test。
真正危险的是:自定义 Abstraction 在没有承担完整责任的情况下,开始替代 Framework Core Concept。
例如替代 Routing。 替代 Validation。 替代 Change Detection。 替代 State Model。 制造 Double Truth。 需要 Insider Knowledge。 阻挡 Upgrade。
真正的问题不是:
我能不能自己做 Abstraction?
当然可以。
更好的问题是:
这个 Abstraction 是在扩展 Framework,还是正在悄悄替换它的 Runtime Model?
这才是区别。
Facade over Store 可以合理。 Mapper into ViewModel 往往非常合理。 ACL 用来隔离外部 API Model 很常见。 Domain Navigation Abstraction 也可能很有帮助。 Application Service 可以清楚表达 Use Case。
但自建 Router、自建 Validation、自建 Change Detection 与 Global Cache Class 已经不是小 Helper。
它们是 Architecture Decision。
应该按这个级别对待。
怎么判断项目已经在和 Framework 对抗
Section titled “怎么判断项目已经在和 Framework 对抗”项目不会因为有一个 Helper 或一段命令式 Method 就自动“反 Framework”。
真正的 Warning Sign 是:
Routing 只能通过 Internal State 工作。 Validation 在三个位置重复同一 Truth。 Component 经常手工触发 Rendering。 Cache Class 比 ViewModel 更了解 Screen。 Framework Upgrade 不是卡在 API,而是卡在那些接管了 Framework Responsibility 的自建 Logic。
此时问题不再是某一行 Code。
而是整个项目已经开始维护自己的 Runtime Model。
请尊重你选择的 Framework Paradigm
Section titled “请尊重你选择的 Framework Paradigm”这才是本文真正想留下的提醒:
请尊重你选择的 Framework Paradigm。
不是因为 Dogma。
不是因为 Angular、React 或 Vue 神圣。 不是因为 Framework Documentation 永远正确。 也不是因为禁止自己的 Idea。
而是因为 Framework 本身就是 Runtime Model。
选择 Framework,并不只是选择 Syntax、Component 或 Build Tool。
同时也是在选择一种 Thinking Model。
可以学习它。 可以有意识地离开它。 可以在局部做 Abstraction。 也可以批评它。
但不要在不自知的情况下把它替换掉。
如果使用 Angular、React 或 Vue,就让 Framework 做它们擅长的工作。
建模 State。 描述 Derivation。 发送 Intent。 按照 Framework 的 Runtime Model 使用 Routing、Validation 与 Rendering。 保护 Domain,但不要悄悄替代 Runtime Model。
其他做法在开始时往往感觉像掌控。
最后则会变成一个从来没人真正决定要构建的自有 Framework。