附录B - 项目结构最佳实践
嗨,朋友!我是长安。
当你的 Flutter 项目越来越大时,良好的项目结构就变得尤为重要。它能让你的代码更易读、更易维护、更易扩展。
这一章,我会介绍从小型到大型项目的结构组织方式,以及常用的架构模式!
📁 小型项目结构
适合个人项目、Demo 或功能简单的 App。
my_app/
├── lib/
│ ├── main.dart # 入口文件
│ ├── pages/ # 页面
│ │ ├── home_page.dart
│ │ ├── login_page.dart
│ │ └── profile_page.dart
│ ├── widgets/ # 自定义组件
│ │ ├── custom_button.dart
│ │ └── user_card.dart
│ ├── models/ # 数据模型
│ │ └── user.dart
│ ├── services/ # 服务(网络、存储等)
│ │ └── api_service.dart
│ └── utils/ # 工具类
│ └── constants.dart
├── assets/ # 资源文件
│ ├── images/
│ └── fonts/
├── test/ # 测试
└── pubspec.yaml
示例代码
// lib/main.dart
import 'package:flutter/material.dart';
import 'pages/home_page.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
home: const HomePage(),
);
}
}
// lib/pages/home_page.dart
import 'package:flutter/material.dart';
import '../widgets/user_card.dart';
import '../services/api_service.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _apiService = ApiService();
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('首页')),
body: const Center(child: Text('Hello')),
);
}
}
📂 中型项目结构
适合有一定规模的项目,引入了状态管理和更细致的分层。
my_app/
├── lib/
│ ├── main.dart
│ ├── app.dart # App 配置(主题、路由等)
│ │
│ ├── config/ # 配置
│ │ ├── routes.dart # 路由配置
│ │ ├── themes.dart # 主题配置
│ │ └── constants.dart # 常量
│ │
│ ├── core/ # 核心功能
│ │ ├── network/ # 网络层
│ │ │ ├── api_client.dart
│ │ │ ├── api_endpoints.dart
│ │ │ └── interceptors.dart
│ │ ├── storage/ # 存储层
│ │ │ └── local_storage.dart
│ │ └── utils/ # 工具类
│ │ ├── validators.dart
│ │ └── formatters.dart
│ │
│ ├── data/ # 数据层
│ │ ├── models/ # 数据模型
│ │ │ ├── user.dart
│ │ │ └── product.dart
│ │ └── repositories/ # 数据仓库
│ │ ├── user_repository.dart
│ │ └── product_repository.dart
│ │
│ ├── providers/ # 状态管理(Provider/GetX)
│ │ ├── auth_provider.dart
│ │ └── cart_provider.dart
│ │
│ ├── pages/ # 页面
│ │ ├── auth/
│ │ │ ├── login_page.dart
│ │ │ └── register_page.dart
│ │ ├── home/
│ │ │ └── home_page.dart
│ │ └── profile/
│ │ └── profile_page.dart
│ │
│ └── widgets/ # 全局组件
│ ├── common/ # 通用组件
│ │ ├── loading_widget.dart
│ │ └── error_widget.dart
│ └── buttons/
│ └── primary_button.dart
│
├── assets/
├── test/
└── pubspec.yaml
Repository 模式示例
// lib/data/models/user.dart
class User {
final int id;
final String name;
final String email;
final String? avatar;
User({
required this.id,
required this.name,
required this.email,
this.avatar,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
avatar: json['avatar'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'avatar': avatar,
};
}
}
// lib/data/repositories/user_repository.dart
import '../models/user.dart';
import '../../core/network/api_client.dart';
class UserRepository {
final ApiClient _apiClient;
UserRepository(this._apiClient);
// 获取用户信息
Future<User> getUser(int id) async {
final response = await _apiClient.get('/users/$id');
return User.fromJson(response.data);
}
// 更新用户信息
Future<User> updateUser(int id, Map<String, dynamic> data) async {
final response = await _apiClient.put('/users/$id', data: data);
return User.fromJson(response.data);
}
// 获取用户列表
Future<List<User>> getUsers() async {
final response = await _apiClient.get('/users');
return (response.data as List)
.map((json) => User.fromJson(json))
.toList();
}
}
// lib/providers/auth_provider.dart
import 'package:flutter/material.dart';
import '../data/models/user.dart';
import '../data/repositories/user_repository.dart';
class AuthProvider extends ChangeNotifier {
final UserRepository _userRepository;
User? _currentUser;
bool _isLoading = false;
String? _error;
AuthProvider(this._userRepository);
User? get currentUser => _currentUser;
bool get isLoading => _isLoading;
bool get isLoggedIn => _currentUser != null;
String? get error => _error;
Future<void> login(String email, String password) async {
_isLoading = true;
_error = null;
notifyListeners();
try {
// 模拟登录逻辑
_currentUser = await _userRepository.getUser(1);
} catch (e) {
_error = e.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
void logout() {
_currentUser = null;
notifyListeners();
}
}
🏗️ 大型项目结构(功能模块划分)
适合团队协作的大型项目,按功能模块划分代码。
my_app/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ │
│ ├── core/ # 核心模块(全局共享)
│ │ ├── constants/
│ │ ├── theme/
│ │ ├── network/
│ │ ├── storage/
│ │ ├── utils/
│ │ └── widgets/ # 全局通用组件
│ │
│ ├── features/ # 功能模块
│ │ │
│ │ ├── auth/ # 认证模块
│ │ │ ├── data/
│ │ │ │ ├── models/
│ │ │ │ ├── repositories/
│ │ │ │ └── datasources/
│ │ │ ├── domain/
│ │ │ │ └── usecases/
│ │ │ └── presentation/
│ │ │ ├── pages/
│ │ │ ├── widgets/
│ │ │ └── providers/
│ │ │
│ │ ├── home/ # 首页模块
│ │ │ ├── data/
│ │ │ ├── domain/
│ │ │ └── presentation/
│ │ │
│ │ ├── product/ # 商品模块
│ │ │ ├── data/
│ │ │ ├── domain/
│ │ │ └── presentation/
│ │ │
│ │ ├── cart/ # 购物车模块
│ │ │ ├── data/
│ │ │ ├── domain/
│ │ │ └── presentation/
│ │ │
│ │ ├── order/ # 订单模块
│ │ │ ├── data/
│ │ │ ├── domain/
│ │ │ └── presentation/
│ │ │
│ │ └── profile/ # 个人中心模块
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ │
│ └── shared/ # 共享模块
│ ├── models/ # 共享数据模型
│ ├── widgets/ # 共享组件
│ └── services/ # 共享服务
│
├── assets/
├── test/
│ ├── unit/ # 单元测试
│ ├── widget/ # Widget 测试
│ └── integration/ # 集成测试
└── pubspec.yaml
功能模块示例
// lib/features/product/data/models/product.dart
class Product {
final int id;
final String name;
final String description;
final double price;
final String imageUrl;
final int stock;
Product({
required this.id,
required this.name,
required this.description,
required this.price,
required this.imageUrl,
required this.stock,
});
factory Product.fromJson(Map<String, dynamic> json) {
return Product(
id: json['id'],
name: json['name'],
description: json['description'],
price: (json['price'] as num).toDouble(),
imageUrl: json['image_url'],
stock: json['stock'],
);
}
bool get isInStock => stock > 0;
}
// lib/features/product/data/repositories/product_repository.dart
import '../models/product.dart';
import '../../../../core/network/api_client.dart';
abstract class ProductRepository {
Future<List<Product>> getProducts();
Future<Product> getProductById(int id);
Future<List<Product>> searchProducts(String query);
}
class ProductRepositoryImpl implements ProductRepository {
final ApiClient _apiClient;
ProductRepositoryImpl(this._apiClient);
Future<List<Product>> getProducts() async {
final response = await _apiClient.get('/products');
return (response.data as List)
.map((json) => Product.fromJson(json))
.toList();
}
Future<Product> getProductById(int id) async {
final response = await _apiClient.get('/products/$id');
return Product.fromJson(response.data);
}
Future<List<Product>> searchProducts(String query) async {
final response = await _apiClient.get('/products/search',
queryParameters: {'q': query},
);
return (response.data as List)
.map((json) => Product.fromJson(json))
.toList();
}
}
// lib/features/product/presentation/providers/product_provider.dart
import 'package:flutter/material.dart';
import '../../data/models/product.dart';
import '../../data/repositories/product_repository.dart';
class ProductProvider extends ChangeNotifier {
final ProductRepository _repository;
ProductProvider(this._repository);
List<Product> _products = [];
Product? _selectedProduct;
bool _isLoading = false;
String? _error;
List<Product> get products => _products;
Product? get selectedProduct => _selectedProduct;
bool get isLoading => _isLoading;
String? get error => _error;
Future<void> loadProducts() async {
_isLoading = true;
_error = null;
notifyListeners();
try {
_products = await _repository.getProducts();
} catch (e) {
_error = '加载商品失败:$e';
} finally {
_isLoading = false;
notifyListeners();
}
}
Future<void> loadProductDetail(int id) async {
_isLoading = true;
_error = null;
notifyListeners();
try {
_selectedProduct = await _repository.getProductById(id);
} catch (e) {
_error = '加载商品详情失败:$e';
} finally {
_isLoading = false;
notifyListeners();
}
}
Future<void> searchProducts(String query) async {
if (query.isEmpty) {
await loadProducts();
return;
}
_isLoading = true;
_error = null;
notifyListeners();
try {
_products = await _repository.searchProducts(query);
} catch (e) {
_error = '搜索失败:$e';
} finally {
_isLoading = false;
notifyListeners();
}
}
}
// lib/features/product/presentation/pages/product_list_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/product_provider.dart';
import '../widgets/product_card.dart';
class ProductListPage extends StatefulWidget {
const ProductListPage({super.key});
State<ProductListPage> createState() => _ProductListPageState();
}
class _ProductListPageState extends State<ProductListPage> {
void initState() {
super.initState();
// 加载商品列表
Future.microtask(() {
context.read<ProductProvider>().loadProducts();
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('商品列表')),
body: Consumer<ProductProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (provider.error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(provider.error!),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => provider.loadProducts(),
child: const Text('重试'),
),
],
),
);
}
return GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.75,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: provider.products.length,
itemBuilder: (context, index) {
return ProductCard(product: provider.products[index]);
},
);
},
),
);
}
}
🏛️ 分层架构
MVC 模式
Model-View-Controller,适合简单项目。
lib/
├── models/ # Model - 数据模型
│ └── user.dart
├── views/ # View - 页面/UI
│ └── user_page.dart
├── controllers/ # Controller - 业务逻辑
│ └── user_controller.dart
└── main.dart
// models/user.dart
class User {
final String name;
final String email;
User({required this.name, required this.email});
}
// controllers/user_controller.dart
class UserController {
User? _user;
User? get user => _user;
Future<void> loadUser() async {
// 加载用户数据
_user = User(name: '张三', email: 'zhangsan@example.com');
}
void updateName(String name) {
if (_user != null) {
_user = User(name: name, email: _user!.email);
}
}
}
// views/user_page.dart
class UserPage extends StatefulWidget {
State<UserPage> createState() => _UserPageState();
}
class _UserPageState extends State<UserPage> {
final _controller = UserController();
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
await _controller.loadUser();
setState(() {});
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(_controller.user?.name ?? '加载中...'),
),
);
}
}
MVVM 模式
Model-View-ViewModel,推荐使用 Provider/GetX。
lib/
├── models/ # Model - 数据模型
├── views/ # View - 页面/UI
├── viewmodels/ # ViewModel - 视图模型(状态+逻辑)
└── services/ # Services - 数据服务
// models/todo.dart
class Todo {
final int id;
final String title;
final bool completed;
Todo({
required this.id,
required this.title,
this.completed = false,
});
Todo copyWith({String? title, bool? completed}) {
return Todo(
id: id,
title: title ?? this.title,
completed: completed ?? this.completed,
);
}
}
// services/todo_service.dart
class TodoService {
Future<List<Todo>> fetchTodos() async {
// 模拟 API 请求
await Future.delayed(const Duration(seconds: 1));
return [
Todo(id: 1, title: '学习 Flutter'),
Todo(id: 2, title: '写代码'),
Todo(id: 3, title: '看文档'),
];
}
}
// viewmodels/todo_viewmodel.dart
import 'package:flutter/material.dart';
class TodoViewModel extends ChangeNotifier {
final TodoService _service;
List<Todo> _todos = [];
bool _isLoading = false;
TodoViewModel(this._service);
List<Todo> get todos => _todos;
bool get isLoading => _isLoading;
int get completedCount => _todos.where((t) => t.completed).length;
Future<void> loadTodos() async {
_isLoading = true;
notifyListeners();
_todos = await _service.fetchTodos();
_isLoading = false;
notifyListeners();
}
void toggleTodo(int id) {
final index = _todos.indexWhere((t) => t.id == id);
if (index != -1) {
_todos[index] = _todos[index].copyWith(
completed: !_todos[index].completed,
);
notifyListeners();
}
}
void addTodo(String title) {
final newTodo = Todo(
id: DateTime.now().millisecondsSinceEpoch,
title: title,
);
_todos.add(newTodo);
notifyListeners();
}
}
// views/todo_page.dart
class TodoPage extends StatelessWidget {
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => TodoViewModel(TodoService())..loadTodos(),
child: const _TodoPageContent(),
);
}
}
class _TodoPageContent extends StatelessWidget {
const _TodoPageContent();
Widget build(BuildContext context) {
final viewModel = context.watch<TodoViewModel>();
return Scaffold(
appBar: AppBar(
title: Text('待办事项 (${viewModel.completedCount}/${viewModel.todos.length})'),
),
body: viewModel.isLoading
? const Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: viewModel.todos.length,
itemBuilder: (context, index) {
final todo = viewModel.todos[index];
return CheckboxListTile(
title: Text(
todo.title,
style: TextStyle(
decoration: todo.completed
? TextDecoration.lineThrough
: null,
),
),
value: todo.completed,
onChanged: (_) => viewModel.toggleTodo(todo.id),
);
},
),
);
}
}
Clean Architecture(整洁架构)
适合复杂的大型项目,分层更清晰。
lib/
├── core/ # 核心层(全局共享)
│ ├── error/ # 错误处理
│ ├── network/ # 网络配置
│ └── usecases/ # 基础 UseCase
│
├── features/
│ └── user/
│ ├── data/ # 数据层
│ │ ├── datasources/ # 数据源
│ │ │ ├── user_remote_datasource.dart
│ │ │ └── user_local_datasource.dart
│ │ ├── models/ # 数据模型(DTO)
│ │ │ └── user_model.dart
│ │ └── repositories/ # 仓库实现
│ │ └── user_repository_impl.dart
│ │
│ ├── domain/ # 领域层(业务逻辑)
│ │ ├── entities/ # 实体
│ │ │ └── user.dart
│ │ ├── repositories/ # 仓库接口
│ │ │ └── user_repository.dart
│ │ └── usecases/ # 用例
│ │ ├── get_user.dart
│ │ └── update_user.dart
│ │
│ └── presentation/ # 表示层
│ ├── pages/
│ ├── widgets/
│ └── bloc/ # 或 provider/cubit
│
└── injection_container.dart # 依赖注入
// domain/entities/user.dart(实体 - 纯业务对象)
class User {
final int id;
final String name;
final String email;
const User({
required this.id,
required this.name,
required this.email,
});
}
// domain/repositories/user_repository.dart(仓库接口)
abstract class UserRepository {
Future<User> getUser(int id);
Future<void> updateUser(User user);
}
// domain/usecases/get_user.dart(用例 - 业务逻辑)
class GetUser {
final UserRepository repository;
GetUser(this.repository);
Future<User> call(int id) async {
return await repository.getUser(id);
}
}
// data/models/user_model.dart(数据模型 - 带序列化)
class UserModel extends User {
const UserModel({
required super.id,
required super.name,
required super.email,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
Map<String, dynamic> toJson() {
return {'id': id, 'name': name, 'email': email};
}
}
// data/datasources/user_remote_datasource.dart
abstract class UserRemoteDataSource {
Future<UserModel> getUser(int id);
}
class UserRemoteDataSourceImpl implements UserRemoteDataSource {
final ApiClient client;
UserRemoteDataSourceImpl(this.client);
Future<UserModel> getUser(int id) async {
final response = await client.get('/users/$id');
return UserModel.fromJson(response.data);
}
}
// data/repositories/user_repository_impl.dart(仓库实现)
class UserRepositoryImpl implements UserRepository {
final UserRemoteDataSource remoteDataSource;
final UserLocalDataSource localDataSource;
UserRepositoryImpl({
required this.remoteDataSource,
required this.localDataSource,
});
Future<User> getUser(int id) async {
try {
// 优先从网络获取
final user = await remoteDataSource.getUser(id);
// 缓存到本地
await localDataSource.cacheUser(user);
return user;
} catch (e) {
// 网络失败,从本地获取
return await localDataSource.getUser(id);
}
}
Future<void> updateUser(User user) async {
// 实现更新逻辑
}
}
📝 命名约定
文件命名
使用 小写字母 + 下划线 (snake_case):
✅ 正确:
user_profile_page.dart
api_service.dart
custom_button.dart
❌ 错误:
UserProfilePage.dart
apiService.dart
CustomButton.dart
类命名
使用 大驼峰 (PascalCase):
// ✅ 正确
class UserProfile {}
class ApiService {}
class CustomButton extends StatelessWidget {}
// ❌ 错误
class userProfile {}
class api_service {}
变量和函数命名
使用 小驼峰 (camelCase):
// ✅ 正确
String userName = 'Tom';
void loadUserData() {}
final isLoading = false;
// ❌ 错误
String user_name = 'Tom';
void LoadUserData() {}
常量命名
使用 小驼峰 或 全大写下划线:
// ✅ 两种风格都可以
const int maxRetryCount = 3;
const int MAX_RETRY_COUNT = 3;
const String apiBaseUrl = 'https://api.example.com';
const String API_BASE_URL = 'https://api.example.com';
私有成员
使用 下划线前缀:
class UserService {
// 私有变量
final String _apiKey;
// 私有方法
void _validateInput() {}
// 公开方法
Future<void> loadUser() async {}
}
📁 文件组织规范
导入顺序
按以下顺序组织 import 语句:
// 1. Dart SDK
import 'dart:async';
import 'dart:convert';
// 2. Flutter SDK
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// 3. 第三方包
import 'package:dio/dio.dart';
import 'package:provider/provider.dart';
// 4. 项目内部包
import 'package:my_app/core/utils.dart';
import 'package:my_app/models/user.dart';
// 5. 相对路径导入
import '../widgets/custom_button.dart';
import './user_card.dart';
代码文件结构
// 1. 导入
import 'package:flutter/material.dart';
// 2. 常量
const double _kDefaultPadding = 16.0;
// 3. 枚举
enum UserStatus { active, inactive, pending }
// 4. 类定义
class UserPage extends StatefulWidget {
// 4.1 静态常量
static const routeName = '/user';
// 4.2 成员变量
final int userId;
// 4.3 构造函数
const UserPage({super.key, required this.userId});
// 4.4 方法
State<UserPage> createState() => _UserPageState();
}
class _UserPageState extends State<UserPage> {
// 4.1 成员变量
bool _isLoading = false;
// 4.2 生命周期方法
void initState() {
super.initState();
_loadData();
}
void dispose() {
super.dispose();
}
// 4.3 私有方法
Future<void> _loadData() async {}
// 4.4 build 方法
Widget build(BuildContext context) {
return Scaffold();
}
}
// 5. 辅助函数
String formatUserName(String name) => name.trim();
🎯 最佳实践总结
项目结构选择指南
| 项目规模 | 推荐结构 | 特点 |
|---|---|---|
| 小型(1-2人) | 简单分层 | 快速开发,易于理解 |
| 中型(3-5人) | 功能模块 + Repository | 清晰分层,便于维护 |
| 大型(5人以上) | Clean Architecture | 高度解耦,易于测试 |
通用原则
代码组织原则
- 单一职责 - 每个文件/类只做一件事
- 高内聚低耦合 - 相关代码放一起,减少依赖
- 统一命名 - 团队保持一致的命名风格
- 合理分层 - 业务逻辑和 UI 分离
- 便于测试 - 代码结构要方便写单元测试
避免常见错误
// ❌ 错误:把所有逻辑写在 UI 中
class BadPage extends StatefulWidget {
Widget build(BuildContext context) {
// 直接在这里调用 API、处理数据...
}
}
// ✅ 正确:分离业务逻辑
class GoodPage extends StatelessWidget {
Widget build(BuildContext context) {
return Consumer<UserProvider>(
builder: (context, provider, child) {
// UI 只负责展示
},
);
}
}
// ❌ 错误:超大文件(>500行)
// 一个文件包含多个类和大量逻辑
// ✅ 正确:拆分文件
// user_page.dart - 页面
// user_provider.dart - 状态管理
// user_widgets.dart - 子组件
💪 练习题
- 将一个简单的计数器应用重构为 MVC 模式
- 创建一个中型项目结构,包含用户模块和商品模块
- 为一个功能模块实现 Repository 模式
- 实践 Clean Architecture,实现一个简单的待办事项功能
🚀 下一步
了解了项目结构后,你可以前往 附录C - 国际化配置,学习如何让你的 App 支持多语言!
由 编程指南 提供
