前端框架的“军备竞赛”何时休?我们真正需要的是一种“沉默的架构”
过去十年,前端世界陷入了一场愈演愈烈的“军备竞赛”:React Hooks 刚成为事实标准,Vue 3 的 Composition API 就掀起新一轮心智模型刷新,接着 Svelte 用编译时魔法宣称“不用框架的框架”,紧接着 Solid、Qwik 又开始争夺“性能王座”。每一年都有新概念、新工具、新脚手架,似乎谁不紧跟潮流,谁就会被时代抛弃。但在这种狂飙突进的背后,我们是否问过自己一个问题:我们究竟是在用工具解决问题,还是在为工具制造问题?当项目代码变成依赖层层嵌套的“俄罗斯套娃”,当一次简单的按钮点击需要经过三个 Provider、五个 Hook、七层 memo 时,我们早已忘记了前端最初的承诺:把信息呈现给用户,并且允许用户与之交互。
我并非反对框架本身。框架是工程化的必然产物,尤其在复杂应用、团队协作、状态同步等方面,成熟框架提供了宝贵的抽象。但问题在于,我们已经把框架当作了一种“信仰”,而不是一种“工具”。信仰意味着不加批判地全盘接受,意味着为了框架的“先进性”而牺牲真正的产品需求。我们习惯于从框架出发思考问题——一个功能要不要做,取决于它在 React 里好不好实现;一次交互怎么设计,受限于 Vue 的响应式机制。这种“以框架为中心的开发”,让开发者变成了框架的附庸,让业务逻辑与框架 API 深度耦合,最终被技术债死死绑住。尤其随着应用规模增长,这种耦合带来的痛苦呈指数级放大:升级框架像做一次心脏手术,重构一个组件可能引发连锁崩溃,而开发者只能在充满“魔法”的抽象中艰难打捞真实逻辑。
所以,我提出一个反直觉的观点:真正卓越的前端架构,应当是“沉默的”——它不卖弄任何炫技的 API,不需要开发者时刻记得框架的存在,甚至不该让人一眼看出你用了什么技术。这样的架构依托于三条支柱:第一,优先使用平台原生能力。现代浏览器已经提供了自定义元素、Shadow DOM、CSS 变量、CSP 等能力,为什么还要将其封装成私有 API?原生实现意味着零依赖、零构建成本,也意味着当框架消失时,你的代码依然能跑。第二,将状态响应式与业务逻辑解耦。与其用框架的 subscribe/effect 系统把全局状态搅成一团,不如明确划分“状态源”与“副作用边界”:数据变更用纯函数描述,UI 更新由浏览器自己处理,我们只关心“什么变了”,而不强制规定“怎么变”。第三,以渐进增强而非全量替换为导向。新功能用 Web Component 包裹,旧系统保留原有栈,通过一个轻量的事件总线互相通信。这种策略不仅降低迁移成本,更重要的是让团队不必为了“统一”而重构所有历史代码——这往往是技术债的最大来源。
“沉默的架构”并不意味着没有架构,而是将架构隐藏在显性的智能之后。它要求开发者具备更强的纪律性:你必须在引入一个依赖前问自己,这个依赖负责解决什么问题?它是否在十年后还存在?它的复杂度是由谁承担的?例如,如果想要实现一个日期选择器,原生 <input type=date> 足够就绝不自己画界面;如果只是某个模块局部需要用状态机,就不必把一个抽象层推到全项目。这种“够用即止”的哲学在追求“极致性能”的当下显得格格不入,但正是这种克制,才能带来真正的长期回报:仓库体积更小,构建更快,新手更容易上手,团队不再被框架迁移的恐惧所支配。更实际的是,当公司某天决定更换技术栈时,“沉默的架构”能让你从容地说:这些组件是标准的,拿走即用,不必重写。
诚然,这种观点在很多场景下会被认为“过于理想化”,因为现实很骨感:团队要招聘,人才市场上全是 React/Vue 专家;框架社区生态成熟,遇到问题一搜就有答案;而“原生”往往意味着“自己造轮子”的道德负担。但如果今天我们可以用 CSS 变量轻松实现主题切换,通过 :has 选择器处理大部分状态样式,用 AbortController 管理请求取消,为什么还要把这些逻辑重新封装成框架的 “useTheme”“useFetch” 呢?实际上,前端世界的每一次进步,都是从平台身上汲取能量,而不是远离平台。框架的终极宿命,不是统治一切,而是逐渐内化到浏览器本身,如同 jQuery 的许多思想已被原生 API 吸收。因此,我们要做的不是追逐下一个新框架,而是理解框架背后的本质,然后用尽可能简单的技术解决当下问题。
未来已来,只是分布不均。与其在框架的洪流中努力保持不下沉,不如学会捕捉那些“沉默”的能力。下一次当你准备初始化一个新的项目时,先不要习惯性地执行 create-react-app 或 npm create vue@latest。深呼吸,打开一个空文件夹,写下一个原生的 HTML 文件。感受一下没有构建工具、没有包依赖的清爽。哪怕你最终还是选择了框架,也请带着“它只是我的一种实现方案”的清醒去选择,而不是把它当作所有问题的答案。毕竟,最好的架构,是当你回过头来看时,发现它根本没有刻意存在过——它只是让代码自然而然地正确地工作。