Flutter 项目结构为什么“看起来干净,后期却很难改”?
摘要:本文探讨Flutter项目架构演进中的常见问题及解决方案。作者指出,初期按技术类型划分的目录结构(pages/widgets/models/services)随着业务增长会变得臃肿难维护,并提出借鉴前端的"Feature First"思想,按业务边界组织代码。文章分析了iOS模块化的优缺点,总结了Flutter项目的三大痛点:依赖扩散、公共组件滥用和伪模块化。最后推荐了一

大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
引言
很多人第一次搭 Flutter 项目时,都感觉很舒服:
目录不多、层级清晰、代码集中,甚至几天就能把业务主流程跑起来。
但只要项目活得足够久,几乎一定会进入另一个阶段:结构没变复杂,但改动却越来越痛苦。
这并不是 Flutter 独有的问题,而是不同技术栈在“结构设计”上的共同演进轨迹。
项目初期的“干净感”,其实是一种错觉
大多数 Flutter 项目一开始都是典型的三层或四层结构:
lib/
├─ pages/
├─ widgets/
├─ models/
├─ services/
看起来非常合理:
页面放 pages,通用组件放 widgets,数据结构放 models,请求逻辑放 services。
问题在于——这个结构是按“技术类型”拆的,而不是按“业务边界”拆的。
当只有三五个页面时没问题,但当页面变成三十个、五十个:
- widgets 目录会越来越臃肿
- services 开始出现跨业务调用
- models 逐渐变成“公共垃圾场”
结构依旧整齐,但修改一个功能要跨四五个目录跳转,维护成本迅速上升。
前端 Feature First 为什么能长期稳定
很多前端团队早就经历过类似阶段,所以逐渐演化出 Feature First 结构:
features/
├─ user/
│ ├─ api/
│ ├─ components/
│ ├─ pages/
│ └─ store/
├─ order/
└─ home/
核心思想只有一句话:
按业务切,而不是按技术切。
这样带来的好处非常直接:
- 新人只看一个目录就能理解一个完整功能
- 删除一个业务不会影响其他模块
- 依赖关系天然被收敛在 feature 内部
当然,它也不是没有代价:
- 公共依赖管理更复杂
- 模块之间通信需要额外设计
- 构建速度可能下降
但在中大型项目周期里,这些成本远小于后期维护混乱带来的损失。
iOS 模块化为什么清晰,却很难推广
iOS 生态更早进入模块化阶段,常见做法是:
- 按业务拆分独立 Module
- 通过路由或协议通信
- 每个模块独立编译
优点非常明显:
- 编译隔离
- 依赖边界清晰
- 复用能力强
但真正落地时,团队往往会遇到两个现实问题:
第一是开发成本高。
拆一个模块不仅是目录移动,还涉及:
- 工程配置
- 依赖管理
- 版本控制
第二是业务变化太快。
很多需求生命周期只有几个月,过度模块化反而增加了维护负担。
所以你会看到一个很真实的现象:
架构越“完美”,越难在快速迭代的团队里长期坚持。
Flutter 在项目拆分上的真实难点
Flutter 既不像前端有成熟的 Feature 生态,也不像 iOS 有完整的模块化工程体系。
最常见的卡点其实只有三个。
依赖很容易无边界扩散
Dart import 没有强约束,只要路径能写到,就能直接引用。
结果就是:
- UI 直接调 service
- service 反向依赖 widget
- 工具类逐渐变成“万能入口”
结构看似没问题,但依赖图已经悄悄失控。
公共组件越来越难抽
很多团队都会经历这个过程:
- 先写业务页面
- 发现可以复用
- 抽到 common/widgets
- 参数越来越多
最后得到一个:
谁都能用,但谁改都怕崩的组件。
这其实不是代码问题,而是抽象时机过早。
拆 package ≠ 真正模块化
Flutter 支持 package,看起来像天然模块化方案。
但现实是:
- package 之间仍然可以强依赖
- 版本管理成本高
- 本地调试流程复杂
如果没有清晰的业务边界设计,拆多少 package 都只是形式变化。
一套更适合长期演进的目录实践
真正能让 Flutter 项目活得久的结构,通常长这样:
lib/
├─ core/ // 全局能力:网络、路由、主题
├─ shared/ // 纯通用组件(极少)
├─ features/
│ ├─ home/
│ ├─ user/
│ └─ order/
└─ app.dart
这里有几个关键原则。
先限制 shared,而不是扩展它
shared 只允许放:
- 无业务语义的 UI
- 纯工具方法
一旦出现业务词汇,必须回到 feature 内。
这样才能避免“公共层膨胀”。
feature 内部自闭环
一个 feature 目录应该包含:
user/
├─ data/
├─ logic/
├─ ui/
└─ user_router.dart
目标只有一个:
离开这个目录,就能完整删除一个功能。
如果做不到,说明边界还没切干净。
core 只做基础设施
core 常见内容:
- 网络封装
- 日志
- 路由框架
- 主题配置
绝不放具体业务逻辑。
否则 core 会迅速变成新的“巨石层”。
结构设计,本质是团队协作设计
很多人把目录结构当成技术问题,但真正决定结构能否长期稳定的,其实是三件事:
- 需求变化速度
- 团队规模
- 代码评审文化
没有任何一种结构是“最优解”,只有是否匹配当前阶段。
总结
Flutter 项目之所以常见“前期干净,后期难改”,并不是框架问题,而是:
我们太习惯按文件类型思考,而不是按业务边界思考。
当项目进入中后期,真正需要升级的从来不是目录层级,
而是对模块边界、依赖方向、复用时机的理解。
结构不是为了好看,而是为了让三年后的自己,依然敢改今天写下的代码。
更多推荐



所有评论(0)