AsyncRedux撤销重做实现Skill asyncredux-undo-redo

这个技能教你如何在AsyncRedux库中实现撤销和重做功能,通过状态观察者跟踪状态变化、记录历史并管理UI集成,适用于Flutter移动应用开发。关键词:AsyncRedux, 撤销, 重做, Flutter, Dart, 状态管理, 移动开发

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

name: asyncredux-undo-redo description: 使用状态观察者实现撤销/重做功能。涵盖使用stateObserver记录状态历史、创建RecoverStateAction、实现全状态或部分状态的撤销,以及管理历史限制。

AsyncRedux中的撤销和重做

AsyncRedux通过一个直接的模式简化撤销/重做:创建一个状态观察者来保存状态到历史列表,并创建动作来浏览该历史。

理解StateObserver

StateObserver抽象类跟踪状态修改。实现其observe方法来通知状态变化:

abstract class StateObserver<St> {
  void observe(
    ReduxAction<St> action,  // 动作
    St stateIni,             // 初始状态
    St stateEnd,             // 结束状态
    Object? error,           // 错误,成功时为null
    int dispatchCount,       // 顺序派发计数
  );
}

参数:

  • action - 触发状态变化的派发动作
  • stateIni - 减少器应用更改前的状态
  • stateEnd - 减少器返回的新状态
  • error - 如果成功则为null;否则包含抛出的错误
  • dispatchCount - 顺序派发编号

时机: 观察者在减少器返回后、after()方法和错误包装过程之前触发。

步骤1:创建UndoRedoObserver

class UndoRedoObserver implements StateObserver<AppState> {
  final List<AppState> _history = [];
  int _currentIndex = -1;
  final int maxHistorySize;

  UndoRedoObserver({this.maxHistorySize = 50});

  @override
  void observe(
    ReduxAction<AppState> action,
    AppState stateIni,
    AppState stateEnd,
    Object? error,
    int dispatchCount,
  ) {
    // 如果动作有错误,跳过
    if (error != null) return;

    // 如果状态未改变,跳过
    if (stateIni == stateEnd) return;

    // 跳过撤销/重做动作以防止递归历史条目
    if (action is UndoAction || action is RedoAction) return;

    // 当导航回退后执行新动作时,清除“未来”历史
    if (_currentIndex < _history.length - 1) {
      _history.removeRange(_currentIndex + 1, _history.length);
    }

    // 添加新状态到历史
    _history.add(stateEnd);
    _currentIndex = _history.length - 1;

    // 通过移除最旧条目强制最大历史大小
    while (_history.length > maxHistorySize) {
      _history.removeAt(0);
      _currentIndex--;
    }
  }

  /// 返回上一个状态,如果处于开头则返回null
  AppState? getPreviousState() {
    if (_currentIndex > 0) {
      _currentIndex--;
      return _history[_currentIndex];
    }
    return null;
  }

  /// 返回下一个状态,如果处于结尾则返回null
  AppState? getNextState() {
    if (_currentIndex < _history.length - 1) {
      _currentIndex++;
      return _history[_currentIndex];
    }
    return null;
  }

  bool get canUndo => _currentIndex > 0;
  bool get canRedo => _currentIndex < _history.length - 1;
}

步骤2:将观察者注册到存储

在存储创建期间将观察者传递到stateObservers

// 创建观察者实例以便动作可以访问
final undoRedoObserver = UndoRedoObserver(maxHistorySize: 100);

var store = Store<AppState>(
  initialState: AppState.initialState(),
  stateObservers: [undoRedoObserver],
);

步骤3:创建导航动作

创建UndoActionRedoAction以从历史检索状态:

class UndoAction extends ReduxAction<AppState> {
  final UndoRedoObserver observer;

  UndoAction(this.observer);

  @override
  AppState? reduce() {
    return observer.getPreviousState();
  }
}

class RedoAction extends ReduxAction<AppState> {
  final UndoRedoObserver observer;

  RedoAction(this.observer);

  @override
  AppState? reduce() {
    return observer.getNextState();
  }
}

替代方案: 通过环境模式使用依赖注入访问观察者:

class UndoAction extends ReduxAction<AppState> {
  @override
  AppState? reduce() {
    final observer = env.undoRedoObserver;
    return observer.getPreviousState();
  }
}

步骤4:与UI集成

从小部件派发撤销/重做动作:

class UndoRedoButtons extends StatelessWidget {
  final UndoRedoObserver observer;

  const UndoRedoButtons({required this.observer});

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        IconButton(
          icon: Icon(Icons.undo),
          onPressed: observer.canUndo
              ? () => context.dispatch(UndoAction(observer))
              : null,
        ),
        IconButton(
          icon: Icon(Icons.redo),
          onPressed: observer.canRedo
              ? () => context.dispatch(RedoAction(observer))
              : null,
        ),
      ],
    );
  }
}

