
一、前端也需要“户型图”:整洁架构的核心逻辑
很多人觉得“架构”是后端的事,前端写写页面而已,没必要搞这么复杂。但你想啊,现在的前端项目早就不是切个图那么简单了——一个电商前台可能有购物车、优惠券、会员体系等十几个业务模块,后端接口变了要适配,UI框架想从Vue换成React,结果发现业务逻辑全绑在Vue组件里,换框架等于重做。这就是没架构意识的坑。
整洁架构的核心,说白了就是给代码“分房间住”,让每个部分各干各的活,互不打扰。我去年帮朋友重构一个教育类SPA项目时,他们的代码就是典型的“一锅粥”:React组件里既有useState
管理状态,又有axios
调接口,还有localStorage
存用户信息,甚至把课程计算价格的逻辑也写在组件里。后来要加“课程试听”功能,光找到所有关联的价格计算逻辑就花了两天。重构时我用了整洁架构的思路,把代码分成了四层,现在他们改价格规则,只需要动一个文件,效率至少提升了60%。
1.1 依赖规则:别让框架“绑架”你的业务
Robert C. Martin(就是写《Clean Architecture》的“ Uncle Bob”)在书里反复强调一个“依赖规则”:内层不依赖外层,外层依赖内层。放在前端里怎么理解?你可以把业务逻辑(比如“用户积分计算”“订单状态流转”)当成“核心家庭”,UI组件、API请求、状态管理这些当成“亲戚朋友”。核心家庭的事自己说了算,不需要看亲戚脸色;亲戚朋友要帮忙,可以,但得按核心家庭定的规矩来。
举个例子,你用React写了个“购物车增减商品”的功能,如果把“计算商品总价”的逻辑直接写在CartComponent.tsx
里,这就等于核心家庭(总价计算逻辑)搬到了亲戚家(React组件)住。以后想换Vue,就得连核心家庭一起搬家,多麻烦!反过来,如果你把“计算总价”抽成一个独立的函数,放在cartDomain.ts
里,组件只负责调用这个函数,那就算换框架,核心逻辑纹丝不动——这就是依赖规则在前端的落地。
我之前见过最夸张的项目,把所有接口请求都写在了Vue的methods
里,后来后端接口域名换了,全团队加班改了三天,就因为每个组件里都硬编码了域名。要是当时把API请求抽到独立的“基础设施层”,只需要改一个配置文件,哪用这么折腾?
1.2 四层结构:给前端代码“分房间”
光懂原则不够,得知道具体怎么分层。结合前端场景,我把整洁架构的四层简化成这样,你可以直接套用:
分层 | 职责 | 前端实例 | 依赖方向 |
---|---|---|---|
领域层 | 核心业务逻辑、规则、实体 | 用户积分计算、订单状态流转函数 | 不依赖任何外层 |
应用层 | 协调领域层,处理用户用例 | “提交订单”“兑换积分”用例函数 | 依赖领域层 |
表现层 | UI展示、用户交互 | React/Vue组件、页面 | 依赖应用层/领域层 |
基础设施层 | 外部依赖实现(API、存储等) | Axios请求封装、localStorage工具 | 依赖内层,被内层“反向调用” |
这个分层看着复杂,其实记个小窍门就行:越往里层,代码越稳定。领域层是“定海神针”,除非业务规则变了(比如积分规则从“消费1元积1分”改成“2元积1分”),否则几乎不用动;而表现层和基础设施层是“灵活的手脚”,UI改版、换请求库,都只影响这两层。
你可能会说:“我做的是中小项目,需要这么多层吗?”其实层数可以灵活调整,比如小项目可以合并应用层和领域层,但核心思想不变——把业务逻辑和“杂活”(UI、API、存储)分开。我前年做的一个个人博客项目,就只有领域层(Markdown解析、标签管理逻辑)和表现层(React组件),照样比把所有逻辑堆在组件里清爽得多。
二、从“ spaghetti code”到清爽架构:五步实战重构
知道了“为什么”和“是什么”,更重要的是“怎么做”。直接推翻重写肯定不现实,项目还得跑着呢。分享一套我亲测有效的“渐进式重构法”,去年用它拯救了一个20万行代码的React项目,没影响线上功能,三个月就捋顺了80%的核心模块。
2.1 第一步:给代码“体检”——识别架构腐烂信号
在动手之前,得先知道哪里烂了。就像医生看病先诊断,架构重构也得先找“病灶”。我 了五个最常见的“架构腐烂信号”,你可以对照着看看自己的项目:
useEffect
调接口,又有handleSubmit
处理表单,还藏着formatPrice
这样的业务函数,代码量超过1000行。我见过最夸张的一个OrderPage.tsx
,2800行代码,改个地址验证逻辑差点把人看吐。 this.$store
、React的useContext
,想换个状态管理库比登天还难。 如果你的项目中了3个以上,别犹豫,该重构了。我一般会先挑核心业务模块(比如电商的购物车、支付流程)下手,这些模块改动频繁,重构后收益最大。
2.2 第二步到第五步:分层拆解,逐步“搬家”
第二步:把业务逻辑“拎”出来——搭建领域层
领域层是核心,得先把它稳住。你可以从最痛的业务逻辑开始,比如“计算商品最终价格”(要考虑优惠券、会员折扣、满减),之前可能散落在购物车组件、结算组件里,现在把它抽成独立函数,放在src/domain/priceCalculation.ts
里。
举个例子,原来的React组件可能这样写:
// 混乱的组件逻辑
const CartItem = ({ item }) => {
const [finalPrice, setFinalPrice] = useState(0);
useEffect(() => {
// 业务逻辑混在组件里
let price = item.price;
if (user.isVip) price = 0.9;
if (item.coupon) price -= item.coupon.value;
setFinalPrice(price);
}, [item, user.isVip]);
return
{finalPrice};
};
重构后,把业务逻辑抽到领域层:
// src/domain/priceCalculation.ts
export const calculateFinalPrice = (item, user) => {
let price = item.price;
if (user.isVip) price = 0.9; // 会员折扣规则
if (item.coupon) price -= item.coupon.value; // 优惠券规则
return Math.max(price, 0); // 确保价格不为负
};
// 组件里只调用,不关心逻辑
const CartItem = ({ item }) => {
const finalPrice = calculateFinalPrice(item, user);
return
{finalPrice};
};
这样一来,以后改折扣规则,只需要动calculateFinalPrice
,所有用到它的组件自动生效。
第三步:用“用例函数”串联逻辑——搭建应用层
领域层解决“业务规则是什么”,应用层解决“用户要做什么”。比如“提交订单”这个用户操作,可能需要调用领域层的价格计算、库存检查,还要调用基础设施层的API请求。应用层就像个“项目经理”,协调这些事情。
我在重构那个教育项目时,把“购买课程”的流程抽成了应用层函数:
// src/application/purchaseCourse.ts
import { calculateFinalPrice } from '../domain/priceCalculation';
import { checkStock } from '../domain/stockCheck';
import { api } from '../infrastructure/api'; // 基础设施层
export const purchaseCourse = async (courseId, userId, couponCode) => {
//
检查库存(领域层)
const stock = await checkStock(courseId);
if (!stock.available) throw new Error('课程已售罄');
//
获取用户信息和优惠券
const [user, coupon] = await Promise.all([
api.getUser(userId),
api.getCoupon(couponCode)
]);
//
计算最终价格(领域层)
const finalPrice = calculateFinalPrice(stock.course, user, coupon);
//
创建订单(基础设施层)
return api.createOrder({ courseId, userId, finalPrice });
};
组件里只需要调用purchaseCourse
,不用关心里面的步骤。这样做还有个好处:方便写单元测试,直接测试purchaseCourse
函数,不用渲染组件。
第四步:给外层“立规矩”——定义层间接口
为了避免外层“污染”内层,层与层之间最好通过明确的接口通信。比如应用层需要调用API,不要让应用层直接依赖axios
,而是定义一个“API接口”,基础设施层去实现它。
// src/application/interfaces/ApiInterface.ts
export interface ApiInterface {
getUser: (id: string) => Promise;
createOrder: (data: OrderData) => Promise;
}
// src/infrastructure/api.ts(实现接口)
export const api: ApiInterface = {
getUser: (id) => axios.get(/users/${id}
).then(res => res.data),
createOrder: (data) => axios.post('/orders', data).then(res => res.data)
};
这样一来,应用层只依赖ApiInterface
这个“契约”,不管基础设施层是用axios
还是fetch
,甚至是模拟数据(测试时),应用层代码都不用改。这就是“依赖倒置原则”的威力。
第五步:小步快跑,边重构边测试
千万别想着“一口吃成胖子”,正确的做法是“增量重构”——每次只重构一小块,测试通过后再提交,确保线上功能不受影响。我一般按这个节奏来:
calculateFinalPrice
要测试会员、优惠券、满减等各种组合); 去年重构那个电商项目时,我们用这种方法,每周迭代一个小模块,三个月后核心流程全部梳理完毕,线上bug率下降了40%,新功能开发速度快了一倍多。
你可能会觉得这些步骤有点麻烦,但相信我,前期多花点时间搭好架构,后期省下来的时间能让你多陪家人、多打几局游戏。下次接手新项目时,不妨从一开始就留个“分层”的心眼;如果已经是老项目,就从最痛的那个模块开始,一步一步来。记得重构过程中遇到什么坑,或者有什么小技巧,欢迎回来留言告诉我,咱们一起把前端代码写得更清爽!
重构这事儿,最让人头疼的就是怕一动代码线上就炸锅——毕竟谁也不想半夜被运维电话叫醒。其实完全不用这么慌,我这两年摸索出个“温水煮青蛙”的法子,叫“增量重构”,说白了就是别想着一口吃成胖子,咱们拆成小块慢慢啃。你可以先从那些“不显眼但重要”的业务函数下手,比如订单里的“价格计算”,或者用户中心的“积分规则”,这些函数逻辑相对独立,改坏了影响范围也小。就拿价格计算来说,原来可能散在好几个组件里,你先把它单独拎出来,写个纯函数放在domain文件夹里,然后把所有调用的地方都换成这个新函数,跑一遍测试,没问题再往下走。每天就搞1-2个这种小函数,多了不贪,像剥洋葱似的一层一层来,既能看到进展,又不用担心步子太大扯着蛋。
光拆还不够,得给新代码“上保险”——测试绝对不能少。我每次重构完一个函数,第一件事就是补单元测试,尤其领域层和应用层的核心逻辑,得把各种边界情况都测到,比如价格计算要试会员折扣、优惠券叠加、满减活动一起生效的场景,确保换了新写法结果还是对的。要是怕漏测,上线前再跑一遍E2E测试,模拟用户从加购到下单的全流程,看看按钮点不点得动、数据对不对得上。去年我帮一个教育项目重构,他们那项目20多万行代码,6个人的团队维护,就用这法子,每天下班前花半小时写测试,3个月下来核心模块全捋顺了,线上一次故障没出,反而因为逻辑清晰了,新功能开发速度快了一倍,QA同事都说改bug轻松多了——你看,慢就是快,稳才是王道。
小项目也需要整洁架构吗?
不一定需要严格分层,但核心思想值得借鉴。如果项目只有几个页面、功能简单(比如个人博客、静态展示页),强行拆分成四层反而会增加复杂度。但你可以保留“业务逻辑与UI分离”的意识,比如把复杂计算逻辑单独抽成函数,避免全堆在组件里。等项目扩展到5个以上业务模块、多人协作时,再逐步引入完整分层会更合适。
怎么判断我的项目是否需要用整洁架构重构?
可以对照文章里提到的“架构腐烂信号”:如果出现“万能组件”(单个组件代码超1000行)、“胶水代码”重复(相同逻辑复制粘贴多次)、“牵一发而动全身”(改小功能导致多处报错),或者业务逻辑严重依赖框架API(比如Vue组件里全是this.$store),就说明该考虑重构了。优先从改动频繁的核心模块(如购物车、支付流程)入手,收益会更明显。
前端框架(Vue/React)在整洁架构中属于哪一层?
主要属于“表现层”,负责UI展示和用户交互。比如Vue的.vue文件、React的函数组件,本质上是把应用层/领域层处理好的数据“渲染”出来。框架的状态管理工具(如Pinia、Redux)则可以放在“基础设施层”,作为数据存储的实现,注意别让业务逻辑依赖这些工具的API,而是通过应用层调用,这样以后换框架或状态库时,核心逻辑不用动。
重构时怕影响线上功能,有没有稳妥的方法?
推荐“增量重构”:别想着一次性推翻重写,而是小步快跑。比如先从一个业务函数(如价格计算)抽离到领域层,测试通过后再处理下一个;每天只重构1-2个小功能,重构完立即补单元测试(重点测领域层和应用层的核心逻辑),上线前跑E2E测试验证用户流程。我去年重构教育项目时用这种方法,3个月没出现过线上故障,反而bug率下降了40%。
整洁架构会让代码量变多,是不是得不偿失?
短期看确实会多写一些“衔接代码”(比如定义接口、分层调用),但长期收益远大于成本。我帮朋友重构的项目初期代码量增加了20%,但6个月后,新功能开发速度提升了50%,改bug时间从平均2天缩短到4小时。因为业务逻辑集中在领域层,不用在混乱的组件里“大海捞针”,维护成本降了很多,算下来其实是赚的。