您的位置:首页 > 娱乐 > 明星 > Flutter状态管理

Flutter状态管理

2025/3/14 15:41:22 来源:https://blog.csdn.net/nanxuan521/article/details/139501197  浏览:    关键词:Flutter状态管理

状态管理中的声明式编程思维

Flutter应用是 声明式 的,这也就意味着 Flutter 构建的用户界面就是应用的当前状态,在 Flutter 应用中,当状态变化时,会重新构建部分界面,而不是原生Android或iOS的命令式。当Flutter应用的状态发生改变时(例如:点击了一个按钮,触发了某个动画或者某个值的更新),改变状态就会导致UI界面重绘。去改变用户界面本身是没有必要的(例如 widget.setText ),因为这样的代码不会在UI界面上更新,只要改变了状态,那么用户界面将重新构建。

通过setState更新

class MyHomePage extends StatefulWidget {const MyHomePage({super.key, required this.title});final String title;@overrideState<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {int _counter = 0;void _incrementCounter() {setState(() {_counter++;});}@overrideWidget 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

如果我们想在应用中的多个部分之间共享一个非短时的状态,并且在用户会话期间保留这个状态,我们称之为应用状态(有时也称共享状态),为了管理应用状态,就需要研究使用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
  • ChangeNotifierProvider
  • Consumer
ChangeNotifier

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.@overrideWidget 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.@overrideWidget 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 被重构。

context.read和context.watch的区别

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的需求,个人感觉比命令式编程轻松了很多,后面我也会在此基础上看看是否能够封装一套自己的状态管理框架,欢迎同学们一起交流讨论。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com