Flutter 完全组件化的项目结构设计实践

发布于:2025-09-02 ⋅ 阅读:(19) ⋅ 点赞:(0)

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 项目中,它能带来巨大的维护优势。本文给出的结构是一种通用的实践方案,你可以根据团队规模和业务复杂度,灵活调整。