AsyncRedux状态设计Skill asyncredux-state-design

AsyncRedux 是一个用于 Flutter 应用的状态管理库,专注于不可变状态设计和异步操作处理。它提供了一种结构化的方式来管理应用状态,支持嵌套状态、业务逻辑封装和高效更新,便于测试和开发。关键词:AsyncRedux, Flutter, 状态管理, 不可变性, 异步操作, 移动开发, 代码示例, 单元测试。

移动开发 0 次安装 0 次浏览 更新于 3/19/2026

名称: asyncredux-state-design 描述: 遵循 AsyncRedux 最佳实践设计不可变状态类。包括创建具有 copy() 方法的 AppState 类,定义 initialState(),组合嵌套状态对象,以及可选用 fast_immutable_collections 包处理 IList、ISet 和 IMap。

AsyncRedux 状态设计

核心原则:不可变性

状态类必须是不可变的——字段在创建后不能被修改。不是直接改变状态,而是创建新实例。所有字段都应标记为 final

基本状态类结构

class AppState {
  final String name;
  final int age;

  AppState({required this.name, required this.age});

  static AppState initialState() => AppState(name: "", age: 0);

  AppState copy({String? name, int? age}) =>
      AppState(
        name: name ?? this.name,
        age: age ?? this.age,
      );
}

关键组件

  1. Final 字段 - 所有状态字段必须为 final
  2. initialState() 方法 - 静态工厂方法提供默认值
  3. copy() 方法 - 创建修改后的实例而不改变原始对象

copy() 方法模式

copy() 方法接受每个字段的可选参数。如果参数为 null,则保留现有值:

AppState copy({String? name, int? age}) =>
    AppState(
      name: name ?? this.name,
      age: age ?? this.age,
    );

还可以添加便捷方法:

AppState withName(String name) => copy(name: name);
AppState withAge(int age) => copy(age: age);

嵌套/复合状态

对于复杂应用,在单个 AppState 中组合多个状态类:

class AppState {
  final TodoList todoList;
  final User user;
  final Settings settings;

  AppState({
    required this.todoList,
    required this.user,
    required this.settings,
  });

  static AppState initialState() => AppState(
    todoList: TodoList.initialState(),
    user: User.initialState(),
    settings: Settings.initialState(),
  );

  AppState copy({
    TodoList? todoList,
    User? user,
    Settings? settings,
  }) =>
      AppState(
        todoList: todoList ?? this.todoList,
        user: user ?? this.user,
        settings: settings ?? this.settings,
      );
}

每个嵌套类遵循相同模式:

class User {
  final String name;
  final String email;

  User({required this.name, required this.email});

  static User initialState() => User(name: "", email: "");

  User copy({String? name, String? email}) =>
      User(
        name: name ?? this.name,
        email: email ?? this.email,
      );
}

在 Actions 中更新嵌套状态

class UpdateUserName extends ReduxAction<AppState> {
  final String name;
  UpdateUserName(this.name);

  @override
  AppState reduce() {
    var newUser = state.user.copy(name: name);
    return state.copy(user: newUser);
  }
}

使用 fast_immutable_collections

对于列表、集合和映射,使用 fast_immutable_collections 包(由 AsyncRedux 作者开发):

dependencies:
  fast_immutable_collections: ^10.0.0

IList 示例

在构造函数和 copy 方法中使用 Iterable,通过 IList.orNull() 进行转换。这允许调用者传递任何可迭代对象(List、Set、IList),无需手动转换:

import 'package:fast_immutable_collections/fast_immutable_collections.dart';

class AppState {
  final IList<Todo> todos;

  AppState({
    Iterable<Todo>? todos,
  }) : todos = IList.orNull(todos) ?? const IList.empty();

  static AppState initialState() => AppState();

  AppState copy({Iterable<Todo>? todos}) =>
      AppState(todos: IList.orNull(todos) ?? this.todos);

  // 带有业务逻辑的便捷方法
  AppState addTodo(Todo todo) => copy(todos: todos.add(todo));
  AppState removeTodo(Todo todo) => copy(todos: todos.remove(todo));
  AppState toggleTodo(int index) => copy(
    todos: todos.replace(index, todos[index].copy(done: !todos[index].done)),
  );
}

