name: asyncredux-events
description: 使用Event类与Flutter的有状态部件(如TextField、ListView等)交互。涵盖在状态中创建Event对象、使用context.event()消费事件、滚动列表、更改文本字段以及事件生命周期。
AsyncRedux中的事件
事件是单次使用的通知,用于在部件中触发副作用。它们设计用于控制原生Flutter部件,如TextField和ListView,这些部件通过控制器管理自己的状态。
何时使用事件
使用事件用于:
- 控制器操作:清除文本、更改文本、滚动列表、聚焦输入
- 一次性UI操作:显示对话框、snackbars、触发动画
- 隐式状态更改:导航、任何应该只发生一次的操作
不要使用事件用于:
- 需要多次读取的值(改用常规状态)
- 应该持久化的数据(事件不应保存到本地存储)
设置:添加context.event()扩展
将event方法添加到你的BuildContext扩展中:
extension BuildContextExtension on BuildContext {
AppState get state => getState<AppState>();
R select<R>(R Function(AppState state) selector) =>
getSelect<AppState, R>(selector);
// 为事件添加这个:
R? event<R>(Evt<R> Function(AppState state) selector) =>
getEvent<AppState, R>(selector);
}
创建事件
布尔事件
对于不携带数据的简单触发器:
// 创建一个未消费的事件(将返回一次true)
var clearTextEvt = Evt();
// 创建一个已消费的事件(将返回false)
var clearTextEvt = Evt.spent();
类型化事件
对于携带值的事件:
// 创建一个携带值的未消费事件(将返回一次值,然后null)
var changeTextEvt = Evt<String>("新文本");
var scrollToIndexEvt = Evt<int>(42);
// 创建一个已消费的事件(将返回null)
var changeTextEvt = Evt<String>.spent();
在状态中声明事件
在初始状态中将所有事件初始化为已消费:
class AppState {
final Evt clearTextEvt;
final Evt<String> changeTextEvt;
final Evt<int> scrollToIndexEvt;
AppState({
required this.clearTextEvt,
required this.changeTextEvt,
required this.scrollToIndexEvt,
});
static AppState initialState() => AppState(
clearTextEvt: Evt.spent(),
changeTextEvt: Evt<String>.spent(),
scrollToIndexEvt: Evt<int>.spent(),
);
AppState copy({
Evt? clearTextEvt,
Evt<String>? changeTextEvt,
Evt<int>? scrollToIndexEvt,
}) => AppState(
clearTextEvt: clearTextEvt ?? this.clearTextEvt,
changeTextEvt: changeTextEvt ?? this.changeTextEvt,
scrollToIndexEvt: scrollToIndexEvt ?? this.scrollToIndexEvt,
);
}
从动作中分派事件
动作创建未消费事件并将其放入状态:
// 布尔事件 - 触发清除文本字段
class ClearTextAction extends AppAction {
AppState reduce() => state.copy(clearTextEvt: Evt());
}
// 类型化事件 - 将文本字段更改为新值
class ChangeTextAction extends AppAction {
final String newText;
ChangeTextAction(this.newText);
AppState reduce() => state.copy(changeTextEvt: Evt<String>(newText));
}
// 从异步操作中获取的类型化事件
class FetchAndSetTextAction extends AppAction {
Future<AppState> reduce() async {
String text = await api.fetchText();
return state.copy(changeTextEvt: Evt<String>(text));
}
}
// 在ListView中滚动到特定索引
class ScrollToItemAction extends AppAction {
final int index;
ScrollToItemAction(this.index);
AppState reduce() => state.copy(scrollToIndexEvt: Evt<int>(index));
}
在部件中消费事件
在部件的build方法中使用context.event()。事件在读取时立即被消费(标记为已消费)。
TextField示例
class MyTextField extends StatefulWidget {
@override
State<MyTextField> createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
final controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// 消费清除事件 - 返回一次true,然后false
bool shouldClear = context.event((s) => s.clearTextEvt);
if (shouldClear) {
controller.clear();
}
// 消费更改事件 - 返回一次值,然后null
String? newText = context.event((s) => s.changeTextEvt);
if (newText != null) {
controller.text = newText;
}
return TextField(controller: controller);
}
}
ListView滚动示例
class MyListView extends StatefulWidget {
@override
State<MyListView> createState() => _MyListViewState();
}
class _MyListViewState extends State<MyListView> {
final scrollController = ScrollController();
final itemHeight = 50.0;
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final items = context.select((s) => s.items);
// 消费滚动事件
int? scrollToIndex = context.event((s) => s.scrollToIndexEvt);
if (scrollToIndex != null) {
// 在帧构建后调度滚动
WidgetsBinding.instance.addPostFrameCallback((_) {
scrollController.animateTo(
scrollToIndex * itemHeight,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
});
}
return ListView.builder(
controller: scrollController,
itemCount: items.length,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text(items[index]),
),
);
}
}
事件生命周期
- 创建为已消费:事件在初始状态中作为
Evt.spent()开始 - 分派为未消费:动作创建
Evt()或Evt<T>(value)并将其放入状态 - 部件重建:状态更改触发部件重建
- 消费一次:
context.event()返回值并将事件标记为已消费 - 返回null/false:后续读取返回
null(类型化)或false(布尔)
重要规则
每个事件只能被一个部件消费
如果多个部件需要相同的触发器,创建单独的事件:
class AppState {
final Evt clearSearchEvt; // 用于搜索字段
final Evt clearCommentsEvt; // 用于评论字段
// ...
}
不要将事件用于持久化数据
事件是可变的,设计为一次性使用。永远不要将它们持久化到本地存储。
事件相等性防止不必要的重建
事件具有特殊的相等性方法,当与选择器模式正确使用时,可以防止不必要的部件重建。
高级:检查事件状态而不消费
使用这些方法检查事件状态而不消费它:
// 检查事件是否已被消费
bool consumed = myEvent.isSpent;
// 检查事件是否准备好被消费
bool ready = myEvent.isNotSpent;
// 获取底层状态而不消费
var eventState = myEvent.state;
高级:Event.map()用于转换
转换事件的值:
// 将事件映射到不同类型
Evt<String> nameEvt = Evt<int>(42).map((value) => '项目 $value');
高级:从多个事件源消费
当需要从多个可能的事件源消费时:
// 创建一个从第一个未消费源消费的事件
var combined = Event.from([event1, event2, event3]);
// 或使用静态方法
var value = Event.consumeFrom([event1, event2, event3]);
参考
文档中的URL: