AsyncRedux选择器Skill asyncredux-selectors

AsyncRedux选择器是一种用于Flutter应用的状态管理技术,通过创建和缓存函数来高效提取和转换Redux存储状态数据,避免不必要的重新计算,提升应用性能。关键词包括AsyncRedux、选择器、缓存、状态管理、Flutter、Redux、性能优化、Memoization、Widget、Action。

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

name: asyncredux-selectors description: 创建和缓存选择器以高效访问状态。涵盖编写选择器函数、使用cache1cache2进行缓存、重选模式以及在widget中避免重复计算。

什么是选择器?

选择器是从Redux存储状态中提取特定数据的函数。它们提供三个关键好处:

  1. 计算派生数据 - 将状态转换或过滤为您的widget需要的格式
  2. 抽象状态结构 - 组件不依赖于状态的如何组织
  3. 启用缓存(记忆化) - 避免不必要的重新计算

问题:重复计算

在显示过滤或计算的数据时,如果没有选择器,您可能会这样写:

// 低效 - 每次访问时过滤整个列表
state.users.where((user) => user.name.startsWith("A")).toList()[index].name;

这个过滤操作在每次widget重建时都会运行,即使数据没有改变。

基本选择器函数

创建一个执行一次计算的选择器函数:

List<User> selectUsersStartingWith(AppState state, String text) {
  return state.users.where((user) => user.name.startsWith(text)).toList();
}

缓存选择器(重选器)

对于昂贵的计算,用缓存函数包装您的选择器。AsyncRedux提供了内置的缓存实用程序。

基本缓存示例

List<User> selectUsersStartingWith(AppState state, {required String text}) =>
    _selectUsersStartingWith(state)(text);

static final _selectUsersStartingWith = cache1state_1param(
  (AppState state) => (String text) =>
      state.users.where((user) => user.name.startsWith(text)).toList()
);

优化缓存(状态子集)

为了更好的性能,仅依赖于相关的特定状态子集:

List<User> selectUsersStartingWith(AppState state, {required String text}) =>
    _selectUsersStartingWith(state.users)(text);

static final _selectUsersStartingWith = cache1state_1param(
  (List<User> users) => (String text) =>
      users.where((user) => user.name.startsWith(text)).toList()
);

这个版本仅在state.users更改时重新计算,而不是在任何部分状态更改时。

可用的缓存函数

AsyncRedux提供以下缓存函数:

函数 状态数 参数数 使用案例
cache1state 1 0 从一个状态计算的简单值
cache1state_1param 1 1 带有一个参数的过滤/计算值
cache1state_2params 1 2 带有两个参数的计算
cache1state_0params_x 1 多个 可变数量的参数
cache2states 2 0 结合两个状态部分
cache2states_1param 2 1 结合两个状态带一个参数
cache2states_2params 2 2 结合两个状态带两个参数
cache2states_0params_x 2 多个 两个状态,可变参数
cache3states 3 0 结合三个状态部分
cache3states_0params_x 3 多个 三个状态,可变参数

命名约定:cache[N]state[s]_[M]param[s] 其中 N = 状态数,M = 参数数。

缓存特性

  • 多个缓存结果 - 为不同的参数组合维护单独的缓存
  • 弱映射存储 - 当状态更改或不再使用时自动丢弃缓存数据
  • 内存高效 - 不会保留过时信息

动作选择器

创建一个动作可以通过专用类使用的选择器:

class ActionSelect {
  final AppState state;
  ActionSelect(this.state);

  List<Item> get items => state.items;
  Item get selectedItem => state.selectedItem;

  Item? findById(int id) =>
      state.items.firstWhereOrNull((item) => item.id == id);

  Item? searchByText(String text) =>
      state.items.firstWhereOrNull((item) => item.text.contains(text));

  int get selectedIndex => state.items.indexOf(state.selectedItem);
}

在您的基本动作中添加一个getter:

abstract class AppAction extends ReduxAction<AppState> {
  ActionSelect get select => ActionSelect(state);
}

在动作中使用:

class LoadItemAction extends AppAction {
  final int itemId;
  LoadItemAction(this.itemId);

  @override
  AppState? reduce() {
    var item = select.findById(itemId);
    if (item == null) return null;
    return state.copy(selectedItem: item);
  }
}

Widget选择器

创建一个WidgetSelect类以组织widget级选择器:

class WidgetSelect {
  final BuildContext context;
  WidgetSelect(this.context);

  List<Item> get items => context.select((st) => st.items);
  Item get selectedItem => context.select((st) => st.selectedItem);

  Item? findById(int id) =>
      context.select((st) => st.items.firstWhereOrNull((item) => item.id == id));

  Item? searchByText(String text) =>
      context.select((st) => st.items.firstWhereOrNull((item) => item.text.contains(text)));

  int get selectedIndex =>
      context.select((st) => st.items.indexOf(st.selectedItem));
}

添加到您的BuildContext扩展:

extension BuildContextExtension on BuildContext {
  AppState get state => getState<AppState>();
  R select<R>(R Function(AppState state) selector) => getSelect<AppState, R>(selector);
  WidgetSelect get selector => WidgetSelect(this);
}

在widget中使用:

Widget build(BuildContext context) {
  final item = context.selector.findById(42);
  return Text(item?.name ?? 'Not found');
}

在Widget中重用动作选择器

Widget选择器可以利用动作选择器来避免重复:

class WidgetSelect {
  final BuildContext context;
  WidgetSelect(this.context);

  Item? findById(int id) =>
      context.select((st) => ActionSelect(st).findById(id));

  Item? searchByText(String text) =>
      context.select((st) => ActionSelect(st).searchByText(text));
}

重要指南

避免在选择器中使用context.state

永远不要在选择器函数中使用context.state - 这会破坏选择性重建:

// 错误 - 在任何状态更改时重建
var items = context.select((state) => context.state.items.where(...));

// 正确 - 仅在items更改时重建
var items = context.select((state) => state.items.where(...));

永远不要嵌套context.select调用

嵌套context.select会导致错误:

// 错误 - 会导致错误
var result = context.select((state) =>
  context.select((s) => s.items).where(...)  // 嵌套select!
);

// 正确
var items = context.select((state) => state.items);
var result = items.where(...).toList();

与外部Reselect包比较

AsyncRedux的内置缓存与外部reselect包不同:

特性 AsyncRedux reselect
每个选择器的结果 多个(不同参数) 仅一个
状态更改时的内存 丢弃旧缓存 永久保留

完整示例:缓存过滤列表

// 带缓存的选择器
class UserSelectors {
  static List<User> usersStartingWith(AppState state, String prefix) =>
      _usersStartingWith(state.users)(prefix);

  static final _usersStartingWith = cache1state_1param(
    (List<User> users) => (String prefix) =>
        users.where((u) => u.name.startsWith(prefix)).toList()
  );

  static List<User> activeUsers(AppState state) =>
      _activeUsers(state.users);

  static final _activeUsers = cache1state(
    (List<User> users) => users.where((u) => u.isActive).toList()
  );
}

// 在widget中使用
Widget build(BuildContext context) {
  var filtered = context.select(
    (state) => UserSelectors.usersStartingWith(state, 'A')
  );
  return ListView.builder(
    itemCount: filtered.length,
    itemBuilder: (_, i) => Text(filtered[i].name),
  );
}

参考文献

文档中的URL: