深入解析:Flutter 项目结构该如何设计,才能支撑长期迭代

大家好,我是展菲,目前在上市企业从事人工智能方案研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端制作、鸿蒙构建、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,首要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特殊关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告为读者提供有深度、有实用价值的技术洞察与分析。就是,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标
展菲:您的前沿技术领航员
大家好,我是展菲!
全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技巧进阶之路畅通无阻。
行业趋势的探讨,随时畅所欲言。就是 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还
最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
- 前言
- 常见 Flutter 工程结构对比
- 页面级和业务级模块怎么划分
- 可扩展的目录规范
- 实际项目演进中的取舍
- 总结
前言
很多 Flutter 项目一开始目录挺整齐,按「页面」「模型」「服务」一分,看起来挺好维护。可随着机制越加越多,改一处要动好几个文件夹、新人看半天不知道从哪下手、加个新需求总怕踩到老代码的雷。本质上是结构没按「长期迭代」来设计,只满足了「当下能跑」。要支撑长期迭代,就得在目录划分、模块边界和扩展规范上想清楚,让每个改动都有明确归属,新人也能按图索骥。
下面从常见结构对比、页面级和业务级怎么切、可扩展的目录规范、以及实际演进中的取舍几方面说一下。
常见 Flutter 项目结构对比
最常见的有两种思路:按「类型」分(type-first)和按「功能/业务」分(feature-first)。
按类型分,就是大家熟悉的 lib/ 下 models/、views/、services/、utils/ 这种。所有模型放一起、所有页面放一起、所有接口放一起。好处是刚上手简单,找「所有接口」或「所有页面」很快。问题在于:一个业务(比如「订单」)的模型、页面、接口、状态会散在四个目录里,改一个需求要到处跳,职责也容易混——services 里既有订单接口又有用户接口,时间一长就变成大杂烩,新人很难判断「这段逻辑该放哪」。
按功能/业务分(Feature First),则是按业务域或功能块建目录,每个功能块里再放自己的页面、模型、接口、状态。例如 lib/features/order/、lib/features/user/,每个 feature 下面再分 screens/、widgets/、models/、services/ 等。这样「订单相关」的改动基本都在 order 下完成,边界清晰,新人接手也容易:先找 feature,再在 feature 里找具体文件。代价是前期要定好「哪些算一个 feature」,避免 feature 划分过细或过粗。
实际项目里,往往不会非此即彼:核心、会长期演进的业务用 Feature First,真正全局的通用能力(如网络封装、路由、通用组件)单独抽成 shared 或 core,这样既避免「全按类型」导致的扩散,也避免「全按 feature」导致公共层缺失。
页面级和业务级模块怎么划分
「模块」可以按粒度拆成两种:页面级和业务级。
页面级以「一个或一组相关页面」为单位。比如「订单列表」「订单详情」「订单创建」算一个订单模块,里面主要是 UI、页面状态和该页用到的接口。适合功能相对独立、复用面不大的场景。划分时建议:一个模块一个目录,里面再按 screens/、widgets/、providers/ 或 bloc/ 等分层,避免一个文件上千行。
业务级比页面大一层,是「一整块业务」:订单从列表、详情、创建、支付、售后都算订单业务,可能跨多个页面、甚至多个 Tab。业务级模块里会包含多个页面级子模块,以及该业务共用的模型、领域逻辑、接口。划分业务级的意义在于:把「会一起变、一起发版」的东西收拢,减少跨模块的随意依赖。比如订单和商品是两条业务线,订单模块可以依赖「商品」的少量接口或模型,但不要反过来让订单逻辑散进商品模块。
实践中:先按业务级划出几个大目录(如 order、user、product),再在业务内部按页面或子功能划子目录。这样既不会一上来就几十个平级目录,也不会所有代码挤在少数几个文件夹里。
可扩展的目录规范
要支撑长期迭代,目录不能只满足「现在」,还要约定「以后怎么加」。建议定几条简单规范:
第一,统一命名和层级。例如每个 feature 下都允许有 screens/、widgets/、models/、services/(或你项目用的分层),新增时只在这几类里加,不随意在 feature 根目录堆新名字。这样任何人一看就知道「新页面放 screens,新接口放 services」。
第二,公共能力单独成层。如 lib/core/ 或 lib/shared/ 放网络、路由、存储、通用 UI 组件、常量等。feature 只依赖 core/shared,不互相依赖;core 不依赖具体 feature。这样以后加新 feature 或换掉某个业务,不会牵一发动全身。
第三,约定「入口」。每个 feature 尽量有一个 index.dart 或明确入口文件,对外只 export 需要被路由或其它模块引用的部分,内部实现细节不暴露。这样依赖关系清晰,重构时也容易缩小影响面。
第四,文档化。在项目根目录或 lib/ 下放一个简单的 STRUCTURE.md,说明「业务模块有哪些、公共层放什么、新加一个 feature 要建哪些目录、命名约定」。新人按文档操作即可,减少「随便建个文件夹」的随意性。
一个可参考的目录示例(按上述规范收敛后)许可长这样:
lib/
├── core/ # 公共层,被各 feature 依赖
│ ├── network/
│ ├── router/
│ ├── storage/
│ └── widgets/ # 通用按钮、弹窗等
├── features/
│ ├── order/
│ │ ├── screens/ # 订单列表、详情、创建等页
│ │ ├── widgets/ # 订单相关卡片、表单等
│ │ ├── models/
│ │ ├── services/
│ │ └── index.dart # 只 export 路由需要的 Page、对外模型
│ ├── user/
│ │ ├── screens/
│ │ ├── widgets/
│ │ ├── models/
│ │ ├── services/
│ │ └── index.dart
│ └── product/
│ └── ...
└── app.dart # 入口,注册路由、主题等
解析:core 里不放业务逻辑,只放「大家都要用」的能力,这样新加或替换某个 feature 时,不会动到 core。每个 feature 内部结构一致(screens/widgets/models/services),新人加「订单下的新页」就知道去 order/screens/ 新建文件。index.dart 只对外暴露路由用到的页面和少量对外模型,避免其它模块直接 import 'order/xxx/internal.dart',以后改内部实现不会波及全局。按这种约定扩展,长期迭代时改动的范围更容易控制。
实际项目演进中的取舍
真实项目里,很少能从零就按「理想结构」来,多半是边做边收拢。
初期:功能少、人少,用类型分(models/views/services)就能跑。这时不必强行上 Feature First,但可以约定:新增功能时,尽量在一个「功能子目录」下集中放(例如 views/order/、services/order/),为以后拆成独立 feature 留余地。
中期:功能多了、改一处动多处的情况开始出现,就能够选 1~2 个最核心、最常改的业务,先拆成 feature 目录,把相关页面、模型、接口都迁进去。其它暂时保持原样,用「新代码按新结构、老代码逐步迁」的方式演进,避免一次性大重构。
长期:核心业务都进 feature,公共能力稳定在 core/shared,再配合「入口 export」和依赖方向约定(feature 不互相依赖),就能比较稳地支撑长期迭代。遇到「这段到底算哪个 feature」的争议,以「谁改得多、谁负责」为准来归属,再慢慢用边界清晰化。
总结
Flutter 工程要支撑长期迭代,结构上建议:用 Feature First 收拢业务,用 core/shared 收拢公共能力;区分清楚页面级和业务级模块,先按业务划大目录再在内部按页面或子功能细分;并约定好目录层级、命名、入口和依赖方向,再配一份简短的结构说明文档。这样初期不会过度设计,后期也有清晰的扩展路径,改起来敢动、新人接手成本也会低很多。
