什么是状态管理
在响应式编程框架中, UI开发就是对状态
(数据源
)进行封装, 将之转换成具体的UI界面
简化为如下公式
- UI: 表示屏幕上的展示的界面
- f: 你实现的build方法
- state: 状态(数据源)
Flutter通过build
方法,将我们拥有的状态(数据源)转换成具体的UI界面
状态类型
短时状态(ephemeral State)
: 包含在单个Widget且不会与其他Widget共享的状态应用状态(App State)
: 多个Widget共享
的状态(数据)
共享类型
- 子widget共享父类widget
- 父widget共享子widget状态
- 兄弟widget共享状态
如果widget之间需要共享状态,就把这个状态提升到两个Widget的共同祖先
中,将这个短时状态变成应用状态
Flutter中状态管理
要解决的根本问题,就是如何在任意一个Widget中获取应用状态
如何共享状态
构造方法
: 将父Widget中状态(数据)传递给子Widget提高父Widget层级
, 比如把状态定义为全局的单例
对象, 在任何地方都能获取到它
但这两种方式无法满足大型项目的需求, 大型项目一般使用如下三种办法, 最推荐的是使用 Provider
- 事件同步(
EventBus
) InheritedWidget
Provider
和Scoped Model
是对InheritedWidget的API封装,让我们能够少写重复性的代码
事件同步状态
- 使用全局事件总线
EventBus
- 观察者模式的实现
- 注意需要在 dispose时候
bus.off
关闭事件,避免内存泄漏
// 定义事件
enum Event{
login,
...
}
// 登录状态改变后发布状态改变事件
bus.emit(Event.login);
void onLoginChanged(e){
}
@override
void initState() {
//订阅登录状态改变事件
bus.on(Event.login,onLogin);
super.initState();
}
@override
void dispose() {
//取消订阅
bus.off(Event.login,onLogin);
super.dispose();
}
InheritedWidget
- 在 widget 树中
从上到下``跨级
共享数据,比如根 widget 中通过InheritedWidget共享了一个数据,那么便可以在任意子widget 中来获取该共享的数据! - 这个特性在一些需要在整个 widget 树中共享数据的场景中非常方便!如Flutter SDK中正是通过 InheritedWidget 来共享
应用主题(Theme)和 Locale (当前语言环境)
信息的。
class FrogColor extends InheritedWidget {
/// 构造方法
const FrogColor({
super.key,
required this.color,
required super.child,
});
/// 需要共享的数据
final Color color;
/// 默认的约定:如果状态是希望暴露出的,应当提供一个`of`静态方法来获取其对象,开发者便可直接通过该方法来获取
/// 返回实例对象,方便子树中的widget获取共享数据
static FrogColor of(BuildContext context) {
final FrogColor? result = maybeOf(context);
assert(result != null, 'No FrogColor found in context');
return result!;
}
/// 是否通知widget树中依赖该共享数据的子widget
/// 这里当color发生变化时,是否通知子树中所有依赖color的Widget重新build
/// 这里判断注意:是值改变还是内存地址改变。
@override
bool updateShouldNotify(FrogColor oldWidget) => color != oldWidget.color;
}
在子widget中调用of方法获取状态
// continuing from previous example...
class MyPage extends StatelessWidget {
const MyPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: FrogColor(
color: Colors.green,
child: Builder(
builder: (BuildContext innerContext) {
return Text(
'Hello Frog',
style: TextStyle(color: FrogColor.of(innerContext).color),
);
},
),
),
);
}
}
Provider
对InheritedWidget的包装
, 简化了状态共享和获取的代码,最推荐的状态管理方式
void main() {
runApp(
// MultiProvider 支持多个状态
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
],
child: const MyApp(),
),
);
}
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
// 通知所有依赖 Counter 的Widget刷新
notifyListeners();
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text('You have pushed the button this many times:'),
/// 作为单独的小部件提取,用于性能优化。
///作为一个单独的小部件,它将独立于[MyHomePage]进行重建。
///同样,我们也可以使用[Consumer]或[Selector]。
Count(),
],
),
),
floatingActionButton: FloatingActionButton(
key: const Key('increment_floatingActionButton'),
/// context.read读取状态, Counter变化时不会自动rebuild
onPressed: () => context.read<Counter>().increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class Count extends StatelessWidget {
const Count({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(
/// watch 表示Counter变化时候自动rebuild widget
'${context.watch<Counter>().count}',
key: const Key('counterState'),
style: Theme.of(context).textTheme.headlineMedium,
);
}
}
Model 数据改变, 通过 ChangeNotifierProvider 更新所有依赖改 Model 的 Widget