懂 Vue、React 就懂 Flutter 状态管理
嘿!你是不是经常在开发Flutter应用的时候被状态搞得头大?别担心,我也是,每次面对不同的flutter项目应用着不同的状态管理方案在阅读时总会有一定的心智负担,尤其是总觉得 flutter 状态管理要比 Vue, React 更难? 其实不是的,本文梳理了一下常见的 flutter 状态管理实践,帮助大家加深一下flutter状态的理解。
状态提升
即使我们不引入任何状态管理的库,只要我们继承 StatefulWidget 创建了一个 State 对象,我们就已经开始了状态管理。
class MyStatefulWidget extends StatefulWidget {
@override _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
String _displayText = 'Hello, world!';
void _updateText() {
setState(() {
_displayText = 'something changed!';
});
}
StatefulWidget 由两部分组成:一个是 StatefulWidget 类,另一个是对应的 State 类。StatefulWidget 类负责定义小部件的结构和配置,而 State 类负责跟踪和管理小部件的状态。
StatefulWidget 可以包含一个或多个 State 对象,每个 State 对象都与 StatefulWidget 对象相关联,并负责处理小部件的状态变化和重建。在本例中,我们的状态就是 Line 6 的 _displayText
, 我们定义一个方法 _updateText
, 我们可以给这个方法绑定一个交互事件,比如点击按钮,点击键盘,滑动屏幕等,只要交互触发了这个 _updateText
方法,我们就通过 setState
改变了 _displayText
的值。是不是和 Class 时代的 React 一模一样?
在Flutter中,有了stateful widgets,我们可以在小部件内部管理它的状态。但是,当我们的应用程序变得更加复杂时,问题就会发生了。状态可能会在不同的小部件之间共享,或者我们可能需要在不同的小部件之间进行状态同步。在 React 中我们怎么办呢?状态提升!
将状态提升到共同的父级小部件:如果两个小部件之间需要共享状态,可以将该状态提升到它们的共同父级小部件中。然后,父级小部件可以传递该状态给它们,并在需要时更新状态。这样,两个小部件就可以共享同一个状态。
举个例子,2个组件需要同步状态,Text 组件需要显示当前 switch 的状态,比如开启还是关闭, 另一个 switch 组件负责更改状态,那这2个组件在同一个UI中应该如何同步呢? 我们需要将 switch 的状态提取到父级来控制,将switch 这个子组件变成无状态组件,父级统一来同步 switch 状态的变更。
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _isSwitchOn = false;
void _toggleSwitch(bool newValue) {
setState(() {
_isSwitchOn = newValue;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ChildWidget(
isSwitchOn: _isSwitchOn,
toggleSwitch: _toggleSwitch,
),
Text('Switch is ${_isSwitchOn ? 'ON' : 'OFF'}'),
],
);
}
}
class ChildWidget extends StatelessWidget {
final bool isSwitchOn;
final Function(bool) toggleSwitch;
ChildWidget({required this.isSwitchOn, required this.toggleSwitch});
@override
Widget build(BuildContext context) {
return Switch(
value: isSwitchOn,
onChanged: toggleSwitch,
);
}
}
在这个示例中,ParentWidget
是一个有状态的小部件,它包含一个布尔值 _isSwitchedOn
作为状态。ChildWidget
是一个无状态的小部件,它显示一个开关,其值由父组件的状态控制。
当用户在 ChildWidget
上切换开关时,会调用父组件的 _toggleSwitch
方法,从而更新 _isSwitchedOn
的值,值变化会导致重新构建父组件的小部件树。这样,ChildWidget
和 Text
小部件都会反映出新的开关状态。
但是,当组件状态共享情况变得复杂时,提升状态会变得困难,尤其是组件嵌套层级很深的2个组件要共享状态,那要提升到最父级然后层层透传状态维护起来简直是灾难。
这时候该全局状态管理库出场了,状态管理工具可以帮助我们更好地组织和管理应用程序中的状态。它们提供了一种方式来共享和跟踪状态,使我们的代码更易于维护和扩展。一些常见的状态管理解决方案包括Provider、Redux和GetX。
Provider
我们来详细介绍一下 Provider,它基于InheritedWidget和ChangeNotifier的组合。可以创建一个继承自ChangeNotifier的类来表示应用程序状态。
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
我们的状态就是 _count
, 以及状态变更方法 increment
, 当有人调用 increment
后我们的 _count 会+1,同时我们也通知所有的订阅者,告诉他们这个 _count 值发生了改变,需要重新渲染 UI。
在我们的Flutter应用程序中,我们可以使用ChangeNotifierProvider来提供状态给需要访问它的小部件,在下面这个例子中,我们将 Counter 的状态提供给 MyApp 组件。
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}
Provider还提供了一些便利的方法,如Provider.of和Consumer,可以使用Provider.of方法来获取状态,并在小部件树中的任何位置访问它。而Consumer小部件则允许你对状态进行监听和响应。
例如下面,我们使用Consumer小部件来订阅 Counter 状态的变化,只要 _count 值变化,这个Text 显示的文本就会变化。我们通过 Provider.of 来获取 Counter 的状态,和触发 Counter 状态的变更。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Provider Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Count:',
),
Consumer<Counter>(
builder: (context, counter, child) => Text(
'${counter.count}',
style: TextStyle(fontSize: 24),
),
),
RaisedButton(
child: Text('Increment'),
onPressed: () {
Provider.of<Counter>(context, listen: false).increment();
},
),
],
),
),
),
);
}
}
当我们的程序状态比较多,我们可以建立多个 provider 来管理应用的数据
MultiProvider(
providers: [
ChangeNotifierProvider<Counter>(create: (_) => Counter()),
ChangeNotifierProvider<ThemeModel>(create: (_) => ThemeModel()),
// 添加更多的Provider
],
child: MyApp(),
)
在 React 中我们为了避免重复的 UI 渲染,我们通常会只订阅有变化的数据,在 Flutter 中也一样,我们可以通过 Selector 选择性地监听特定的状态变化, 来避免没有意义的重新渲染 UI或者实现一些自定渲染逻辑。当然这里需要提醒的是,如果组件数不复杂,防止重不是性能的瓶颈。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer2<MyProvider, AnotherProvider>(
builder: (context, myProvider, anotherProvider, child) {
// 根据两个状态进行自定义逻辑
String message = myProvider.count > 5 && anotherProvider.isReady
? '条件满足'
: '条件不满足';
return Text(
message,
style: TextStyle(fontSize: 20),
);
},
selector: (context, myProvider, anotherProvider) =>
'${myProvider.count}${anotherProvider.isReady}',
);
}
}
class MyProvider with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class AnotherProvider with ChangeNotifier {
bool _isReady = false;
bool get isReady => _isReady;
void setReady(bool value) {
_isReady = value;
notifyListeners();
}
}
在这个例子中,MyWidget订阅了MyProvider和AnotherProvider两个状态。当count和isReady满足一定条件时,Text小部件会显示"条件满足",否则显示"条件不满足"。通过使用Selector,我们可以自定义选择逻辑,并且只有在选择的状态发生变化时才会重新构建Text小部件。这样可以避免不必要的重建,提高性能。
最后来分享一下 PorxyProvider 来处理更复杂的情况,比如:
- 当一个状态依赖于其他状态的计算结果时,可以使用
ProxyProvider
来根据依赖的状态计算出新的状态。 - 当某个状态需要经过复杂的逻辑处理,而不仅仅是简单的属性获取时,可以使用
ProxyProvider
来处理逻辑并提供新的状态给子部件使用。 - 当某个状态需要在多个子部件中共享时,可以使用
ProxyProvider
来提供该状态给多个子部件使用。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ProxyProvider<MyProvider, AnotherProvider>(
update: (context, myProvider, anotherProvider) {
// 根据MyProvider计算得到AnotherProvider的值
bool isReady = myProvider.count > 10;
return AnotherProvider(isReady);
},
child: Consumer<AnotherProvider>(
builder: (context, anotherProvider, child) {
return Text(
'Is Ready: ${anotherProvider.isReady}',
style: TextStyle(fontSize: 20),
);
},
),
);
}
}
class MyProvider with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class AnotherProvider with ChangeNotifier {
bool _isReady;
bool get isReady => _isReady;
AnotherProvider(this._isReady);
}
还是和之前的例子类似,我们改为ProxyProvider,MyWidget使用了ProxyProvider和Consumer。ProxyProvider可以根据一个或多个依赖的状态计算得到新的状态,并提供给后续的子部件。在这个例子中,ProxyProvider会根据MyProvider中的count属性计算出AnotherProvider的isReady属性。当count大于10时,isReady为true,否则为false。
结尾
状态管理在构建强大和可扩展的 Flutter 应用时非常重要,会让我们的代码变得更容易维护。Stateful Widgets、Provider 或者 BLoC 模式只是 Flutter 中的几种状态管理方法。每种方法没有好坏,只有合适的场景和个人的偏好。
如果你觉得这篇文章有帮助,请点个赞和关注吧❤️!我将持续分享更多有价值的技术知识和经验。谢谢!
转载自:https://juejin.cn/post/7283813865719087143