// 灵活用法:
var state = AppState();                           // 空列表
var state = AppState(todos: [todo1, todo2]);      // List 有效
var state = AppState(todos: {todo1, todo2});      // Set 有效
var state = AppState(todos: existingIList);       // IList 重用(不复制)

IMap 示例

在构造函数和 copy 方法中使用 Map,通过 IMap.orNull() 进行转换:

class AppState {
  final IMap<String, User> usersById;

  AppState({
    Map<String, User>? usersById,
  }) : usersById = IMap.orNull(usersById) ?? const IMap.empty();

  static AppState initialState() => AppState();

  AppState copy({Map<String, User>? usersById}) =>
      AppState(usersById: IMap.orNull(usersById) ?? this.usersById);

  AppState addUser(User user) => copy(usersById: usersById.add(user.id, user));
  AppState removeUser(String id) => copy(usersById: usersById.remove(id));
}

ISet 示例

在构造函数和 copy 方法中使用 Iterable,通过 ISet.orNull() 进行转换:

class AppState {
  final ISet<String> selectedIds;

  AppState({
    Iterable<String>? selectedIds,
  }) : selectedIds = ISet.orNull(selectedIds) ?? const ISet.empty();

  static AppState initialState() => AppState();

  AppState copy({Iterable<String>? selectedIds}) =>
      AppState(selectedIds: ISet.orNull(selectedIds) ?? this.selectedIds);

  AppState toggleSelection(String id) => copy(
    selectedIds: selectedIds.contains(id)
        ? selectedIds.remove(id)
        : selectedIds.add(id),
  );
}

状态中的事件

对于一次性 UI 交互(滚动、文本字段变化),使用 Evt

class AppState {
  final Evt clearTextEvt;
  final Evt<String> changeTextEvt;

  AppState({
    required this.clearTextEvt,
    required this.changeTextEvt,
  });

  static AppState initialState() => AppState(
    clearTextEvt: Evt.spent(),
    changeTextEvt: Evt<String>.spent(),
  );

  AppState copy({
    Evt? clearTextEvt,
    Evt<String>? changeTextEvt,
  }) =>
      AppState(
        clearTextEvt: clearTextEvt ?? this.clearTextEvt,
        changeTextEvt: changeTextEvt ?? this.changeTextEvt,
      );
}

事件初始化为“已消耗”,在 Actions 中替换为新实例时变为活跃。

状态类中的业务逻辑

AsyncRedux 建议将业务逻辑放在状态类中,而不是 Actions 或 Widgets:

class TodoList {
  final IList<Todo> items;

  TodoList({required this.items});

  // 业务逻辑方法
  int get completedCount => items.where((t) => t.done).length;
  int get pendingCount => items.length - completedCount;
  double get completionRate => items.isEmpty ? 0 : completedCount / items.length;

  IList<Todo> get completed => items.where((t) => t.done).toIList();
  IList<Todo> get pending => items.where((t) => !t.done).toIList();

  TodoList addTodo(Todo todo) => TodoList(items: items.add(todo));
  TodoList removeTodo(Todo todo) => TodoList(items: items.remove(todo));
}

Actions 变为简单的协调器:

class AddTodo extends ReduxAction<AppState> {
  final Todo todo;
  AddTodo(this.todo);

  @override
  AppState reduce() => state.copy(
    todoList: state.todoList.addTodo(todo),
  );
}

Actions 中的状态访问

Actions 通过 getters 访问状态:

  • state - 当前状态(在异步 Actions 的每个 await 后更新)
  • initialState - 动作首次分发时的状态(永不改变)
class MyAction extends ReduxAction<AppState> {
  @override
  Future<AppState?> reduce() async {
    var originalValue = initialState.counter; // 保留原始值
    await someAsyncWork();
    var currentValue = state.counter; // 可能已改变
    return state.copy(counter: currentValue + 1);
  }
}

测试优势

不可变状态和纯方法使得单元测试变得直接:

void main() {
  test('addTodo adds item to list', () {
    var state = AppState.initialState();
    var todo = Todo(text: 'Test', done: false);

    var newState = state.addTodo(todo);

    expect(newState.todos.length, 1);
    expect(newState.todos.first.text, 'Test');
    expect(state.todos.length, 0); // 原始状态未改变
  });
}

参考资料

文档中的 URL: