AsyncRedux乐观更新技术Skill asyncredux-optimistic-update-mixin

该技能用于移动应用开发中实现乐观更新,通过AsyncRedux框架提供即时UI反馈。在服务器操作完成前乐观更新状态,失败时自动回滚,提升用户体验。适用于创建、删除、提交等一次性操作,以及实时同步场景。关键词:乐观更新、AsyncRedux、Flutter、状态管理、UI反馈、服务器同步、回滚处理。

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

名称: asyncredux-optimistic-update-mixin 描述: 添加OptimisticUpdate混合器,用于在服务器确认前提供即时UI反馈。涵盖即时状态更改、失败时自动回滚,以及可选地通知用户回滚。

乐观更新混合器

AsyncRedux为不同场景提供了三种乐观更新混合器:

混合器 使用场景
OptimisticCommand 一次性操作(创建、删除、提交)带回滚
OptimisticSync 快速切换/交互,带合并
OptimisticSyncWithPush 实时服务器推送场景,带版本跟踪

OptimisticCommand

用于一次性服务器操作,其中即时UI反馈重要:创建待办事项、删除项目、提交表单或处理支付。

基本示例

没有乐观更新(用户等待服务器):

class SaveTodo extends AppAction {
  final Todo newTodo;
  SaveTodo(this.newTodo);

  Future<AppState?> reduce() async {
    await saveTodo(newTodo);
    var reloadedList = await loadTodoList();
    return state.copy(todoList: reloadedList);
  }
}

使用OptimisticCommand(即时UI反馈):

class SaveTodo extends AppAction with OptimisticCommand {
  final Todo newTodo;
  SaveTodo(this.newTodo);

  // 立即应用于UI的值
  Object? optimisticValue() => newTodo;

  // 从状态提取当前值(用于回滚比较)
  Object? getValueFromState(AppState state)
    => state.todoList.getById(newTodo.id);

  // 应用值到状态并返回新状态
  AppState applyValueToState(AppState state, Object? value)
    => state.copy(todoList: state.todoList.add(value as Todo));

  // 发送到服务器(如果使用Retry混合器,则重试)
  Future<Object?> sendCommandToServer(Object? value) async
    => await saveTodo(newTodo);

  // 可选:错误时从服务器重新加载
  Future<Object?> reloadFromServer() async
    => await loadTodoList();
}

回滚如何工作

如果sendCommandToServer失败,混合器仅当当前状态仍匹配乐观值时才自动回滚。这避免了在请求进行中撤销更新的更改。

重写这些方法以自定义回滚:

// 确定是否恢复先前状态
bool shouldRollback() => true;

// 指定要恢复的确切状态
AppState? rollbackState() => previousState;

默认非重入

OptimisticCommand防止同一操作的并发执行。使用nonReentrantKeyParams()允许对不同项目进行并行操作:

class SaveTodo extends AppAction with OptimisticCommand {
  final String itemId;
  SaveTodo(this.itemId);

  // 允许SaveTodo('A')和SaveTodo('B')同时运行
  // 但防止两个SaveTodo('A')一起运行
  Object? nonReentrantKeyParams() => itemId;

  // ... 其余实现
}

在UI中检查操作是否进行中:

if (context.isWaiting(SaveTodo)) {
  return CircularProgressIndicator();
}

与其他混合器组合

  • 与Retry:仅sendCommandToServer重试;乐观UI保持稳定
  • 与CheckInternet:离线时不应用乐观状态

OptimisticSync

用于快速用户交互(切换喜欢、开关、滑块),其中只有最终值重要,中间状态可丢弃。

切换示例

class ToggleLike extends AppAction with OptimisticSync<AppState, bool> {
  final String itemId;
  ToggleLike(this.itemId);

  // 允许对不同项目的并发操作
  Object? optimisticSyncKeyParams() => itemId;

  // 应用乐观值(切换当前值)
  bool valueToApply() => !state.items[itemId].liked;

  // 应用乐观更改到状态
  AppState applyOptimisticValueToState(AppState state, bool isLiked)
    => state.copy(items: state.items.setLiked(itemId, isLiked));

  // 从状态提取当前值
  bool getValueFromState(AppState state) => state.items[itemId].liked;

  // 发送到服务器
  Future<Object?> sendValueToServer(Object? value) async
    => await api.setLiked(itemId, value);

  // 可选:应用服务器响应到状态
  AppState? applyServerResponseToState(AppState state, Object serverResponse)
    => state.copy(items: state.items.setLiked(itemId, serverResponse as bool));