部分状态撤销/重做

相同方法适用于仅撤销/重做部分状态。这在您想独立跟踪特定状态切片的变化时很有用。

class PartialUndoRedoObserver implements StateObserver<AppState> {
  final List<DocumentState> _history = [];
  int _currentIndex = -1;
  final int maxHistorySize;

  PartialUndoRedoObserver({this.maxHistorySize = 50});

  @override
  void observe(
    ReduxAction<AppState> action,
    AppState stateIni,
    AppState stateEnd,
    Object? error,
    int dispatchCount,
  ) {
    if (error != null) return;
    if (action is UndoDocumentAction || action is RedoDocumentAction) return;

    // 仅跟踪状态的文档部分变化
    if (stateIni.document == stateEnd.document) return;

    if (_currentIndex < _history.length - 1) {
      _history.removeRange(_currentIndex + 1, _history.length);
    }

    _history.add(stateEnd.document);
    _currentIndex = _history.length - 1;

    while (_history.length > maxHistorySize) {
      _history.removeAt(0);
      _currentIndex--;
    }
  }

  DocumentState? getPreviousDocument() {
    if (_currentIndex > 0) {
      _currentIndex--;
      return _history[_currentIndex];
    }
    return null;
  }

  DocumentState? getNextDocument() {
    if (_currentIndex < _history.length - 1) {
      _currentIndex++;
      return _history[_currentIndex];
    }
    return null;
  }
}

class UndoDocumentAction extends ReduxAction<AppState> {
  final PartialUndoRedoObserver observer;

  UndoDocumentAction(this.observer);

  @override
  AppState? reduce() {
    final previousDoc = observer.getPreviousDocument();
    if (previousDoc == null) return null;
    return state.copy(document: previousDoc);
  }
}

管理历史限制

历史管理的关键考虑因素:

  1. 设置适当限制 - 平衡内存使用与撤销深度需求
  2. 移除最旧条目 - 当超过限制时,从开头移除
  3. 清除未来历史 - 当撤销后执行新动作时,丢弃重做堆栈
  4. 过滤无关动作 - 跳过未改变状态或导航动作
// 示例:不同用例的不同限制
final documentObserver = UndoRedoObserver(maxHistorySize: 100); // 重型撤销
final preferencesObserver = UndoRedoObserver(maxHistorySize: 10); // 轻型撤销

完整示例

// observer.dart
class UndoRedoObserver implements StateObserver<AppState> {
  final List<AppState> _history = [];
  int _currentIndex = -1;
  final int maxHistorySize;

  UndoRedoObserver({this.maxHistorySize = 50});

  @override
  void observe(
    ReduxAction<AppState> action,
    AppState stateIni,
    AppState stateEnd,
    Object? error,
    int dispatchCount,
  ) {
    if (error != null) return;
    if (stateIni == stateEnd) return;
    if (action is UndoAction || action is RedoAction) return;

    if (_currentIndex < _history.length - 1) {
      _history.removeRange(_currentIndex + 1, _history.length);
    }

    _history.add(stateEnd);
    _currentIndex = _history.length - 1;

    while (_history.length > maxHistorySize) {
      _history.removeAt(0);
      _currentIndex--;
    }
  }

  AppState? getPreviousState() {
    if (_currentIndex > 0) {
      _currentIndex--;
      return _history[_currentIndex];
    }
    return null;
  }

  AppState? getNextState() {
    if (_currentIndex < _history.length - 1) {
      _currentIndex++;
      return _history[_currentIndex];
    }
    return null;
  }

  bool get canUndo => _currentIndex > 0;
  bool get canRedo => _currentIndex < _history.length - 1;

  void clear() {
    _history.clear();
    _currentIndex = -1;
  }
}

// actions.dart
class UndoAction extends ReduxAction<AppState> {
  @override
  AppState? reduce() => env.undoRedoObserver.getPreviousState();
}

class RedoAction extends ReduxAction<AppState> {
  @override
  AppState? reduce() => env.undoRedoObserver.getNextState();
}

// main.dart
void main() {
  final undoRedoObserver = UndoRedoObserver(maxHistorySize: 100);

  final store = Store<AppState>(
    initialState: AppState.initialState(),
    stateObservers: [undoRedoObserver],
    environment: Environment(undoRedoObserver: undoRedoObserver),
  );

  runApp(
    StoreProvider<AppState>(
      store: store,
      child: MyApp(),
    ),
  );
}

参考

文档URL: