

子玥酱 (掘金 / 知乎 / CSDN / 简书 同名)
大家好,我是 子玥酱,一名长期深耕在一线的前端程序媛 👩💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚焦于业务型系统的工程化建设与长期维护。
我持续输出和沉淀前端领域的实战经验,日常关注并分享的技术方向包括 前端工程化、小程序、React / RN、Flutter、跨端方案,
在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。
技术方向: 前端 / 跨端 / 小程序 / 移动端工程化 内容平台: 掘金、知乎、CSDN、简书 创作特点: 实战导向、源码拆解、少空谈多落地 **文章状态:**长期稳定更新,大量原创输出
我的内容主要围绕 前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读 展开。文章不会停留在"API 怎么用",而是更关注为什么这么设计、在什么场景下容易踩坑、真实项目中如何取舍,希望能帮你在实际工作中少走弯路。
子玥酱 · 前端成长记录官 ✨
👋 如果你正在做前端,或准备长期走前端这条路
📚 关注我,第一时间获取前端行业趋势与实践总结
🎁 可领取 11 类前端进阶学习资源 (工程化 / 框架 / 跨端 / 面试 / 架构)
💡 一起把技术学"明白",也用"到位"
持续写作,持续进阶。
愿我们都能在代码和生活里,走得更稳一点 🌱
文章目录
-
- 引言
- 什么是"好的模块边界"
- [为什么 Flutter 项目很容易边界失效](#为什么 Flutter 项目很容易边界失效)
-
- [import 没有限制](#import 没有限制)
- 目录结构不等于模块结构
- 过早"抽公共",反而破坏边界
- [一个更稳定的模块划分方式:按 Feature 拆](#一个更稳定的模块划分方式:按 Feature 拆)
-
- [每个 Feature 必须"自闭环"](#每个 Feature 必须“自闭环”)
- 依赖必须单向流动
- [shared 只放"真正无业务"的代码](#shared 只放“真正无业务”的代码)
- 如何判断边界是否健康
- 一个常见误区:过度模块化
- 实际项目中的落地建议
- 总结
引言
很多 Flutter 项目在前期其实并不缺"结构"。甚至可以说,大多数项目一开始都有一套看起来很合理的目录:
pages / widgets / services / models
问题不是有没有结构,而是:
这套结构,无法支撑业务持续变化。
当项目进入中后期,你会频繁遇到这些情况:
- 一个需求要改 5 个目录
- 一个模块改动影响多个页面
- 想删一个功能,却发现删不干净
这些问题的本质,其实只有一个:
模块边界从一开始就没有设计好。
什么是"好的模块边界"
很多人理解模块边界,是"文件放在哪"。但真正有效的边界,应该解决三个问题:
- 哪些代码可以改
- 哪些代码不能随便动
- 改动会影响到哪里
换句话说,一个好的模块边界应该具备:
可独立理解
不用跳多个目录,就能看懂一个功能
可独立修改
修改一个模块,不需要同步改其他模块
可独立删除
删除一个功能,不会留下"幽灵依赖"
如果做不到这三点,说明边界只是"看起来存在"。
为什么 Flutter 项目很容易边界失效
Flutter 在语法和工程上都非常"自由",这种自由在前期是优势,但也埋下了隐患。
import 没有限制
import '../../services/order_service.dart';
任何地方都可以直接访问任何模块,结果就是:
- UI 可以直接调 data 层
- 工具类被全局依赖
- 不同业务之间相互调用
边界一旦被跨越,就很难再收回来。
目录结构不等于模块结构
很多项目看起来分层清晰:
pages/
services/
models/
但实际依赖是这样的:
page A → service B → model C
page D → service B → model C
本质上:
这是按技术分层,而不是按业务分模块。
当业务增长,这种结构一定会交叉。
过早"抽公共",反而破坏边界
很多团队会很早建立:
common/
shared/
utils/
初衷是复用,但结果往往是:
- shared 越来越大
- 所有模块都依赖它
- 修改风险越来越高
最后变成一个典型问题:
公共层成了最大的耦合源。
一个更稳定的模块划分方式:按 Feature 拆
更适合长期维护的方式,是按业务拆分:
lib/
├─ core/
├─ shared/
└─ features/
├─ user/
├─ order/
└─ home/
这里的关键点不是目录,而是边界规则。
每个 Feature 必须"自闭环"
一个完整的 feature,应该长这样:
order/
├─ data/
│ ├─ order_api.dart
│ ├─ order_dto.dart
│ └─ order_repository.dart
├─ domain/
│ └─ order_entity.dart
├─ ui/
│ ├─ order_page.dart
│ └─ widgets/
└─ order_router.dart
核心原则:
一个功能的所有实现,都在一个目录内完成。
这样带来的好处是:
- 新人只看一个目录就能理解功能
- 改动范围天然被限制
- 删除功能变得可行
依赖必须单向流动
必须明确一条规则:
UI → Domain → Data
禁止:
- data 调 UI
- feature 之间直接调用内部实现
如果需要跨模块通信,可以用:
- 抽象接口
- 事件机制
- 路由参数
本质是:
依赖只能向内收敛,不能横向扩散。
shared 只放"真正无业务"的代码
很多项目最大的问题就是 shared 失控,一个更严格的约束是,shared 只允许存在两类东西:
- 完全无业务语义的 UI(比如基础按钮)
- 纯工具函数(无状态、无依赖)
举个反例:
UserCardWidget (有业务语义)
正确做法是:
这种组件必须放在 user feature 内。
否则边界会逐渐被侵蚀。
如何判断边界是否健康
你可以用三个简单的问题做自检:
问题一:删除一个 feature,能否不改其他代码?
如果不能,说明存在隐性依赖。
问题二:新增一个功能,需要改多少旧代码?
如果修改范围很大,说明边界不清晰。
问题三:一个 bug 是否能快速定位模块?
如果需要全局搜索,说明结构已经失控。
一个常见误区:过度模块化
有些团队意识到问题后,会走向另一个极端:
- 拆很多 package
- 引入复杂依赖管理
- 强行做"组件化"
结果是:
- 开发效率下降
- 调试成本变高
- 团队难以适应
需要明确一点:
模块化不是目的,降低复杂度才是。
如果业务还没复杂到一定程度,过度设计反而是负担。
实际项目中的落地建议
结合实际开发经验,可以从这几个步骤开始优化:
第一步:停止扩展 shared
所有新功能优先放 feature 内
第二步:逐步迁移老代码
按业务把代码往 feature 收拢
第三步:在 code review 中约束依赖方向
这是最关键的一步
第四步:控制模块大小
一个 feature 太大时,再内部细分,而不是拆到全局
总结
模块边界这件事,很少有人在项目初期认真设计,但几乎所有团队都会在后期为此付出代价。
Flutter 给了我们足够的灵活性,但也意味着:
边界不会自动出现,只能靠人为约束。
当你开始用"业务"而不是"文件类型"去划分模块,用"依赖方向"而不是"目录层级"去约束代码时,项目才会真正从"能写",走向"能长期维护"。