  // 可选:处理完成/错误
  Future<AppState?> onFinish(Object? error) async {
    if (error != null) {
      // 失败时从服务器重新加载
      var reloaded = await api.getItem(itemId);
      return state.copy(items: state.items.update(itemId, reloaded));
    }
    return null;
  }
}

合并如何工作

多个快速更改合并为最小服务器请求:

  1. 用户快速点击喜欢按钮5次
  2. UI立即每次更新(切换、切换、切换…)
  3. 一个服务器请求发送最终状态
  4. 如果在飞行请求期间状态更改,则后续请求发送新最终值

OptimisticSyncWithPush

当应用接收实时服务器更新(WebSockets、Firebase),跨多个设备修改共享数据时使用。

与OptimisticSync的关键区别

  • 每个本地调度递增localRevision计数器
  • 服务器推送不递增localRevision
  • 后续逻辑比较版本而不是仅值
  • 过时推送自动忽略

实现

class ToggleLike extends AppAction with OptimisticSyncWithPush<AppState, bool> {
  final String itemId;
  ToggleLike(this.itemId);

  Object? optimisticSyncKeyParams() => itemId;

  bool valueToApply() => !state.items[itemId].liked;

  AppState applyOptimisticValueToState(AppState state, bool isLiked)
    => state.copy(items: state.items.setLiked(itemId, isLiked));

  bool getValueFromState(AppState state) => state.items[itemId].liked;

  // 从状态读取服务器版本
  int? getServerRevisionFromState(Object? key)
    => state.items[key as String].serverRevision;

  AppState? applyServerResponseToState(AppState state, Object serverResponse)
    => state.copy(items: state.items.setLiked(itemId, serverResponse as bool));

  Future<Object?> sendValueToServer(Object? value) async {
    // 在await之前获取本地版本
    int localRev = localRevision();

    var response = await api.setLiked(itemId, value, localRev: localRev);

    // 响应后记录服务器版本
    informServerRevision(response.serverRev);

    return response.liked;
  }
}

ServerPush混合器

使用自动过时检测处理传入服务器推送:

class PushLikeUpdate extends AppAction with ServerPush<AppState> {
  final String itemId;
  final bool liked;
  final int serverRev;

  PushLikeUpdate({
    required this.itemId,
    required this.liked,
    required this.serverRev,
  });

  // 链接到相应的OptimisticSyncWithPush操作
  Type associatedAction() => ToggleLike;

  Object? optimisticSyncKeyParams() => itemId;

  int serverRevision() => serverRev;

  int? getServerRevisionFromState(Object? key)
    => state.items[key as String].serverRevision;

  AppState? applyServerPushToState(AppState state, Object? key, int serverRevision)
    => state.copy(
         items: state.items.update(
           key as String,
           (item) => item.copy(liked: liked, serverRevision: serverRevision),
         ),
       );
}

如果传入serverRevision ≤ 当前已知版本,推送自动忽略。这防止较旧服务器状态覆盖较新状态。

版本跟踪的数据模型

在数据模型中存储服务器版本:

class Item {
  final bool liked;
  final int? serverRevision;

  Item({required this.liked, this.serverRevision});

  Item copy({bool? liked, int? serverRevision}) => Item(
    liked: liked ?? this.liked,
    serverRevision: serverRevision ?? this.serverRevision,
  );
}

通知用户回滚

要通知用户回滚发生时,在错误处理中使用UserException

class SaveTodo extends AppAction with OptimisticCommand {
  // ... 所需方法 ...

  Future<Object?> sendCommandToServer(Object? value) async {
    try {
      return await saveTodo(newTodo);
    } catch (e) {
      // 抛出UserException以在回滚后显示对话框
      throw UserException('保存失败。您的更改已恢复。').addCause(e);
    }
  }
}

或使用OptimisticSync的onFinish

Future<AppState?> onFinish(Object? error) async {
  if (error != null) {
    // 调度通知操作
    dispatch(UserExceptionAction('更新失败。正在恢复...'));

    // 从服务器重新加载正确状态
    var reloaded = await api.getItem(itemId);
    return state.copy(items: state.items.update(itemId, reloaded));
  }
  return null;
}

选择正确的混合器

场景 混合器
创建/删除/提交操作 OptimisticCommand
切换开关、喜欢按钮 OptimisticSync
滑块、快速输入更改 OptimisticSync
多设备实时同步 OptimisticSyncWithPush + ServerPush

参考

文档中的URL: