鸿蒙Flutter 不可变状态设计
·


概述
不可变状态设计是一种重要的状态管理模式,它要求状态对象一旦创建就不能被修改。任何状态变化都通过创建新对象来实现。这种设计模式可以提高代码的可预测性、可测试性和性能。
什么是不可变状态
不可变状态(Immutable State)是指状态对象在创建后其内部数据不能被修改。如果需要改变状态,必须创建一个新的状态对象。
核心特点
- 只读性:状态对象的所有字段都是
final,不能被修改 - 不可变性:一旦创建,状态对象就不能改变
- 新对象替换:状态变化通过创建新对象来实现
- 线程安全:不可变对象天然线程安全
与可变状态的对比
可变状态
class CartItem {
final String id;
final String name;
final double price;
int quantity; // 可变字段
CartItem({
required this.id,
required this.name,
required this.price,
this.quantity = 1,
});
}
// 修改状态
final item = CartItem(id: '1', name: '商品A', price: 19.99);
item.quantity = 2; // 直接修改
不可变状态
class CartItem {
final String id;
final String name;
final double price;
final int quantity; // 不可变字段
const CartItem({
required this.id,
required this.name,
required this.price,
this.quantity = 1,
});
// 通过 copyWith 创建新对象
CartItem copyWith({
String? id,
String? name,
double? price,
int? quantity,
}) {
return CartItem(
id: id ?? this.id,
name: name ?? this.name,
price: price ?? this.price,
quantity: quantity ?? this.quantity,
);
}
}
// 修改状态 - 创建新对象
final item = CartItem(id: '1', name: '商品A', price: 19.99);
final updatedItem = item.copyWith(quantity: 2); // 创建新对象
不可变状态的优势
优势 1:可预测性
不可变状态确保状态变化总是产生新对象,不会意外修改旧状态。
// 可变状态的问题
void updateCart(List<CartItem> items, String id, int quantity) {
final item = items.firstWhere((item) => item.id == id);
item.quantity = quantity; // 直接修改,可能影响其他引用
}
// 不可变状态的解决方案
List<CartItem> updateCart(List<CartItem> items, String id, int quantity) {
return items.map((item) =>
item.id == id ? item.copyWith(quantity: quantity) : item
).toList(); // 返回新列表
}
优势 2:线程安全
不可变对象天然线程安全,因为它们不能被修改。
// 不可变对象可以安全地在多个线程间共享
final config = AppConfig(theme: 'dark', language: 'zh');
// 在多个异步操作中使用
Future<void> loadData() async {
await Future.wait([
_fetchProducts(config),
_fetchUsers(config),
_fetchOrders(config),
]);
}
优势 3:易于调试
不可变状态可以轻松记录状态变化历史,便于调试。
class HistoryManager<T> {
final List<T> _history = [];
void push(T state) {
_history.add(state);
}
T? undo() {
if (_history.isEmpty) return null;
return _history.removeLast();
}
}
// 使用
final history = HistoryManager<CartState>();
history.push(currentState); // 保存状态
final previousState = history.undo(); // 撤销
优势 4:性能优化
不可变状态可以使用 == 比较判断是否需要重建。
// 使用 const 构造函数和不可变状态
class CartSummary extends StatelessWidget {
final CartState state;
const CartSummary({super.key, required this.state});
Widget build(BuildContext context) {
return Text('总价: \$${state.totalPrice}');
}
}
// 只有当 state 真正变化时才重建
CartSummary(state: cartState)
优势 5:便于测试
不可变状态是隔离的,测试更简单。
// 测试不可变状态
void main() {
test('update quantity', () {
final initialItem = CartItem(id: '1', name: '商品A', price: 19.99, quantity: 1);
final updatedItem = initialItem.copyWith(quantity: 2);
expect(initialItem.quantity, 1); // 原对象不变
expect(updatedItem.quantity, 2); // 新对象更新
expect(initialItem.id, updatedItem.id); // ID不变
});
}
不可变状态的实现
方式 1:手动实现
手动实现不可变状态,使用 @immutable 注解和 copyWith 方法。
class CartState {
final List<CartItem> items;
const CartState({required this.items});
// 添加商品
CartState addItem(CartItem item) {
return CartState(items: [...items, item]);
}
// 删除商品
CartState removeItem(String id) {
return CartState(items: items.where((i) => i.id != id).toList());
}
// 更新数量
CartState updateQuantity(String id, int quantity) {
if (quantity <= 0) {
return removeItem(id);
}
return CartState(items: items.map((i) =>
i.id == id ? i.copyWith(quantity: quantity) : i
).toList());
}
// 计算总价
double get totalPrice {
return items.fold(0, (sum, item) => sum + item.price * item.quantity);
}
}
方式 2:使用 Freezed 代码生成
Freezed 是一个代码生成库,可以自动生成不可变类和 copyWith 方法。
添加依赖
dependencies:
freezed_annotation: ^2.4.1
dev_dependencies:
build_runner: ^2.4.8
freezed: ^2.4.6
定义不可变类
import 'package:freezed_annotation/freezed_annotation.dart';
part 'cart_state.freezed.dart';
class CartState with _$CartState {
const factory CartState({
required List<CartItem> items,
}) = _CartState;
const CartState._(); // 私有构造函数,用于添加自定义方法
// 添加自定义方法
CartState addItem(CartItem item) {
return copyWith(items: [...items, item]);
}
CartState removeItem(String id) {
return copyWith(items: items.where((i) => i.id != id).toList());
}
double get totalPrice {
return items.fold(0, (sum, item) => sum + item.price * item.quantity);
}
}
class CartItem with _$CartItem {
const factory CartItem({
required String id,
required String name,
required double price,
(1) int quantity,
}) = _CartItem;
}
生成代码
dart run build_runner build
方式 3:使用 built_value
built_value 是另一个代码生成库,提供更强大的不可变类支持。
添加依赖
dependencies:
built_value: ^8.9.2
built_collection: ^5.1.1
dev_dependencies:
build_runner: ^2.4.8
built_value_generator: ^8.9.2
定义不可变类
import 'package:built_value/built_value.dart';
import 'package:built_collection/built_collection.dart';
part 'cart_state.g.dart';
abstract class CartState implements Built<CartState, CartStateBuilder> {
factory CartState([void Function(CartStateBuilder) updates]) = _$CartState;
CartState._();
BuiltList<CartItem> get items;
// 添加自定义方法
CartState addItem(CartItem item) {
return rebuild((b) => b.items.add(item));
}
CartState removeItem(String id) {
return rebuild((b) => b.items.removeWhere((i) => i.id == id));
}
}
abstract class CartItem implements Built<CartItem, CartItemBuilder> {
factory CartItem([void Function(CartItemBuilder) updates]) = _$CartItem;
CartItem._();
String get id;
String get name;
double get price;
int get quantity;
}
生成代码
dart run build_runner build
不可变状态的使用模式
模式 1:状态替换
在状态管理中,使用不可变状态时,每次变化都替换整个状态对象。
class CartNotifier extends StateNotifier<CartState> {
CartNotifier() : super(const CartState(items: []));
void addItem(CartItem item) {
state = state.addItem(item); // 替换状态
}
void removeItem(String id) {
state = state.removeItem(id); // 替换状态
}
void updateQuantity(String id, int quantity) {
state = state.updateQuantity(id, quantity); // 替换状态
}
}
模式 2:状态合并
在复杂场景中,可能需要合并多个状态变化。
void batchUpdate(List<CartItem> updates) {
var newState = state;
for (final item in updates) {
newState = newState.addItem(item);
}
state = newState; // 只触发一次重建
}
模式 3:状态比较
使用不可变状态可以高效地比较状态是否变化。
// 在 Widget 中使用
class CartScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final cartState = ref.watch(cartProvider);
// 只有当 cartState 真正变化时才重建
return ListView(children: cartState.items.map(...).toList());
}
}
不可变状态的最佳实践
实践 1:使用 @immutable 注解
使用 @immutable 注解可以帮助编译器检查类是否真正不可变。
class CartItem {
final String id;
final String name;
final double price;
final int quantity;
const CartItem({
required this.id,
required this.name,
required this.price,
this.quantity = 1,
});
}
实践 2:实现 copyWith 方法
copyWith 方法是不可变状态的核心,用于创建状态副本。
CartItem copyWith({
String? id,
String? name,
double? price,
int? quantity,
}) {
return CartItem(
id: id ?? this.id,
name: name ?? this.name,
price: price ?? this.price,
quantity: quantity ?? this.quantity,
);
}
实践 3:使用 const 构造函数
对于不可变对象,使用 const 构造函数可以提高性能。
const CartItem({
required this.id,
required this.name,
required this.price,
this.quantity = 1,
});
实践 4:避免嵌套可变对象
确保不可变对象的所有字段也是不可变的。
// 不好的示例:嵌套可变对象
class CartState {
final List<CartItem> items; // List 是可变的
const CartState({required this.items});
}
// 好的示例:使用不可变集合
class CartState {
final List<CartItem> items;
const CartState({required this.items});
// 返回新列表,不修改原列表
CartState addItem(CartItem item) {
return CartState(items: [...items, item]);
}
}
实践 5:封装状态操作
将状态操作封装为方法,提高代码可读性。
class CartState {
// ...
CartState addItem(CartItem item) { ... }
CartState removeItem(String id) { ... }
CartState updateQuantity(String id, int quantity) { ... }
CartState clear() { ... }
// 计算属性
double get totalPrice { ... }
int get itemCount { ... }
}
不可变状态的局限性
局限性 1:内存开销
每次状态变化都创建新对象,可能会增加内存开销。
局限性 2:学习成本
团队需要学习不可变编程的思维方式。
局限性 3:代码量增加
需要编写 copyWith 方法或使用代码生成工具。
应对策略
- 使用代码生成:使用 Freezed 或 built_value 自动生成代码
- 合理使用:只在需要共享的全局状态中使用不可变设计
- 性能优化:使用
const构造函数和高效的数据结构
总结
不可变状态设计是构建健壮应用的重要模式:
- 核心原则:状态对象一旦创建就不能被修改
- 实现方式:手动实现、Freezed、built_value
- 核心方法:
copyWith用于创建状态副本 - 主要优势:可预测性、线程安全、易于调试、性能优化
- 最佳实践:使用
@immutable、const构造函数、封装状态操作
在下一节中,我们将探讨状态更新策略的选择和实践。
更多推荐


所有评论(0)