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:创建导航动作
创建UndoAction和RedoAction以从历史检索状态:
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);
}
}
管理历史限制
历史管理的关键考虑因素:
- 设置适当限制 - 平衡内存使用与撤销深度需求
- 移除最旧条目 - 当超过限制时,从开头移除
- 清除未来历史 - 当撤销后执行新动作时,丢弃重做堆栈
- 过滤无关动作 - 跳过未改变状态或导航动作
// 示例:不同用例的不同限制
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:
- https://asyncredux.com/flutter/miscellaneous/undo-and-redo
- https://asyncredux.com/flutter/basics/store
- https://asyncredux.com/flutter/miscellaneous/logging
- https://asyncredux.com/flutter/miscellaneous/metrics
- https://asyncredux.com/flutter/advanced-actions/redux-action
- https://asyncredux.com/flutter/testing/store-tester
- https://asyncredux.com/flutter/basics/sync-actions
- https://asyncredux.com/flutter/advanced-actions/before-and-after-the-reducer
- https://asyncredux.com/flutter/basics/async-actions