状态管理
setState
setState 是 flutter 内置的方法
通常有 3 种管理状态的方式
- 管理自身
- 父组件管理
- 混搭管理(将上面两种结合起来即可)
管理自身
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$_counter'),
ElevatedButton(
onPressed: () {
setState(() {
_counter++;
});
},
child: const Text('Increment'),
)
],
);
}
}
父组件管理
class TapWidget extends StatefulWidget {
const TapWidget({super.key});
State<TapWidget> createState() => _TapWidgetState();
}
class _TapWidgetState extends State<TapWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
Widget build(BuildContext context) {
return SizedBox(
child: TapboxWidget(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
class TapboxWidget extends StatelessWidget {
const TapboxWidget({super.key, this.active = false, required this.onChanged});
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context) {
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Text(
active ? 'Active' : 'Inactive',
),
ElevatedButton(
onPressed: _handleTap,
child: const Text('Tap me'),
)
]);
}
}
flutter_redux
各种包太多了,为了方便选择了 flutter_redux
安装
flutter pub add flutter_redux
配置 pubspec.yaml 文件
dependencies:
flutter_redux: ^0.10.0
redux: ^5.0.0
创建 state、action、reducer
此处将其放在一个文件内
// 创建 state
class UserState {
String nickname;
String age;
String birthday;
UserState({
this.nickname,
this.age,
this.birthday,
});
// 初始化
factory UserState.initial() => UserState(nickname: '', age: '', birthday: '');
String toString() {
return 'nickname: $nickname, age: $age, birthday: $birthday}';
}
}
// 创建 action
enum UserAction{
getProfile,
setProfile
}
// 创建 reducer
UserState userReducer(UserState prevState, action) {
UserState state
switch (action['type']) {
case UserAction.getProfile:
state = prevState;
break;
case UserAction.setProfile:
final data = action['payload'];
state = UserState(
nickname: data['nickname'],
age: data['age'],
birthday: data['birthday']
);
break;
default:
state = prevState;
break;
}
return state;
}
创建 store
如无特殊需求,通常和 state、action、reducer 在同一个文件内创建
import 'package:redux/redux.dart';
void main() {
Store<UserState> store = Store<UserState>(
userReducer,
initialState: UserState.initial()
);
runApp(MyApp(store: store));
}
store 与根组件关联
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
void main() {
Store<UserState> store = Store<UserState>(
userReducer,
initialState: UserState.initial()
);
runApp(MyApp(store: store));
}
class MyApp extends StatelessWidget {
final Store<TokenState> store;
const MyApp({Key? key, required this.store}) : super(key: key);
Widget build(BuildContext context) {
return StoreProvider<TokenState>(
store: store,
child: MaterialApp(
title: 'Flutter Demo',
// ...
)
)
}
}
store 与其他组件关联
import 'package:flutter_redux/flutter_redux.dart';
class AppStart extends StatelessWidget {
const AppStart({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StoreConnector<TokenState, TokenState>(
// 这里有回调几个事件,具体查看文档
onWillChange: onReduxChange, // 状态改变时的回调
onInitialBuild: afterBuild, // 初始化时的回调
builder: (context, tokenState) {
return Text('token: ${tokenState.token}');
},
converter: (store) => store.state,
),
StoreConnector<TokenState, VoidCallback>(
// 需要执行 dispatch 时的类型不同
builder: (context, callback) {
return ElevatedButton(
onPressed: callback,
child: const Text('change token'),
);
},
// 这里要注意一下语法,需要返回一个函数,否则 onPressed 无法正常执行
converter: (store) {
return () => store.dispatch(
{
'type': TokenAction.setToken,
'payload': Random().nextInt(100).toString()
},
);
},
),
// 当执行 callback 需要传参时,将 StoreConnector 的第二个参数类型改为自定义参数即可,此时需要注意调用 callback 时的写法
StoreConnector<TokenState, dynamic>(
builder: (context, callback) {
return ElevatedButton(
onPressed: () {
callback('123')
},
child: const Text('change token'),
);
},
// 这里要注意一下语法,需要返回一个函数,否则 onPressed 无法正常执行
converter: (store) {
return (data) => store.dispatch(
{
'type': TokenAction.setToken,
'payload': Random().nextInt(100).toString()
},
);
},
)
],
),
),
);
}
void onReduxChange(TokenState? prevState, TokenState state) {
print('prevState: $prevState');
print('state: $state');
}
void afterBuild(TokenState state) {
print('afterBuild: $state');
}
}
StoreConnector 主要是起一个数据转化的作用,可以在组件赋值之前做一些数据转化操作。有两个泛型参数
- 原本的
store类型, - 需要转换的参数类型,它可以是类,方法,基本数据类型等都可以
需要声明两个方法:
builder接收数据,构建组件交互,第二个参数就是我们声明的转化参数的返回值,返回值类型视传入的泛型类型而定。converter起数据转化作用,返回值会直接传递到builder方法的第二个参数中去。
多 store 使用
当使用多 store 时,需要多次调用 StoreProvider 方法,然后使用时根据传入的泛型类型区分是哪个 store
根组件多 store
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
void main() {
final store1 = new Store<int>(counterReducer, initialState: 0);
final store2 = new Store<String>(textReducer, initialState: "");
runApp(new MyApp(store1, store2));
}
class MyApp extends StatelessWidget {
final Store<int> store1;
final Store<String> store2;
MyApp(this.store1, this.store2);
Widget build(BuildContext context) {
// 注意第一个泛型变量
return new StoreProvider<int>(
store: store1,
child: new StoreProvider<String>(
store: store2,
child: new MaterialApp(
title: 'Flutter Redux Demo',
home: new MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatelessWidget {
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Flutter Redux Demo"),
),
body: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// 注意第一个泛型变量
new StoreConnector<int, String>(
converter: (store) => store.state.toString(),
builder: (context, count) {
return new Text(
"Counter: $count",
style: new TextStyle(fontSize: 20.0),
);
},
),
// 注意第一个泛型变量
new StoreConnector<String, String>(
converter: (store) => store.state,
builder: (context, text) {
return new Text(
"Text: $text",
style: new TextStyle(fontSize: 20.0),
);
},
),
],
),
floatingActionButton: new Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
new FloatingActionButton(
onPressed: () {
// 注意第一个泛型变量
StoreProvider.of<int>(context).dispatch(new IncrementAction());
},
child: new Icon(Icons.add),
),
new Padding(
padding: new EdgeInsets.only(top: 16.0),
child: new FloatingActionButton(
onPressed: () {
// 注意第一个泛型变量
StoreProvider.of<String>(context).dispatch(new ChangeTextAction());
},
child: new Icon(Icons.text_fields),
),
),
],
),
)
}
}
子组件多 store
class MyChildWidget extends StatelessWidget {
Widget build(BuildContext context) {
return new StoreProvider<int>(
// 仅在此处不同,直接在此处创建即可
store: new Store<int>(counterReducer, initialState: 0),
child: new StoreProvider<String>(
store: new Store<String>(textReducer, initialState: ""),
child: new Column(
children: <Widget>[
new StoreConnector<int, String>(
converter: (store) => store.state.toString(),
builder: (context, count) {
return new Text(
"Counter: $count",
style: new TextStyle(fontSize: 20.0),
);
},
),
new StoreConnector<String, String>(
converter: (store) => store.state,
builder: (context, text) {
return new Text(
"Text: $text",
style: new TextStyle(fontSize: 20.0),
);
},
),
new RaisedButton(
onPressed: () {
StoreProvider.of<int>(context).dispatch(new IncrementAction());
},
child: new Text("Increment"),
),
new RaisedButton(
onPressed: () {
StoreProvider.of<String>(context).dispatch(new ChangeTextAction());
},
child: new Text("Change Text"),
),
],
),
),
);
}
}
StoreBuilder
有时候需要在一个组件内既获取 store 又执行 dispatch,这时候推荐使用 StoreBuilder 组件
class MyWidget extends StatefulWidget {
Widget build(BuildContext context) {
return StoreBuilder<VideoState>(
builder: (context, store) {
print('这是store:$store');
return ElevatedButton(
onPressed: () {
// 这里可以执行 dispatch
store.dispatch(
{
'type': VideoAction.setLoading,
'payload': false,
},
);
},
child: Text('123'),
),
},
);
}
}
中间件
用于拦截 action -> reducer 的过程,将其变为 action -> middleware -> reducer
该操作可以实现如异步 action、日志输出、异常报告等功能
// 创建
Middleware<AppState> appMiddleware = (Store<AppState> store, action, NextDispatcher next) {
// 执行需要的操作
print('dispatching ${action.runtimeType}');
// 将操作传递给下一个中间件或store
next(action);
};
// 使用
final store = Store<AppState>(
appReducer,
initialState: AppState.initial(),
middleware: [appMiddleware]
)
- 当使用多个中间件时,它们将按顺序执行
- 在中间件中调用
dispatch发送其他action时,会递归先处理新发出的action - 位于
next后面的代码,会在所有中间件执行完毕后,以中间件组的倒序的方式来执行
异步操作
需要用到 redux_thunk 包
import 'package:redux_thunk/redux_thunk.dart';
// 注册中间件
Store<AppState> store = Store<AppState>(
tokenReducer,
initialState: AppState.init(),
middleware: [thunkMiddleware], // thunkMiddleware 这个中间件是 redux_thunk 包自带的
);
// 创建异步 action
// params 是为了接收调用时候的传参,如果无需传参则直接返回一个异步函数即可
ThunkAcion<AppState> action(Map<String, String> params) => (Store<AppState> store) async {
var res = await login(params);
store.dispatch({'type': AppAction, 'payload': res.data});
};
// 调用
StoreConnector<AppState, VoidCallback>(
builder: (context, callback) {
return ElevatedButton(
onPressed: callback,
child: const Text('Login'),
);
},
converter: (store) {
return () => store.dispatch(
tokenAction({'username': 'admin', 'password': '123456'}));
},
)
distinct 属性
store 有个 distinct 属性,用于设置 store 改变的时候是否重新渲染,返回 true 表示不渲染。
对于 store 中有多个属性,但只改变其中某个属性值时不需要重新渲染的情况下尤其有用
该操作通常需要搭配自定义 == 操作符和 hashCode 一起使用,否则无效
class VideoState {
bool isLoading;
Video? video;
VideoState({
this.isLoading = false,
this.video,
});
factory VideoState.initial() => VideoState(isLoading: false, video: null);
// 这里的 other 根据需要判断返回 true 或者 false
bool operator ==(Object? other) {
if (other == null || !(other is VideoState)) {
return false;
}
return other.video == this.video;
}
// hashCode 也要重写
get hashCode => video.hashCode;
}