# 状态管理方案-Redux
# 工作原理
Redux是一个应用状态管理框架。换句话说,它的主要目标是管理一个状态。
Redux围绕以下原则构建:
单向数据流
Store
- 只存储一个状态
- 暴露了一个入口点,称为dispatch,它只接受参数中的Action。
- 暴露了一个getters来获取当前状态。
- 允许(不)注册,以便(通过StreamSubscription)通知State的任何变化。
- 将动作和存储分配给第一个 MiddleWare 的
- 将动作和当前状态分配给一个Reducer(可能是多个Redducers的门面)。 行动
Actions
Actions是Store访问点接受的唯一输入类型。MiddleWare和Reducer使用Action和当前状态来处理某些功能,这可能导致修改状态。
Actions只描述发生了什么。
MiddleWare
中间件是一种功能,通常以异步运行(但不一定)为目的,基于一个Action。中间件只是简单地使用一个状态(或一个Action作为触发器),但不改变状态。
Reducers
Reducer通常是一个同步函数,它根据Action-State的组合进行一些处理。处理的结果可能导致一个新的State。 Reducer是唯一一个被允许改变状态的函数。
需要注意的是,根据Redux的建议和良好实践,每个应用程序只有一个单一的Store。为了拆分数据处理逻辑,建议使用 "reducer composition "而不是许多Store。
解析:
- 当UI层发生某些事情时(但实际上不限于UI),会创建一个Action并发送给Store(通过store.dispatch(action))。
- 如果配置了一个或多个中间件,它们会被依次调用,并将Action和对Store的引用传递给它们(因此,也间接地传递给State)。
- Middlewares本身也可以在处理过程中向Store发送一个Action。
- 然后,Action和当前状态也会被发送到Reducer。
- Reducer是唯一一个有可能改变State的东西
- 当状态发生变化时,Store会通知所有注册的监听者以告知他们。
- 然后,UI(但不限于UI)可以采取与状态变化相关联的适当操作。
# 安装依赖
Redux最初是为Javascript开发的,并作为一个包移植到Dart。
一个Flutter专用包(flutter_redux (opens new window))提供了一些Widgets,比如。
- StoreProvider将Store传递给所有的子代Widgets。
- StoreBuilder从StoreProvider获取Store,并将其传递给Widget builder函数。
- StoreConnector从最近的StoreProvider祖先中获取Store,将其转换为ViewModel并传递给构建函数。
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
redux: ^5.0.0
flutter_redux: ^0.8.2
然后使用flutter pub get
安装依赖。
# Redux案例 - 计数器
# 核心概念
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
// 1.删除无用的文件,删除注释
enum Actions { increment }
// 传递进来一个state + action
// 返回去一个新的state
int counterReducer(int state, action) {
if (action == Actions.increment) {
// action -> enum
return state + 1;
}
return state;
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// 2.创建Store -> state
final store = Store(counterReducer, initialState: 5);
MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
// 3.使用StoreProvider
return StoreProvider(
store: store,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHome(),
),
);
}
}
class MyHome extends StatelessWidget {
const MyHome({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: [
Text('计数器的状态:'),
// 4.添加按钮 -> dispatch -> action -> reducer -> 更新state
// builder -> 最终返回的widgets
// StoreConnector -> <初始的state类型, 最终输出到builder的类型>
// converter -> 取用最近的Provider -> store -> 转化成我们需要的类型数据
// builder -> 返回一个Widget -> 使用state -> viewModel
StoreConnector<int, String>(
builder: (context, countStr) {
return Text(
countStr,
style: const TextStyle(
fontSize: 24.0,
),
);
},
converter: (store) => store.state.toString(),
),
],
),
),
floatingActionButton: StoreConnector<int, VoidCallback>(
builder: (context, callback) => FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
// 触发dispatch方法 -> action -> Actions.increment
callback();
},
),
converter: (store) {
// 返回store.dispatch -> Funciton -> builder的第二个参数
return () => store.dispatch(Actions.increment);
},
),
);
}
}
# 优化案例&拆分逻辑
新建文件:lib/store/state.dart
class AppState {
final int count;
AppState(this.count );
AppState.initState()
: count = 0;
}
新建文件:lib/store/actions.dart
enum CountActions {
increment,
decrement,
}
新建文件:lib/store/reducers.dart
import 'actions.dart';
int counterReducer(int state, action) {
if (action == CountActions.increment) {
// action -> enum
return state + 1;
}
if (action == CountActions.decrement) {
return state - 1;
}
return state;
}
优化main.dart
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/reducers.dart';
import 'package:redux_example/store/state.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// 2.创建Store -> state
final store = Store(appStateReducer, initialState: AppState(0));
MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
// 3.使用StoreProvider
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHome(),
),
);
}
}
class MyHome extends StatelessWidget {
const MyHome({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: [
Text('计数器的状态:'),
// 4.添加按钮 -> dispatch -> action -> reducer -> 更新state
// builder -> 最终返回的widgets
// StoreConnector -> <初始的state类型, 最终输出到builder的类型>
// converter -> 取用最近的Provider -> store -> 转化成我们需要的类型数据
// builder -> 返回一个Widget -> 使用state -> viewModel
StoreConnector<AppState, String>(
builder: (context, countStr) {
return Text(
countStr,
style: const TextStyle(
fontSize: 24.0,
),
);
},
converter: (store) => store.state.count.toString(),
),
],
),
),
// 1. 需要添加一个按钮 -> onPress -> new Action
// 2. 需要添加action -> decrement
// 3. 需要配置reducer -> dispatch -> new state
floatingActionButton:
StoreConnector<AppState, Function(CountActions action)>(
builder: (context, callback) => Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
// 触发dispatch方法 -> action -> Actions.increment
callback(CountActions.increment);
},
),
const Padding(
padding: EdgeInsets.only(top: 10.0),
),
FloatingActionButton(
child: const Icon(Icons.remove),
onPressed: () {
// 触发dispatch方法 -> action -> Actions.increment
callback(CountActions.decrement);
},
),
],
),
converter: (store) {
// 返回store.dispatch -> Funciton -> builder的第二个参数
return (action) => store.dispatch(action);
},
),
);
}
}
# Redux案例 - TodoList
# 创建actions,reducers,state
修改文件:lib/store/state.dart
// 定义全局的state
// int count;
// List<Item> lists;
// Item {int id, String value}
class Item {
final int? id;
final String value;
Item({this.id, required this.value});
}
class AppState {
final int count;
final List<Item> lists;
AppState({this.count = 0, this.lists = const <Item>[]});
AppState.initState()
: count = 0,
lists = const <Item>[];
}
Tips:这里要巧用vscode的快捷菜单来创建constructors。
修改文件:lib/store/actions.dart
// 1.删除无用的文件,删除注释
enum CountActions {
increment,
decrement,
}
// TodoAddAction, TodoRemoveAction, TodoClearAction
class TodoAddAction {
// Item -> int id, String text
static int _id = 0;
final String? text;
TodoAddAction(this.text) {
_id++;
}
int get id => _id;
}
修改文件:lib/store/reducers.dart
// 传递进来一个state + action
// 返回去一个新的state
import 'package:redux_example/store/state.dart';
import 'actions.dart';
int counterReducer(int state, action) {
if (action == CountActions.increment) {
// action -> enum
return state + 1;
}
if (action == CountActions.decrement) {
return state - 1;
}
return state;
}
List<Item> todoReducer(List<Item> state, action) {
if (action is TodoAddAction) {
// 添加item -> lists
// 新item -> 从传入action来
// 新lists -> 新item -> 添加到旧的lists后面, add & 扩展运算符 ...
return [
...state,
Item(id: action.id, value: action.text ?? ''),
];
}
return state;
}
AppState appStateReducer(AppState state, dynamic action) {
return AppState(
count: counterReducer(state.count, action),
lists: todoReducer(state.lists, action),
// ...
);
}
// 1.创建state -> actions -> reducers
// 2.初始化store -> StoreProvider
// 3.添加input -> add button
// 4.分发actions -> store.dispatch -> new state
# 封装ViewModel
目标:
- 管理所有的state与state相关的actions;
- 作为一个统一的入口组件;
- 清晰的逻辑关系,封装StoreConnecter,redux的commit操作;
创建lib/widgets/view_model.dart
文件:
// 1.定义所有的reducer方法
// 2.定义工厂函数,返回所有的state状态 -> appState
import 'package:redux/redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/state.dart';
class ViewModel {
final List<Item> lists;
final Function addItem;
// count计数器
final int count;
final Function onIncrement;
final Function onDecrement;
ViewModel({
required this.lists,
required this.addItem,
required this.count,
required this.onIncrement,
required this.onDecrement,
});
factory ViewModel.create(Store<AppState> store) {
_onIncrement() {
store.dispatch(CountActions.increment);
}
_onDecrement() {
store.dispatch(CountActions.decrement);
}
_onAddItem(String value) {
store.dispatch(TodoAddAction(value));
}
return ViewModel(
lists: store.state.lists,
addItem: _onAddItem,
count: store.state.count,
onIncrement: _onIncrement,
onDecrement: _onDecrement,
);
}
}
# 待办清单应用
学习目标:
- 学会如何封装组件、组件传参;
- 学会redux的综合应用:state,actions,reducers的作用,传参;
- 有(无)状态组件如何取用参数;
- 学习vscode的快捷菜单的使用;
# 封装add组件
创建lib/widgets/add.dart
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/view_model.dart';
class Add extends StatefulWidget {
final ViewModel viewModel;
const Add(
this.viewModel, {
Key? key,
}) : super(key: key);
State<Add> createState() => _AddState();
}
class _AddState extends State<Add> {
TextEditingController controller = TextEditingController();
Widget build(BuildContext context) {
return TextField(
controller: controller,
decoration: const InputDecoration(hintText: '请输入待办事项'),
onSubmitted: (String s) {
print('用户输入了$s');
print('用户输入了${controller.text}');
// callback();
widget.viewModel.addItem(controller.text);
controller.text = '';
},
);
}
}
# 列表组件
添加列表组件lib/widgets/lists_item_view.dart
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/view_model.dart';
class ListsItemView extends StatelessWidget {
final ViewModel viewModel;
const ListsItemView(
this.viewModel, {
Key? key,
}) : super(key: key);
Widget build(BuildContext context) {
return ListView(
children: viewModel.lists
.map((Item item) => ListTile(
title: Text('${item.id}-${item.value}'),
))
.toList(),
);
}
}
# 计数器显示组件
添加计数器显示组件lib/widgets/count_view.dart
import 'package:flutter/material.dart';
// import 'package:flutter_redux/flutter_redux.dart';
// import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/view_model.dart';
class CountView extends StatelessWidget {
final ViewModel viewModel;
const CountView(
this.viewModel, {
Key? key,
}) : super(key: key);
Widget build(BuildContext context) {
return Text(
viewModel.count.toString(),
style: const TextStyle(
fontSize: 24.0,
),
);
}
}
# 添加功能
修改main.dart
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/reducers.dart';
import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/add.dart';
import 'package:redux_example/widgets/count_view.dart';
import 'package:redux_example/widgets/lists_item_view.dart';
import 'package:redux_example/widgets/view_model.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// 2.创建Store -> state
final store = Store(appStateReducer, initialState: AppState());
MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
// 3.使用StoreProvider
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHome(),
),
);
}
}
class MyHome extends StatefulWidget {
const MyHome({Key? key}) : super(key: key);
State<MyHome> createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
Widget build(BuildContext context) {
return StoreConnector<AppState, ViewModel>(
builder: (context, viewModel) => Scaffold(
appBar: AppBar(),
body: SizedBox(
height: MediaQuery.of(context).size.height,
child: Column(
children: [
const Text('计数器的状态:'),
// 4.添加按钮 -> dispatch -> action -> reducer -> 更新state
// builder -> 最终返回的widgets
// StoreConnector -> <初始的state类型, 最终输出到builder的类型>
// converter -> 取用最近的Provider -> store -> 转化成我们需要的类型数据
// builder -> 返回一个Widget -> 使用state -> viewModel
CountView(viewModel),
Add(viewModel),
Expanded(
child: ListsItemView(viewModel),
)
],
),
),
// 1. 需要添加一个按钮 -> onPress -> new Action
// 2. 需要添加action -> decrement
// 3. 需要配置reducer -> dispatch -> new state
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
// 触发dispatch方法 -> action -> Actions.increment
viewModel.onIncrement();
},
),
const Padding(
padding: EdgeInsets.only(top: 10.0),
),
FloatingActionButton(
child: const Icon(Icons.remove),
onPressed: () {
// 触发dispatch方法 -> action -> Actions.increment
viewModel.onDecrement();
},
),
],
),
),
converter: (store) => ViewModel.create(store),
);
}
}
# 作业:删除与清空功能
目标:
- 点击删除,可以删除某个待办清单项;
- 点击清空,可以清空所有的清单列表
步骤:
- 添加actions,state,reducers;
- 添加view_model.dart中的对应的方法;
- 添加页面上的元素(按钮)与点击回调的事件;
- 回调viewModel中的方法,同时需要传参的进行参数的传递;
# 修改actions,state,reducers
首先是actions,这里要注意TodoClearAction
这个类,没有参数,所以也不需要加构造函数。
// 1.删除无用的文件,删除注释
enum CountActions {
increment,
decrement,
}
class TodoAddAction {
// Item -> int id, String text
static int _id = 0;
final String? text;
TodoAddAction(this.text) {
_id++;
}
int get id => _id;
}
class TodoRemoveAction {
final int id;
TodoRemoveAction(this.id);
}
class TodoClearAction {}
然后state可以不变,最后是reducers:
扩展:这里使用flutter_redux中的TypedReducer去减少对于类型的判断。
// 传递进来一个state + action
// 返回去一个新的state
import 'package:redux/redux.dart';
import 'package:redux_example/store/state.dart';
import 'actions.dart';
int counterReducer(int state, action) {
if (action == CountActions.increment) {
// action -> enum
return state + 1;
}
if (action == CountActions.decrement) {
return state - 1;
}
return state;
}
final todoReducer = combineReducers<List<Item>>([
// TypedReducer<state的类型, 判断Action的类型>(真正的store state的处理逻辑)
TypedReducer<List<Item>, TodoAddAction>((state, action) =>
[...state, Item(id: action.id, value: action.text ?? '')]),
TypedReducer<List<Item>, TodoRemoveAction>((state, action) =>
state..removeWhere((Item item) => item.id == action.id)),
TypedReducer<List<Item>, TodoClearAction>((state, action) => const <Item>[]),
]);
// List<Item> todoReducer(List<Item> state, action) {
// if (action is TodoAddAction) {
// // 添加item -> lists
// // 新item -> 从传入action来
// // 新lists -> 新item -> 添加到旧的lists后面, add & 扩展运算符 ...
// return [
// ...state,
// Item(id: action.id, value: action.text ?? ''),
// ];
// }
// if (action is TodoRemoveAction) {
// return state..removeWhere((Item item) => item.id == action.id);
// }
// if (action is TodoClearAction) {
// return const <Item>[];
// }
// return state;
// }
AppState appStateReducer(AppState state, dynamic action) {
return AppState(
count: counterReducer(state.count, action),
lists: todoReducer(state.lists, action),
// ...
);
}
// 1.创建state -> actions -> reducers
// 2.初始化store -> StoreProvider
// 3.添加input -> add button
// 4.分发actions -> store.dispatch -> new state
# 调整ViewModel
// 1.定义所有的reducer方法
// 2.定义工厂函数,返回所有的state状态 -> appState
import 'package:redux/redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/state.dart';
class ViewModel {
final List<Item> lists;
final Function addItem;
// 新增的属性
final Function removeItem;
final Function clearItems;
final int count;
final Function onIncrement;
final Function onDecrement;
ViewModel({
required this.lists,
required this.addItem,
// 新增的属性
required this.removeItem,
required this.clearItems,
required this.count,
required this.onIncrement,
required this.onDecrement,
});
factory ViewModel.create(Store<AppState> store) {
_onIncrement() {
store.dispatch(CountActions.increment);
}
_onDecrement() {
store.dispatch(CountActions.decrement);
}
_onAddItem(String value) {
store.dispatch(TodoAddAction(value));
}
// 新增的逻辑
_onRemoveItem(int id) {
store.dispatch(TodoRemoveAction(id));
}
// 新增的逻辑
_onClearItems() {
store.dispatch(TodoClearAction());
}
return ViewModel(
lists: store.state.lists,
addItem: _onAddItem,
// 新增
removeItem: _onRemoveItem,
clearItems: _onClearItems,
count: store.state.count,
onIncrement: _onIncrement,
onDecrement: _onDecrement,
);
}
}
# 添加页面按钮与回调
修改main.dart
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/reducers.dart';
import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/add.dart';
import 'package:redux_example/widgets/count_view.dart';
import 'package:redux_example/widgets/lists_item_view.dart';
import 'package:redux_example/widgets/view_model.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// 2.创建Store -> state
final store = Store(appStateReducer, initialState: AppState());
MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
// 3.使用StoreProvider
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHome(),
),
);
}
}
class MyHome extends StatefulWidget {
const MyHome({Key? key}) : super(key: key);
State<MyHome> createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
Widget build(BuildContext context) {
return StoreConnector<AppState, ViewModel>(
builder: (context, viewModel) => Scaffold(
appBar: AppBar(),
body: SizedBox(
height: MediaQuery.of(context).size.height,
child: Column(
children: [
const Text('计数器的状态:'),
// 4.添加按钮 -> dispatch -> action -> reducer -> 更新state
// builder -> 最终返回的widgets
// StoreConnector -> <初始的state类型, 最终输出到builder的类型>
// converter -> 取用最近的Provider -> store -> 转化成我们需要的类型数据
// builder -> 返回一个Widget -> 使用state -> viewModel
CountView(viewModel),
Add(viewModel),
Expanded(
child: ListsItemView(viewModel),
),
ElevatedButton(
onPressed: () {
viewModel.clearItems();
},
child: const Text('删除所有的Item项'),
)
],
),
),
// 1. 需要添加一个按钮 -> onPress -> new Action
// 2. 需要添加action -> decrement
// 3. 需要配置reducer -> dispatch -> new state
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
// 触发dispatch方法 -> action -> Actions.increment
viewModel.onIncrement();
},
),
const Padding(
padding: EdgeInsets.only(top: 10.0),
),
FloatingActionButton(
child: const Icon(Icons.remove),
onPressed: () {
// 触发dispatch方法 -> action -> Actions.increment
viewModel.onDecrement();
},
),
],
),
),
converter: (store) => ViewModel.create(store),
);
}
}
修改lists_item_view.dart
:
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/view_model.dart';
class ListsItemView extends StatelessWidget {
final ViewModel viewModel;
const ListsItemView(
this.viewModel, {
Key? key,
}) : super(key: key);
Widget build(BuildContext context) {
return ListView(
children: viewModel.lists
.map((Item item) => ListTile(
title: Text('${item.id}-${item.value}'),
// 添加一个按钮,添加对应的viewModel中的事件
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
viewModel.removeItem(item.id);
},
),
))
.toList(),
);
}
}