Flutter 完全组件化的项目结构设计实践
在做 Flutter 项目的时候,随着业务不断扩展,如果所有代码都堆在 lib/
目录里,后期维护会越来越痛苦。组件化(Componentization) 是一种常见的解决方案,它能让项目更清晰、更易扩展,团队协作也会更高效。本文结合实践,分享一种 Flutter 完全组件化的项目结构设计方案。
为什么要组件化?
- 高内聚,低耦合:每个模块(feature)独立,边界清晰。
- 多人协作:不同的功能模块可以交给不同的开发同学,互不干扰。
- 可复用性:部分模块可以直接复用到其他项目中。
- 可维护性:改动时只需要关注单个模块,降低风险。
顶层目录结构
假设我们的项目叫 my_app
,整体结构可以这样设计:
my_app/
├── apps/ # 主应用(App 容器)
│ └── main_app/ # 真正运行的壳工程
├── core/ # 核心基础层(工具 & SDK)
│ ├── network/ # 网络封装(dio/http)
│ ├── database/ # 本地存储(sqflite/hive)
│ ├── common_ui/ # 公共UI组件(按钮、弹窗、空页面)
│ ├── utils/ # 工具类(日志、加密、日期)
│ └── theme/ # 全局主题(颜色、文字样式)
├── features/ # 各业务功能模块
│ ├── home/ # 首页
│ ├── video/ # 视频模块(短视频/播放器)
│ ├── chat/ # 聊天模块
│ └── profile/ # 用户中心
├── shared/ # 跨模块共享的模型、服务
│ ├── models/ # 数据实体
│ └── services/ # 公共 Service(用户、配置、埋点)
├── plugins/ # 自研 Flutter 插件(原生能力)
│ ├── photo_picker/ # 相册选择插件
│ └── short_video_player # 短视频播放器插件
├── pubspec.yaml # 顶层依赖管理
└── README.md
Feature 模块内部结构
以 features/video/
模块为例:
video/
├── lib/
│ ├── src/
│ │ ├── pages/ # 页面
│ │ ├── widgets/ # 模块内私有 widget
│ │ ├── controllers/ # 状态管理(GetX/Bloc)
│ │ ├── services/ # 数据仓库(repository)
│ │ └── models/ # 模块内的数据模型
│ └── video.dart # 对外暴露的统一入口(类似 index.dart)
├── pubspec.yaml # 模块独立依赖
设计要点:
- 模块内自成体系,拥有页面、状态、数据。
- 只暴露
video.dart
给外部使用,内部实现不对外开放。 - 可以被其他项目直接引入复用。
模块间通信方式
1.路由解耦(基于 GetX)
使用 GetX
的命名路由来统一管理,避免模块间直接依赖页面类。
在 apps/main_app
中维护一个全局路由表,例如:
// app_routes.dart
class AppRoutes {static const home = '/home';static const videoDetail = '/videoDetail';static const chat = '/chat';static const profile = '/profile';}
路由配置集中在 GetMaterialApp 中:
// main.dart
import 'package:get/get.dart';
import 'app_routes.dart';void main() {runApp(GetMaterialApp(initialRoute: AppRoutes.home,getPages: [GetPage(name: AppRoutes.home, page: () => const HomePage()),GetPage(name: AppRoutes.videoDetail, page: () => const VideoDetailPage()),GetPage(name: AppRoutes.chat, page: () => const ChatPage()),GetPage(name: AppRoutes.profile, page: () => const ProfilePage()),],),);
}
业务模块跳转时,只需要依赖路由常量,而不是直接依赖页面类:
Get.toNamed('${AppRoutes.videoDetail}?id=$videoId');
这样一来,features/video 模块的内部页面不会被外部直接 import,达到解耦目的。
2. 数据传递
使用 Get.arguments 或事件总线(EventBus/Stream/RxBus)来传递参数,而不是直接依赖模块。
// 跳转时传参
Get.toNamed(AppRoutes.videoDetail, arguments: {'id': videoId});
// 接收参数
final args = Get.arguments as Map;
final videoId = args['id'];
3.共享数据
放在 shared/ 或 core/services/ 中,使用 Get.find() 获取,避免 feature 之间直接耦合。
依赖管理策略
• 每个 feature 拥有独立的 pubspec.yaml,自行管理依赖。
• 模块之间禁止直接 import,只能依赖 core 和 shared。
• 顶层 pubspec.yaml 使用 dependency_overrides 来统一第三方依赖版本。
开发流程
1. 新需求 → 新建 features/xxx 模块。
2. 在 apps/main_app 中集成对应模块。
3. 公共逻辑沉淀到 core 或 shared,避免重复。
4. 原生能力统一封装到 plugins/,避免业务直接写 platform channel。
组件化带来的好处
• 团队协作更高效:模块独立,互不干扰。
• 扩展性强:新业务只需新增模块,不会污染现有代码。
• 维护成本低:定位 bug 或修改逻辑时,只需关注单一模块。
• 跨项目可复用:一些业务模块或插件可以直接抽出来独立使用。
模块依赖关系图
这张图的含义:
• main_app 是壳应用,依赖所有 features。
• 各个 features 只能依赖 core 和 shared,不能互相依赖。
• 插件 plugins 可以被某些 feature 调用,但也只依赖 core 公共能力。
总结
组件化不是 Flutter 独有的概念,但在中大型 Flutter 项目中,它能带来巨大的维护优势。本文给出的结构是一种通用的实践方案,你可以根据团队规模和业务复杂度,灵活调整。