在Flutter开发中,项目的"可维护性、可扩展性、可复用性"是长期迭代的核心,而规范的项目结构、合理的模块化、组件化、插件化设计,正是实现这一目标的关键。新手往往随手创建文件、堆砌代码,初期看似高效,一旦项目迭代、团队协作,就会陷入"找文件半小时、改代码牵一发而动全身"的困境;而中大型项目若缺乏进阶的组件化、插件化设计,会出现模块耦合严重、复用性差、跨项目迁移困难等问题。
本文将从「规范项目结构」「模块化设计核心」「组件化进阶实现」「插件化落地技巧」四个维度,结合具体代码示例,覆盖基础结构、业务模块化、公共组件化、第三方插件封装,衔接前序异步状态统一处理的内容,让项目规范从基础到进阶形成闭环,新手可直接套用,资深开发者可参考进阶思路。
核心原则:职责单一、高内聚低耦合、可复用、可扩展,让每个文件、每个模块、每个组件、每个插件都有明确的作用,避免代码冗余和逻辑混乱,同时兼顾开发效率与长期维护成本。
一、先避坑:新手常见的项目结构与设计误区
很多新手刚开始开发Flutter项目,会出现这样的结构(反面示例):
lib/
├─ main.dart
├─ home.dart
├─ login.dart
├─ goods_list.dart
├─ http_request.dart
├─ user_info.dart
├─ loading_widget.dart
├─ utils.dart
这种结构的问题很明显,同时也是模块化、组件化、插件化设计缺失的前兆:
-
无分类:页面、工具、请求、组件混在一起,文件越多越难查找;
-
耦合严重:页面直接依赖请求、工具类,修改一个地方需要改动多个页面,未实现"模块化解耦";
-
不可复用:重复编写相似组件、请求逻辑,未将可复用部分封装为组件或插件,冗余代码过多;
-
维护困难:团队协作时,多人修改同一类文件易冲突,后期迭代成本高;
-
无进阶设计:未区分组件与模块、未封装插件,导致项目难以扩展、跨项目复用困难。
而规范的项目结构+模块化、组件化、插件化设计,就是要解决这些问题,让项目"有条理、好维护、可扩展、可复用"。
二、规范Flutter项目结构(通用版,适配模块化/组件化/插件化)
无论是小型项目还是中大型项目,都可以基于以下结构扩展,核心是"按功能分类、按职责拆分",同时预留组件化、插件化的扩展空间,以下是最常用的规范结构(附详细说明):
lib/
├─ main.dart # 项目入口,初始化路由、全局配置(如GetX、主题、插件)
├─ app/ # 应用核心配置
│ ├─ routes/ # 路由管理(统一配置所有页面路由)
│ ├─ theme/ # 主题配置(亮色/暗色主题、全局样式)
│ └─ global/ # 全局配置(全局变量、初始化方法)
├─ pages/ # 业务页面(按模块拆分文件夹,仅放UI代码)
│ ├─ login/ # 登录模块页面
│ ├─ home/ # 首页模块页面
│ ├─ goods/ # 商品模块页面
│ └─ profile/ # 个人中心模块页面
├─ components/ # 公共组件(组件化核心,全局可复用,非业务相关)
│ ├─ common/ # 基础公共组件(按钮、输入框、加载框等)
│ ├─ layout/ # 布局组件(页面框架、导航栏等)
│ └─ state/ # 状态相关组件(如前序的AsyncStateWidget)
├─ modules/ # 业务模块化核心(拆分独立业务逻辑,与UI分离)
│ ├─ login/ # 登录业务模块(控制器、模型、请求)
│ ├─ goods/ # 商品业务模块(控制器、模型、请求)
│ └─ user/ # 用户业务模块(控制器、模型、请求)
├─ network/ # 网络请求封装(支撑模块,统一请求拦截、异常处理)
├─ utils/ # 工具类(支撑模块,通用工具,如日期、字符串、存储)
├─ models/ # 数据模型(支撑模块,全局通用数据模型,如用户、商品模型)
└─ plugins/ # 插件化核心(封装第三方插件、自定义插件,全局复用)
├─ toast/ # 提示插件(封装fluttertoast)
├─ permission/ # 权限插件(封装permission_handler)
└─ storage/ # 存储插件(封装shared_preferences)
关键说明:
-
「pages」和「modules」分离:pages只放页面UI代码,业务逻辑(请求、状态管理)全部放在modules,实现"UI与逻辑分离",是模块化的基础;
-
「components」:组件化的核心载体,只放全局可复用组件,避免每个页面重复编写相似组件,同时组件需具备"高内聚、低耦合",不依赖具体业务逻辑;
-
「network」「utils」「models」:支撑模块,为模块化、组件化、插件化提供基础服务,统一封装,全局复用;
-
「plugins」:插件化的核心载体,将第三方插件或自定义功能封装为独立插件,隔离第三方依赖,便于替换、升级和跨项目复用;
-
结构可扩展:随着项目规模扩大,可新增「router_modules」「theme_modules」等,适配更复杂的模块化、组件化需求。
三、模块化设计核心:拆分与通信(基础)
模块化设计是组件化、插件化的基础,核心是"拆分"------将项目拆分为多个独立的、低耦合的模块,每个模块负责一个具体的业务或功能,模块之间通过统一的方式通信,互不干扰。
Flutter模块化主要分为3类,结合实战示例详细说明(衔接前序内容,补充完善):
1. 公共组件模块化(基础模块,为组件化铺垫)
作用:将全局可复用的组件封装为独立模块,避免重复开发,统一UI风格,为后续组件化提供基础。
示例:封装"全局加载组件""统一按钮组件",结合上一篇的异步状态组件,整合到components模块中(组件化的基础形态)。
以「统一按钮组件」为例(components/common/btn/primary_btn.dart):
import 'package:flutter/material.dart';
// 全局统一主按钮组件(可复用,支持自定义文本、点击事件、禁用状态)
// 组件特点:不依赖任何业务逻辑,仅接收外部参数,实现高内聚低耦合
class PrimaryBtn extends StatelessWidget {
final String text; // 按钮文本
final VoidCallback onPressed; // 点击事件(外部传入,不耦合业务)
final bool isDisabled; // 是否禁用
final double? width; // 按钮宽度(可选)
const PrimaryBtn({
super.key,
required this.text,
required this.onPressed,
this.isDisabled = false,
this.width,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: width ?? double.infinity,
child: ElevatedButton(
onPressed: isDisabled ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: isDisabled ? Colors.grey[300] : Colors.blue,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
text,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
);
}
}
// 用法示例(任何页面均可直接导入使用,不依赖具体业务)
// PrimaryBtn(
// text: "登录",
// onPressed: () {}, // 业务逻辑由外部传入
// isDisabled: false,
// );
优势:所有页面的按钮样式统一,后期修改按钮颜色、圆角,只需修改这一个文件,无需逐个页面调整;同时组件不耦合业务,为后续组件化复用打下基础。
2. 业务逻辑模块化(核心模块)
作用:将每个业务模块(如登录、商品、用户)的逻辑、请求、状态管理拆分独立,实现"业务解耦",便于单独开发、测试和迭代,同时为组件化提供业务支撑。
核心原则:一个业务模块对应一个文件夹,包含「控制器(状态管理)、数据模型、请求方法」,不包含页面UI(页面放在pages对应文件夹),模块之间通过接口通信,不直接依赖。
示例:用户业务模块(modules/user/),结合上一篇的全局异步状态管理,实现用户登录、退出、获取用户信息的业务逻辑:
// modules/user/user_controller.dart(用户状态管理,GetX控制器)
import 'package:get/get.dart';
import 'package:your_project/network/http_client.dart';
import 'package:your_project/models/user_model.dart';
import 'package:your_project/components/state/async_state.dart'; // 上一篇封装的异步状态模型
class UserController extends GetxController {
// 异步状态:管理用户登录、获取信息的状态(全局共享)
final Rx<AsyncState<UserModel>> userState = AsyncState.idle().obs;
// 登录请求(业务逻辑,不耦合UI)
Future<void> login(String username, String password) async {
userState.value = AsyncState.loading(); // 加载中状态
try {
// 调用network模块的统一请求方法(依赖支撑模块,不依赖UI)
final response = await HttpClient.post(
"/api/login",
data: {"username": username, "password": password},
);
// 转换为用户模型
UserModel user = UserModel.fromJson(response["data"]);
userState.value = AsyncState.success(user); // 成功状态
Get.offAllNamed("/home"); // 跳转首页(路由由全局配置管理)
} catch (e) {
userState.value = AsyncState.error(e.toString()); // 失败状态
}
}
// 退出登录(业务逻辑)
void logout() {
userState.value = AsyncState.idle(); // 重置状态
Get.offAllNamed("/login"); // 跳转登录页
}
// 获取用户信息(业务逻辑)
Future<void> fetchUserInfo() async {
userState.value = AsyncState.loading();
try {
final response = await HttpClient.get("/api/user/info");
UserModel user = UserModel.fromJson(response["data"]);
userState.value = AsyncState.success(user);
} catch (e) {
userState.value = AsyncState.error(e.toString());
}
}
}
// modules/user/user_api.dart(用户相关请求方法,单独拆分,便于复用)
import 'package:your_project/network/http_client.dart';
class UserApi {
// 登录请求(单独封装,便于复用和修改)
static Future<Map<String, dynamic>> login(String username, String password) async {
return await HttpClient.post(
"/api/login",
data: {"username": username, "password": password},
);
}
// 获取用户信息请求
static Future<Map<String, dynamic>> getUserInfo() async {
return await HttpClient.get("/api/user/info");
}
}
对应的页面UI(pages/login/login_page.dart),只负责渲染UI,不写业务逻辑,通过调用模块控制器实现业务交互:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:your_project/components/common/btn/primary_btn.dart';
import 'package:your_project/components/state/async_state_widget.dart';
import 'package:your_project/modules/user/user_controller.dart';
class LoginPage extends StatelessWidget {
// 获取用户业务模块的控制器(不直接写业务逻辑,低耦合)
final UserController userController = Get.put(UserController());
final TextEditingController usernameCtrl = TextEditingController();
final TextEditingController passwordCtrl = TextEditingController();
LoginPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("登录")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: usernameCtrl,
hintText: "请输入用户名",
),
const SizedBox(height: 16),
TextField(
controller: passwordCtrl,
hintText: "请输入密码",
obscureText: true,
),
const SizedBox(height: 24),
// 复用公共按钮组件(组件化复用)
PrimaryBtn(
text: "登录",
onPressed: () {
// 调用控制器的业务方法,不写具体逻辑(模块化解耦)
userController.login(
usernameCtrl.text,
passwordCtrl.text,
);
},
// 根据异步状态判断是否禁用按钮(状态由模块管理)
isDisabled: userController.userState.value.type == AsyncStateType.loading,
),
const SizedBox(height: 20),
// 复用异步状态组件,展示登录状态(组件化复用)
Obx(() {
return AsyncStateWidget<UserModel>(
state: userController.userState.value,
onSuccess: (data) => const SizedBox.shrink(),
onError: (state) => Center(
child: Text(
state.errorMsg ?? "登录失败",
style: const TextStyle(color: Colors.red),
),
),
);
}),
],
),
),
);
}
}
优势:业务逻辑与UI完全分离,修改登录请求地址、状态处理逻辑,只需修改modules/user下的文件,不影响登录页面UI;其他页面需要用到用户相关逻辑,直接导入UserController即可,无需重复编写;同时为组件化提供了业务支撑,组件可通过调用模块控制器实现业务交互。
3. 工具/网络模块化(支撑模块)
作用:将网络请求、工具类等支撑性功能封装为独立模块,统一管理,便于全局复用和维护,为模块化、组件化、插件化提供基础服务。
示例1:网络请求模块化(network/http_client.dart),统一封装请求拦截、异常处理:
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:your_project/utils/toast_util.dart';
// 全局网络请求封装(模块化,所有请求都走这里)
class HttpClient {
static final Dio _dio = Dio();
// 初始化请求配置(拦截器、基础地址、超时时间)
static void init() {
// 基础地址(可根据环境切换)
_dio.options.baseUrl = "https://api.example.com";
_dio.options.connectTimeout = const Duration(milliseconds: 5000);
_dio.options.receiveTimeout = const Duration(milliseconds: 5000);
// 请求拦截器(添加token、请求头)
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
// 从全局配置中获取token,添加到请求头
String? token = Get.find<GlobalController>().token.value;
if (token != null) {
options.headers["Authorization"] = "Bearer $token";
}
return handler.next(options);
},
// 响应拦截器(统一处理错误)
onResponse: (response, handler) {
// 接口返回错误(如code != 200)
if (response.data["code"] != 200) {
ToastUtil.showError(response.data["msg"] ?? "请求失败");
return handler.reject(DioException(
requestOptions: response.requestOptions,
message: response.data["msg"],
));
}
return handler.next(response);
},
// 错误拦截器(网络错误、超时等)
onError: (DioException e, handler) {
String errorMsg = "网络异常,请检查网络后重试";
if (e.type == DioExceptionType.connectionTimeout) {
errorMsg = "请求超时,请稍后重试";
} else if (e.type == DioExceptionType.receiveTimeout) {
errorMsg = "接收数据超时,请稍后重试";
}
ToastUtil.showError(errorMsg);
return handler.next(e);
},
));
}
// GET请求(统一封装)
static Future<Map<String, dynamic>> get(
String path, {
Map<String, dynamic>? queryParameters,
}) async {
final response = await _dio.get(
path,
queryParameters: queryParameters,
);
return response.data;
}
// POST请求(统一封装)
static Future<Map<String, dynamic>> post(
String path, {
Map<String, dynamic>? data,
}) async {
final response = await _dio.post(
path,
data: data,
);
return response.data;
}
}
示例2:工具类模块化(utils/toast_util.dart),统一封装提示框(为后续插件化铺垫):
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
// 全局提示工具(模块化,所有页面统一使用)
// 后续可升级为插件,隔离第三方依赖(fluttertoast)
class ToastUtil {
// 成功提示
static void showSuccess(String msg) {
Fluttertoast.showToast(
msg: msg,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.green,
textColor: Colors.white,
);
}
// 错误提示
static void showError(String msg) {
Fluttertoast.showToast(
msg: msg,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.red,
textColor: Colors.white,
);
}
// 普通提示
static void showInfo(String msg) {
Fluttertoast.showToast(
msg: msg,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.grey,
textColor: Colors.white,
);
}
}
四、组件化设计进阶:从"复用"到"独立"
组件化是模块化的延伸,核心是"将可复用的UI或功能封装为独立的、可插拔的组件",组件不仅能在当前项目中全局复用,还能跨项目复用,同时具备"高内聚、低耦合、可配置、可测试"的特点。
很多开发者会混淆"模块化"和"组件化",核心区别在于:模块化侧重"业务逻辑的拆分",组件化侧重"UI/功能的复用与独立";一个业务模块可以包含多个组件,一个组件可以被多个业务模块复用。
1. 组件化设计的核心要求
-
低耦合:组件不依赖具体业务逻辑,所有业务相关的交互通过"回调、参数传入"实现,避免直接引用业务模块;
-
高内聚:组件内部封装自身的UI、样式、简单交互逻辑,对外只暴露必要的接口(参数、回调);
-
可配置:组件支持通过参数自定义样式、文本、交互行为,适配不同使用场景;
-
可复用:组件可在当前项目多个页面、多个模块中复用,甚至可以提取为独立的组件库,跨项目复用。
2. 组件化实战示例(进阶版)
基于前面的公共按钮组件,升级为"可配置、高复用"的组件化示例,同时新增"用户信息卡片组件",演示组件化的完整实现。
示例1:进阶版统一按钮组件(components/common/btn/primary_btn.dart),支持更多配置:
import 'package:flutter/material.dart';
// 组件化进阶:可配置、高复用的主按钮组件
class PrimaryBtn extends StatelessWidget {
final String text; // 按钮文本(必传)
final VoidCallback onPressed; // 点击事件(必传)
final bool isDisabled; // 是否禁用(默认false)
final double? width; // 按钮宽度(可选)
final double? height; // 按钮高度(可选)
final Color? bgColor; // 背景色(可选,默认蓝色)
final Color? disabledBgColor; // 禁用状态背景色(可选)
final TextStyle? textStyle; // 文本样式(可选)
final double borderRadius; // 圆角(可选,默认8)
// 构造函数:必传参数在前,可选参数在后,提供默认值
const PrimaryBtn({
super.key,
required this.text,
required this.onPressed,
this.isDisabled = false,
this.width,
this.height,
this.bgColor = Colors.blue,
this.disabledBgColor = Colors.grey[300],
this.textStyle,
this.borderRadius = 8.0,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: width ?? double.infinity,
height: height ?? 48,
child: ElevatedButton(
onPressed: isDisabled ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: isDisabled ? disabledBgColor : bgColor,
padding: EdgeInsets.zero, // 取消默认内边距,便于自定义
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius),
),
elevation: isDisabled ? 0 : 2, // 禁用状态取消阴影
),
child: Text(
text,
style: textStyle ?? const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
);
}
}
// 组件化用法示例(适配不同场景)
// 场景1:默认样式(登录按钮)
// PrimaryBtn(
// text: "登录",
// onPressed: () {},
// );
// 场景2:自定义颜色、圆角(提交按钮)
// PrimaryBtn(
// text: "提交",
// onPressed: () {},
// bgColor: Colors.green,
// borderRadius: 12,
// );
// 场景3:自定义大小、文本样式(小按钮)
// PrimaryBtn(
// text: "取消",
// onPressed: () {},
// width: 100,
// height: 36,
// textStyle: TextStyle(fontSize: 14),
// );
示例2:用户信息卡片组件(components/common/card/user_info_card.dart),组件化封装,不依赖具体业务:
import 'package:flutter/material.dart';
import 'package:your_project/models/user_model.dart';
// 组件化:用户信息卡片组件(可复用,不耦合业务)
// 接收用户模型参数,暴露点击头像的回调
class UserInfoCard extends StatelessWidget {
final UserModel user; // 用户信息(必传)
final VoidCallback? onAvatarTap; // 点击头像回调(可选)
final bool showRole; // 是否显示角色(可选,默认true)
final Color cardColor; // 卡片背景色(可选,默认白色)
const UserInfoCard({
super.key,
required this.user,
this.onAvatarTap,
this.showRole = true,
this.cardColor = Colors.white,
});
@override
Widget build(BuildContext context) {
return Card(
color: cardColor,
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
// 头像(可点击)
GestureDetector(
onTap: onAvatarTap,
child: CircleAvatar(
radius: 40,
backgroundImage: NetworkImage(user.avatar),
child: user.avatar.isEmpty ? const Icon(Icons.person) : null,
),
),
const SizedBox(width: 16),
// 用户信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user.username,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
"用户ID:${user.id}",
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
if (showRole)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.blue[100],
borderRadius: BorderRadius.circular(4),
),
child: Text(
user.role,
style: const TextStyle(
fontSize: 12,
color: Colors.blue,
),
),
),
),
],
),
),
],
),
),
);
}
}
// 组件用法示例(多个页面/模块可复用)
// UserInfoCard(
// user: userModel, // 外部传入用户数据,不耦合业务
// onAvatarTap: () {
// // 点击头像的业务逻辑,由外部传入
// Get.toNamed("/avatar/edit");
// },
// showRole: true,
// cardColor: Colors.grey[50],
// );
3. 组件化进阶:独立组件库(跨项目复用)
对于中大型项目或多项目开发,可将常用组件提取为独立的组件库(package),实现跨项目复用,步骤如下:
-
创建Flutter Package(File → New → New Flutter Project → Flutter Package);
-
将components文件夹下的公共组件(如PrimaryBtn、UserInfoCard)迁移到Package中;
-
在Package中完善组件的文档、示例、测试用例;
-
在主项目中通过pubspec.yaml引入该组件库,即可全局使用。
优势:跨项目复用组件,减少重复开发;统一组件版本,便于维护和升级;组件库可单独迭代,不影响主项目。
五、插件化设计:隔离依赖,灵活扩展
插件化是Flutter项目进阶的关键,核心是"将第三方依赖、原生功能、通用工具封装为独立的插件",实现"依赖隔离、灵活替换、跨项目复用",避免第三方依赖直接侵入业务代码,降低后期升级、替换依赖的成本。
组件化与插件化的区别:组件化侧重"UI/功能的复用",插件化侧重"依赖的隔离与封装";组件可以依赖插件,插件不依赖组件和业务模块,是独立的功能载体。
1. 插件化设计的核心场景
-
第三方插件封装:如fluttertoast、permission_handler、shared_preferences等,封装后隔离第三方API,便于替换;
-
原生功能封装:如原生相机、相册、推送等,通过MethodChannel封装为插件,供Flutter端统一调用;
-
通用工具插件:如日期工具、加密工具等,封装为插件,跨项目复用。
2. 插件化实战示例
以"提示插件""存储插件"为例,演示如何封装第三方插件,实现依赖隔离和灵活复用。
示例1:提示插件(plugins/toast/toast_plugin.dart),封装fluttertoast:
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
// 插件化:提示插件,封装fluttertoast,隔离第三方依赖
// 对外暴露统一接口,内部实现可随时替换(如替换为原生提示)
class ToastPlugin {
// 私有构造函数,禁止外部实例化(单例模式)
ToastPlugin._();
// 单例实例
static final ToastPlugin _instance = ToastPlugin._();
// 工厂方法,获取单例
factory ToastPlugin() => _instance;
// 成功提示(统一接口)
void showSuccess(String msg) {
_showToast(
msg: msg,
backgroundColor: Colors.green,
);
}
// 错误提示(统一接口)
void showError(String msg) {
_showToast(
msg: msg,
backgroundColor: Colors.red,
);
}
// 普通提示(统一接口)
void showInfo(String msg) {
_showToast(
msg: msg,
backgroundColor: Colors.grey,
);
}
// 私有方法:封装fluttertoast的具体实现
void _showToast({
required String msg,
required Color backgroundColor,
Toast length = Toast.LENGTH_SHORT,
ToastGravity gravity = ToastGravity.BOTTOM,
}) {
Fluttertoast.showToast(
msg: msg,
toastLength: length,
gravity: gravity,
backgroundColor: backgroundColor,
textColor: Colors.white,
fontSize: 14,
);
}
}
// 用法示例(全局统一调用,不直接依赖fluttertoast)
// ToastPlugin().showSuccess("操作成功");
// ToastPlugin().showError("操作失败");
// 若需替换为原生提示,只需修改插件内部的_showToast方法,无需修改业务代码
示例2:存储插件(plugins/storage/storage_plugin.dart),封装shared_preferences:
import 'package:shared_preferences/shared_preferences.dart';
// 插件化:存储插件,封装shared_preferences,隔离第三方依赖
class StoragePlugin {
// 单例模式
StoragePlugin._();
static final StoragePlugin _instance = StoragePlugin._();
factory StoragePlugin() => _instance;
// 私有变量:shared_preferences实例
late SharedPreferences _prefs;
// 初始化(在main.dart中调用)
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
// 存储String类型数据(统一接口)
Future<bool> setString(String key, String value) async {
return await _prefs.setString(key, value);
}
// 获取String类型数据(统一接口)
String? getString(String key) {
return _prefs.getString(key);
}
// 存储bool类型数据(统一接口)
Future<bool> setBool(String key, bool value) async {
return await _prefs.setBool(key, value);
}
// 获取bool类型数据(统一接口)
bool? getBool(String key) {
return _prefs.getBool(key);
}
// 移除指定key的数据
Future<bool> remove(String key) async {
return await _prefs.remove(key);
}
// 清空所有存储
Future<bool> clear() async {
return await _prefs.clear();
}
}
// 用法示例(全局统一调用)
// 初始化(main.dart)
// await StoragePlugin().init();
// 存储数据
// await StoragePlugin().setString("token", "123456");
// 获取数据
// String? token = StoragePlugin().getString("token");
// 若需替换为其他存储方式(如Hive),只需修改插件内部实现,不影响业务代码
3. 插件化进阶:独立插件包(跨项目复用)
与组件库类似,常用的插件也可以提取为独立的Flutter Package,实现跨项目复用,例如将ToastPlugin、StoragePlugin封装为独立插件包,在多个项目中通过pubspec.yaml引入,统一维护和升级。
关键优势:隔离第三方依赖,降低替换成本;跨项目复用,减少重复开发;统一插件版本,便于维护。
六、模块化、组件化、插件化的关系与通信
1. 三者核心关系
模块化是基础,组件化是模块化的延伸(UI/功能复用),插件化是支撑(依赖隔离、原生功能封装),三者协同工作,构成一个高可维护、高可扩展的Flutter项目架构:
-
模块化:拆分业务逻辑,实现"业务解耦";
-
组件化:封装可复用UI/功能,依赖模块化提供的业务逻辑,实现"UI复用";
-
插件化:封装第三方依赖、原生功能,为模块化、组件化提供基础服务,实现"依赖隔离"。
2. 跨模块/组件/插件通信(低耦合关键)
模块化、组件化、插件化拆分后,各模块、组件、插件之间需要通信,核心是"不直接依赖,通过中间层通信",推荐4种常用方式:
-
状态管理工具(推荐GetX):如UserController,通过GetX的obs监听状态,其他模块、组件只需获取控制器,即可监听状态变化,无需直接依赖;
-
回调函数:组件与模块、组件与组件之间,通过回调函数传递交互逻辑(如UserInfoCard的onAvatarTap回调);
-
事件总线(EventBus):适合跨模块、跨组件、跨插件的无直接关联通信(如商品模块修改商品数量,通知购物车模块、统计插件更新);
-
接口抽象(面向接口编程):定义抽象类,模块、插件之间通过接口通信,避免直接依赖具体实现(适合大型项目)。
示例:组件与模块通信(UserInfoCard组件与UserController模块):
// pages/profile/profile_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:your_project/components/common/card/user_info_card.dart';
import 'package:your_project/modules/user/user_controller.dart';
import 'package:your_project/plugins/toast/toast_plugin.dart';
class ProfilePage extends StatelessWidget {
final UserController userController = Get.find();
ProfilePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("个人中心")),
body: Obx(() {
return userController.userState.value.when(
idle: () => const Center(child: Text("请先登录")),
loading: () => const Center(child: CircularProgressIndicator()),
success: (user) => Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 组件与模块通信:组件接收模块数据,通过回调传递交互逻辑
UserInfoCard(
user: user,
onAvatarTap: () {
// 点击头像,调用模块的业务方法
ToastPlugin().showInfo("修改头像");
Get.toNamed("/avatar/edit");
},
),
const SizedBox(height: 24),
// 复用组件化按钮,调用模块的退出登录方法
PrimaryBtn(
text: "退出登录",
onPressed: userController.logout,
bgColor: Colors.red,
),
],
),
),
error: (msg) => Center(child: Text("获取用户信息失败:$msg")),
empty: () => const Center(child: Text("暂无用户信息")),
);
}),
);
}
}
七、实战总结:从规范到进阶的落地建议
1. 核心优势(模块化+组件化+插件化)
-
可维护性:模块、组件、插件职责清晰,修改一个部分不影响其他部分,后期迭代更高效;
-
可复用性:公共组件、插件可跨页面、跨模块、跨项目复用,减少冗余代码,提高开发效率;
-
可扩展性:新增业务时,只需新增对应的模块和组件;新增功能时,只需封装对应的插件,无需修改现有代码;
-
低耦合:模块、组件、插件之间不直接依赖,通过统一方式通信,降低后期维护和升级成本;
-
团队协作:多人可同时开发不同模块、组件、插件,减少代码冲突,提升协作效率。
2. 落地建议(新手到资深)
-
新手阶段:先规范项目结构,实现基础模块化,掌握"UI与逻辑分离",避免代码混乱;
-
进阶阶段:封装公共组件,实现组件化复用,掌握组件的"高内聚、低耦合"设计;
-
资深阶段:封装插件,实现依赖隔离,提取组件库、插件包,适配中大型项目和多项目开发;
-
避免过度设计:模块化、组件化、插件化拆分以"实用"为原则,不要过度拆分(如一个按钮单独做一个插件);
-
统一规范:文件、类、方法命名统一,组件、插件的接口设计统一,便于团队协作和维护;
-
结合状态管理:模块化、组件化、插件化与GetX等状态管理工具结合,实现状态与UI、业务逻辑的彻底分离。
最后,本文的项目结构、模块化、组件化、插件化示例,可直接套用在你的Flutter项目中,结合上一篇的异步状态统一处理,就能搭建出规范、易维护、可扩展的项目架构。随着项目规模扩大,可在此基础上进一步优化,如实现组件懒加载、插件动态注册等进阶功能。