1. 为什么前端代码总写着写着就成了一团乱麻
先别急着聊架构设计,咱们先回忆一个场景:你接手一个做了两年的后台管理系统,打开项目仓库,src 下面密密麻麻铺了四十多个文件夹,有的叫 utils,有的叫 common,还有几个叫 utils2、new_utils。组件目录里一半是页面组件,一半是业务组件,还有几个不知道干什么用的“万能组件”,接收十来个 props,里面塞了七八个 if 分支。你试着改一个按钮的颜色,顺着 props 找了五层组件,最后发现样式写在一个全局 !important 里。
这种代码,前端圈有个很形象的称呼——意大利面代码。面条缠在一起,扯一根带出来一大坨。我说句得罪人的话:很多团队的技术债,不是欠在技术选型上,而是欠在架构的缺失上。
“前端架构设计”听起来是个特别大的词,好像只有大厂架构师才配聊。但实际上,它解决的就是一个特别朴素的问题:当代码量超过一个人脑容量能装下的范围之后,怎么让代码仍然保持可维护、可扩展、可替换。 我见过不少项目,功能都能跑,接口全通着,也没啥技术难点,但就是没人愿意改。原因很简单——改不动,一改就崩,一崩就加班。这种项目的通病,往往不是某段代码写得烂,而是整个项目的“骨架”就没立起来。
这篇文章不聊虚的。我会从“怎么识别意大利面代码”开始,讲清楚一套可以直接落到中后台项目和前端工程里的架构设计思路,包括分层、模块边界、状态治理、依赖规则这些实操内容,最后把我踩过的坑和排查问题的方法一并交代了。适合谁看?写前端两三年、开始负责模块拆分或项目重构的同学,以及团队里需要定前端规范的“半个架构师”。看完不说脱胎换骨,至少下次代码评审的时候,你能有理有据地指出问题,而不是只能憋出一句“这段代码写得有点乱”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先给项目做个“体检”:意大利面代码的五个典型症状
2.1 症状一:全局状态里什么都有,谁也说不清被谁改了
这是最典型、也最致命的症状。打开项目,Redux 或者 Pinia 的 store 里躺着二三十个字段,有用户信息、有表单数据、有列表筛选条件、有某个弹窗的开合状态,甚至还有上一次请求的时间戳。你去搜某个字段的所有引用,搜出来四十多处,分布在十几个组件里。
问题不在于全局状态里存了东西,而在于全局状态变成了一个“垃圾中转站”。任何组件想共享点数据,懒得抽公共逻辑,直接往 store 里一扔,完事。日子久了,store 里没有任何层级和职责划分,谁都能读,谁都能写,出了问题你想回滚都不知道改哪里。我在实际项目里见过最离谱的一次,一个“确认弹窗”的 visible 状态放在了全局 store 里,就为了两个兄弟组件通信方便——这就是典型的用全局状态掩盖组件通信设计缺失。
2.2 症状二:组件层级的“无底洞”和“深水炸弹”
展开组件树,一个页面组件下面套了五六层,每一层都只负责传递 props 和回调函数。最底层的子组件想通知最顶层的父组件一件事,所有中间层都得帮着把回调函数往下传一遍。传参链路长了,谁改了参数类型,到处报错,这是“无底洞”。
还有一种相反的情况,单个组件极其庞大,一个文件一千多行,里面既能发请求、又能校验表单、还能渲染表格、顺带处理下载。这种组件我管它叫“深水炸弹”——平时安静地躺在项目里,看着似乎没问题,一旦需求变化,你动一行代码,周边炸一片。
组件设计层面最大的误区,是把“组件复用率”当作最高目标。为了追求复用,把各种不相关的逻辑塞进同一个组件,靠一大堆条件分支来区分。结果就是复用了,但没人能看懂、没人敢改。组件设计的第一目标是职责清晰,其次才是复用。
2.3 症状三:循环依赖像一个八爪鱼,谁都在引用谁
用工具扫一下依赖关系图,你会发现模块之间的依赖关系不是树状的,而是网状的,甚至还有 A -> B -> C -> A 这种循环引用。Webpack 打包的时候喊半天警告,代码里 import 倒是没报错,但这是埋在项目里的定时炸弹。遇到循环依赖,初始化顺序一错,变量是 undefined,报错信息还特别难定位,因为报错位置和真正出问题的地方隔了十万八千里。
循环依赖是架构设计缺失最直观的体现。它说明模块边界是模糊的,谁也不知道自己应该处于哪一层、可以依赖谁。这时候你光靠“大家注意点”是没用的,得靠架构分层和工具约束来硬性解决。
2.4 症状四:公共代码是一个“大杂烩”,而不是“工具箱”
很多项目的 utils 目录就是个大杂烩。里面有格式化日期的,有防抖节流的,有解析 URL 参数的,有生成随机 ID 的,还有两个函数功能百分之八十重合的,一个叫 formatTime,一个叫 timeFormat。更离谱的是,居然是不同人各自封装、各自使用、各自维护的。
公共代码的问题不仅仅是重复,更是缺少演进机制。没人给公共函数做归类和文档,也没有测试用例,谁有需要就直接往里面加。等到公共函数出了问题,影响面是全局性的。我接过一个项目,改一个公共工具函数修 bug,结果因为好几个页面依赖了这个函数的“隐式行为”,修完一个 bug 带出来三个新 bug。从那以后我养成一个习惯:任何公共代码的修改,必须先看所有调用点。
2.5 症状五:没有分层概念,业务逻辑散落在各个角落
最典型的场景:一个列表页,请求数据的逻辑写在组件 useEffect 里,数据转换的逻辑写在渲染函数中间,用户权限的判断散落在三个工具函数里,提交表单的时候在组件里临时拼了一个复杂的 payload。整个项目从头到尾看不到“数据层”“业务层”“视图层”这样的概念边界。
分层不是架构师为了画图好看,而是为了让代码有明确的“居住地”。没有分层,业务逻辑就只能长在组件里,而组件是会反复重写的。今天用 Vue 写,明天换 React,如果你没有独立的业务逻辑层,整个项目等于推到重来。有分层,组件就只是个“壳”,核心业务逻辑放在独立的模块里,无论视图怎么换,底层能力都在。
3. 核心架构方案:三层架构加单向依赖,治本不治标
3.1 先立规矩:依赖方向必须是单向的
架构设计里的第一性原理,不是画分层图,而是确定依赖规则。我推荐的方案是经典的三层架构,额外加一条铁律——依赖只能从上往下,不允许反向依赖,不允许跨层依赖。
三层分别是:
- 视图层:页面组件、UI 组件,负责渲染和用户交互。
- 业务层:处理具体业务流程的逻辑,比如“提交订单时要先校验库存再调接口”“列表页加载时要组合三个接口的数据”。
