name: asyncredux-selectors
description: 创建和缓存选择器以高效访问状态。涵盖编写选择器函数、使用cache1和cache2进行缓存、重选模式以及在widget中避免重复计算。
什么是选择器?
选择器是从Redux存储状态中提取特定数据的函数。它们提供三个关键好处:
- 计算派生数据 - 将状态转换或过滤为您的widget需要的格式
- 抽象状态结构 - 组件不依赖于状态的如何组织
- 启用缓存(记忆化) - 避免不必要的重新计算
问题:重复计算
在显示过滤或计算的数据时,如果没有选择器,您可能会这样写:
// 低效 - 每次访问时过滤整个列表
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:
- https://asyncredux.com/sitemap.xml
- https://asyncredux.com/flutter/miscellaneous/cached-selectors
- https://asyncredux.com/flutter/miscellaneous/widget-selectors
- https://asyncredux.com/flutter/advanced-actions/action-selectors
- https://asyncredux.com/flutter/basics/using-the-store-state
- https://asyncredux.com/flutter/connector/store-connector
- https://asyncredux.com/flutter/connector/advanced-view-model