在这里插入图片描述
在这里插入图片描述

概述

不可变状态设计是一种重要的状态管理模式,它要求状态对象一旦创建就不能被修改。任何状态变化都通过创建新对象来实现。这种设计模式可以提高代码的可预测性、可测试性和性能。

什么是不可变状态

不可变状态(Immutable State)是指状态对象在创建后其内部数据不能被修改。如果需要改变状态,必须创建一个新的状态对象。

核心特点

  1. 只读性:状态对象的所有字段都是 final,不能被修改
  2. 不可变性:一旦创建,状态对象就不能改变
  3. 新对象替换:状态变化通过创建新对象来实现
  4. 线程安全:不可变对象天然线程安全

与可变状态的对比

可变状态
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 方法或使用代码生成工具。

应对策略

  1. 使用代码生成:使用 Freezed 或 built_value 自动生成代码
  2. 合理使用:只在需要共享的全局状态中使用不可变设计
  3. 性能优化:使用 const 构造函数和高效的数据结构

总结

不可变状态设计是构建健壮应用的重要模式:

  1. 核心原则:状态对象一旦创建就不能被修改
  2. 实现方式:手动实现、Freezed、built_value
  3. 核心方法copyWith 用于创建状态副本
  4. 主要优势:可预测性、线程安全、易于调试、性能优化
  5. 最佳实践:使用 @immutableconst 构造函数、封装状态操作

在下一节中,我们将探讨状态更新策略的选择和实践。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