赞
踩
Flutter应用是 声明式 的,这也就意味着 Flutter 构建的用户界面就是应用的当前状态,在 Flutter 应用中,当状态变化时,会重新构建部分界面,而不是原生Android或iOS的命令式。当Flutter应用的状态发生改变时(例如:点击了一个按钮,触发了某个动画或者某个值的更新),改变状态就会导致UI界面重绘。去改变用户界面本身是没有必要的(例如 widget.setText ),因为这样的代码不会在UI界面上更新,只要改变了状态,那么用户界面将重新构建。
- class MyHomePage extends StatefulWidget {
- const MyHomePage({super.key, required this.title});
- final String title;
- @override
- State<MyHomePage> createState() => _MyHomePageState();
- }
-
- class _MyHomePageState extends State<MyHomePage> {
- int _counter = 0;
- void _incrementCounter() {
- setState(() {
- _counter++;
- });
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: Text(widget.title),
- ),
- body: Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: <Widget>[
- const Text(
- 'You have pushed the button this many times:',
- ),
- Text(
- '$_counter',
- style: Theme.of(context).textTheme.headlineMedium,
- ),
- ],
- ),
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: _incrementCounter,
- tooltip: 'Increment',
- child: const Icon(Icons.add),
- ), // This trailing comma makes auto-formatting nicer for build methods.
- );
- }
- }
从上述代码可以看出,如果我们需要改变某个值,只需要使用 setState((){ }) 在此函数中更新就会导致界面重绘,会重新执行build构建UI,前提是此Widget是StatefulWidget的子类。
State方式的状态属于短暂状态,widget 树中其他部分不需要访问这种状态。不需要去序列化这种状态,这种状态也不会以复杂的方式改变,需要用的只是一个 StatefulWidget
。
如果我们想在应用中的多个部分之间共享一个非短时的状态,并且在用户会话期间保留这个状态,我们称之为应用状态(有时也称共享状态),为了管理应用状态,就需要研究使用Provider。
假设我们有2个页面,A页面编辑的信息,在B页面需要使用,那此时我们可能无法在B中使用State拿到A页面的信息,此时Provider就帮上忙了,需要访问一些全局的状态。比如,A页面的会被添加到B页面中。但是它可能需要检查和自己相同的元素是否已经被添加到B页面中。
这里我们出现了第一个问题:我们把当前页面的状态放在哪合适呢?
在 Flutter 中,有必要将存储状态的对象置于 widget 树中对应 widget 的上层。
为什么呢?在类似 Flutter 的声明式框架中,如果你想要修改 UI,那么你需要重构它。并没有类似 B.updateWith(newData)
的简单调用方法。很难通过外部调用方法修改一个 widget。即便自己实现了这样的模式,那也是和整个框架不相兼容。
比如在B页面的入口在A页面,那需要在A页面创建Provider,在B中可以共享到A页面创建的 数据,更新数据后A也能同步到最新的数据。
-
- void onTap(BuildContext context) {
- var model = ProviderModel(context);
- model.add(item);
- }
这里 B页面
可以在各种版本的 UI 中调用同一个代码路径,获取数据
-
- Widget build(BuildContext context) {
- var model = ProviderModel(context);
- return Continer(
- // ···
- );
- }
在上面的例子中,model会存在于A-B 的生命周期中。当它发生改变的时候,它会从上层重构 B页面
。因为这个机制,所以 B页面
无需考虑生命周期的问题—它只需要针对 providerModel声明所需显示内容即可。当内容发生改变的时候,旧的 B的
widget 就会消失,完全被新的 widget 替代。
在使用 provider 之前,请不要忘记在 pubspec.yaml 文件里加入依赖。
运行 flutter pub add 将 provider 添加为依赖:
flutter pub add provider
现在可以在代码里加入 import 'package:provider/provider.dart';
进而开始构建你的应用了
provider
你无须关心回调或者 InheritedWidgets
。但是你需要理解三个概念:
ChangeNotifier
是 Flutter SDK 中的一个简单的类。它用于向监听器发送通知。换言之,如果被定义为 ChangeNotifier
,你可以订阅它的状态变化。(这和大家所熟悉的观察者模式相类似)。
在 provider
中,ChangeNotifier
是一种能够封装应用程序状态的方法。对于特别简单的程序,你可以通过一个 ChangeNotifier
来满足全部需求。在相对复杂的应用中,由于会有多个模型,所以可能会有多个 ChangeNotifier
。 (不是必须得把 ChangeNotifier
和 provider
结合起来用,不过它确实是一个特别简单的类)。
在示例中用 ChangeNotifier
来管理状态。我们创建一个新类,继承它(可以把他理解成MVVM中的viewModel),像下面这样:
- class ProviderModel extends ChangeNotifier {
- /// Internal, private state of the cart.
- final List<Item> _items = [];
-
- /// An unmodifiable view of the items in the cart.
- UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
-
- /// The current total price of all items (assuming all items cost $42).
- int get totalPrice => _items.length * 42;
-
- /// Adds [item] to cart. This and [removeAll] are the only ways to modify the
- /// cart from the outside.
- void add(Item item) {
- _items.add(item);
- // This call tells the widgets that are listening to this model to rebuild.
- notifyListeners();
- }
-
- /// Removes all items from the cart.
- void removeAll() {
- _items.clear();
- // This call tells the widgets that are listening to this model to rebuild.
- notifyListeners();
- }
- }
唯一一行和 ChangeNotifier
相关的代码就是调用 notifyListeners()
。当模型发生改变并且需要更新 UI 的时候可以调用该方法。而剩下的代码就是 ProviderModel
和它本身的业务逻辑。可以这么理解,调用了 notifyListeners()
之后,会执行创建Provider的Build方法重新构建UI
如果使用创建的Provider:
- class MyApp extends StatelessWidget {
- const MyApp({super.key});
-
- // This widget is the root of your application.
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Flutter Demo',
- theme: ThemeData(
- primarySwatch: Colors.blue,
- ),
- home: ChangeNotifierProvider(
- create: (context) => ProviderModel(), child: MyHomePage(title: 'Flutter Demo Home Page')),
- );
- }
- }
或者
- class MyApp extends StatelessWidget {
- const MyApp({super.key});
-
- // This widget is the root of your application.
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Flutter Demo',
- theme: ThemeData(
- primarySwatch: Colors.blue,
- ),
- home: ChangeNotifierProvider(
- create: (context) => ProviderModel(),
- child: Consumer<ProviderModel>(builder: (context, viewModel, child) {
- return MyHomePage(title: 'Flutter Demo Home Page');
- })));
- }
- }
现在 ProviderModel
已经通过 ChangeNotifierProvider
在应用中与 widget 相关联。我们可以开始调用它了。
child: Consumer<ProviderModel>(builder: (context, viewModel, child) {}
我们必须指定要访问的模型类型。在这个示例中,我们要访问 ProviderModel
那么就写上 Consumer<ProviderModel>
。
Consumer
widget 唯一必须的参数就是 builder。当 ChangeNotifier
发生变化的时候会调用 builder 这个函数。(换言之,当你在模型中调用 notifyListeners()
时,所有相关的 Consumer
widget 的 builder 方法都会被调用。)
builder 在被调用的时候会用到三个参数。第一个是 context
,在每个 build 方法中都能找到这个参数。
builder 函数的第二个参数是 ChangeNotifier
的实例。它是我们最开始就能得到的实例。你可以通过该实例定义 UI 的内容
第三个参数是 child
,用于优化目的。如果 Consumer
下面有一个庞大的子树,当模型发生改变的时候,该子树 并不会 改变,那么你就可以仅仅创建它一次,然后通过 builder 获得该实例。
- return Consumer<ProviderModel>(
- builder: (context, cart, child) => Stack(
- children: [
- // Use SomeExpensiveWidget here, without rebuilding every time.
- if (child != null) child,
- Text('Total price: ${cart.totalPrice}'),
- ],
- ),
- // Build the expensive widget here.
- child: const SomeExpensiveWidget(),
- );
如果我们Widget树比较庞大,那么就需要考虑到,更换Consumer的位置,在需要更新的位置使用,这样当数据发生改变时就不会全盘重新构建 widget 树了。例如:
- return Consumer<ProviderModel>(
- builder: (context, cart, child) {
- return AWidget(
- // ...
- child: BWidget(
- // ...
- child: Text('Total price: ${cart.totalPrice}'),
- ),
- );
- },
- );
换成:
- return AWidget(
- // ...
- child: Consumer<ProviderModel>(
- builder: (context, cart, child) {
- return BWidget(
- // ...
- child: Text('Total price: ${cart.totalPrice}'),
- ),
- );
- },
- );
有的时候你不需要模型中的 数据 来改变 UI,但是你可能还是需要访问该数据。比如A页面的一个按钮能够B页面的数据。它不需要显示B页面里的内容,只需要调用 clear()
方法。
我们可以使用 Consumer<ProviderModel>
来实现这个效果,不过这么实现有点浪费。因为我们让整体框架重构了一个无需重构的 widget,所以这里我们可以使用 Provider.of
,并且将 listen
设置为 false
可以使用:
Provider.of<ServicePieceViewModel>(context, listen: false).clear();
或许通过context:
context.read<ServicePieceViewModel>().clear();
在 build 方法中使用上面的代码,当 notifyListeners
被调用的时候,并不会使 widget 被重构。
- extension ReadContext on BuildContext {
- T read<T>() {
- return Provider.of<T>(this, listen: false);
- }
- }
- extension WatchContext on BuildContext {
- T watch<T>() {
- return Provider.of<T>(this);
- }
- }
context.read是ReadContext类中的函数,继承了BuildContext,其实就是封装了一下获取Provider的方法,用read方法获取的Provider,那么当value改变的时候,不会使页面重建,而且这个方法不能再StatelessWidget.build和State.build方法中调用,也就是说可在这些方法外面随意调用。
context.watch是WatchContext类中的函数,刚好和read相反,WatchContext中的watch方法和ReadContext中的read方法是相似的,但是watch方法会导致widget重构。
Flutter的状态管理机制涵盖了短时状态和共享状态,足够满足我们在日常开发中所遇到的数据更新去刷新UI的需求,个人感觉比命令式编程轻松了很多,后面我也会在此基础上看看是否能够封装一套自己的状态管理框架,欢迎同学们一起交流讨论。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。