AsyncRedux事件处理Skill asyncredux-events

AsyncRedux 事件处理是Flutter框架中的一种状态管理技术,用于通过事件驱动方式控制UI组件。它允许开发者创建单次使用的通知来触发副作用,如清除文本、滚动列表、显示对话框等。适用于移动应用开发,确保UI操作的精确控制。关键词:Flutter, AsyncRedux, 事件驱动, UI控制, 状态管理, 移动开发

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

name: asyncredux-events description: 使用Event类与Flutter的有状态部件(如TextField、ListView等)交互。涵盖在状态中创建Event对象、使用context.event()消费事件、滚动列表、更改文本字段以及事件生命周期。

AsyncRedux中的事件

事件是单次使用的通知,用于在部件中触发副作用。它们设计用于控制原生Flutter部件,如TextFieldListView,这些部件通过控制器管理自己的状态。

何时使用事件

使用事件用于:

  • 控制器操作:清除文本、更改文本、滚动列表、聚焦输入
  • 一次性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]),
      ),
    );
  }
}

事件生命周期

  1. 创建为已消费:事件在初始状态中作为Evt.spent()开始
  2. 分派为未消费:动作创建Evt()Evt<T>(value)并将其放入状态
  3. 部件重建:状态更改触发部件重建
  4. 消费一次context.event()返回值并将事件标记为已消费
  5. 返回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: