写点什么

Flutter 状态管理概述【Flutter 专题 7】

作者:坚果前端
  • 2021 年 11 月 27 日
  • 本文字数:12008 字

    阅读完需:约 39 分钟

Flutter 状态管理:概述

状态管理是 UI 框架必须实现的关键特性之一并且实现得很好。正是出于这个原因,许多开发人员已经开始构建专用的状态管理库;内置的解决方案对他们来说还不够,或者他们想根据自己的口味进行调整。


UI 框架已经加强了他们的游戏以平衡竞争环境。他们的内置状态管理解决方案现在可以与现有外部解决方案的性能相匹配。例如,React 引入了 Hooks 和 Context 来与 React-Redux 竞争。


Flutter 也发生了同样的情况:它提供了许多内置的方法来管理应用程序状态。在本文中,我们将介绍一些基本但功能强大的方法来管理 Flutter 应用程序中的状态。

setState在 Flutter 中使用

如果你来自 React,你会发现 Flutter 中这种管理状态的方法类似于使用useStateHook。


setState 只管理声明它的小部件中的状态——就像在 React 中一样,其中 useState 钩子只在创建它的组件中管理本地状态。这种类型的状态管理被称为 ephemeral 状态。在这里,这种状态是使用 StatefulWidget 和 setState()方法控制的。

使用小部件本身来管理状态

让我们setState通过创建一个简单的计数器应用程序来查看一些有关如何工作的示例。该应用程序将有一个计数器编号,我们可以通过单击按钮来增加和减少。


首先,通过运行以下命令来搭建 Flutter 项目:


flutter create myapp
复制代码


这将创建一个名为 的 Flutter 项目文件夹myapp。现在让我们在服务器上运行项目:


flutter run myapp
复制代码


在我们的项目文件夹中,我们应该看到一个main.dart文件。这是主要的 Flutter 应用程序文件。清除文件内容并添加以下代码:


import 'package:flutter/material.dart';void main() {  runApp(MyApp());}class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(        title: 'Flutter Demo',        theme: ThemeData(          primarySwatch: Colors.blue,          visualDensity: VisualDensity.adaptivePlatformDensity,        ),        home: Scaffold(            appBar: AppBar(              title: Text("State Mgmt Demo"),            ),            body: CounterPage(title: 'Flutter Demo')),        );  }}
复制代码


Flutter 中的一切都是一个小部件。MyApp是我们应用程序的入口/根小部件。在body道具中,请注意我们正在渲染一个CounterPage小部件。这是一个扩展StatefulWidget类的有状态小部件。


StatefulWidgets用于管理小部件中的本地状态。它们创建一个关联的State对象,并且它们还持有不可变的变量。


下面是一个例子:


class NotificationCounter extends StatefulWidget {    final String name;    NotificationCounter({this.name});    @override    _NotificationCounterState createState() => _NotificationCounterState();}
复制代码


name上面的变量是一个不可变的变量。StatefulWidget只保存不可变的变量和State对象。


让我们看看我们的CounterPage代码:


class CounterPage extends StatefulWidget {  CounterPage({Key key, this.title}) : super(key: key);  final String title;  @override  CounterPageState createState() => CounterPageState();}
复制代码


createState方法从中创建一个对象CounterPageState并返回它。该createState方法在构建小部件时被调用。


让我们看看代码CounterPageState


class CounterPageState extends State<CounterPage> {  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>[            Row(              mainAxisAlignment: MainAxisAlignment.center,              children: [                Text(                  'Counter:',                  style: Theme.of(context).textTheme.headline4,                ),                Text(                  '$_counter',                  style: Theme.of(context).textTheme.headline4,                ),              ],            ),            FlatButton(              color: Colors.orange,              child: Text('Increment Counter', style: TextStyle(color: Colors.white)),              onPressed: _incrementCounter,            )          ],        ),      )    );  }}
复制代码


CounterPageState有一个可变变量_counter,它存储计数器的编号并且可以在小部件的生命周期内更改。


build当必须构建小部件时调用该方法。它返回小部件的 UI,并且appBar->title设置将在页面的应用栏中显示的内容。该body设置窗口小部件的身体的 UI。


通常,此小部件将显示文本 Counter:_counter一行中的变量和下一行中的按钮。该按钮onPressed设置了一个事件,类似于onclickHTML 中的事件。


_incrementCounter函数setState在按下按钮时调用。此方法调用告诉 Flutter 小部件内部的状态已更改,必须重新绘制小部件。setState增加_counter变量的函数参数。


void _incrementCounter() {    setState(() {      _counter++;    });  }
复制代码


因此,每当我们单击 Increment Counter 按钮时,_counter都会增加并setState调用它,这会告诉 Flutter 重建小部件树。的build方法CounterPageState被调用,然后 widget 中的 widget 树被重建并在 UI 上重新渲染(注意,只有发生变化的部分才会被重新渲染)。


如果我们在模拟器中启动我们的应用程序,它应该是这样的:



按下按钮时,数字会增加:



现在让我们添加一个递减按钮。此按钮将减少计数器并将更新反映到屏幕上。我们如何做到这一点?


简单:我们将添加一个新FlatButton的文本Decrement CounteronPressed在其上设置一个事件。我们将创建一个方法_decrementCounter并将其设置为onPressed事件的处理程序。


_decrementCounter方法将_counter在调用时减少 1 并调用setState更新 UI:


class CounterPageState extends State<CounterPage> {  int _counter = 0;  void _incrementCounter() {    setState(() {      _counter++;    });  }  void _decrementCounter() {    setState(() {      _counter--;    });  }  @override  Widget build(BuildContext context) {    return Scaffold(        appBar: AppBar(          title: Text(widget.title),        ),        body: Center(          child: Column(            mainAxisAlignment: MainAxisAlignment.center,            children: <Widget>[              Row(                mainAxisAlignment: MainAxisAlignment.center,                children: [                  Text(                    'Counter:',                    style: Theme.of(context).textTheme.headline4,                  ),                  Text(                    '$_counter',                    style: Theme.of(context).textTheme.headline4,                  ),                ],              ),              FlatButton(                color: Colors.orange,                child: Text('Increment Counter',                    style: TextStyle(color: Colors.white)),                onPressed: _incrementCounter,              ),              FlatButton(                color: Colors.red,                child: Text('Decrement Counter',                    style: TextStyle(color: Colors.white)),                onPressed: _decrementCounter,              )            ],          ),        ));  }}
复制代码


我们给 Decrement Button 一个红色背景,将它放在 Increment Button 下方。该_decrementCounter方法设置为其onPressed事件。该_decrementCounter方法在_counter每次调用时递减,并调用setState来触发 UI 更新。


请观看下面的演示:



现在我们已经看到了如何使用小部件本身来管理状态,让我们看看另外两个选项:使用父小部件来管理状态,以及使用混合匹配方法。

使用父小部件管理状态

在这种方法中,小部件的父级保存状态变量并管理状态。父级通过将状态变量向下传递给子级小部件来告诉小部件何时更新。用于更改状态的方法也传递给子小部件,小部件可以调用它来更改状态并更新自身。


我们可以重写使用上面这种方法来重写counter例子。我们将有一个无状态小部件,其工作是呈现 UI。创建一个类Counter并按如下方式填充它:


class Counter extends StatelessWidget {  final counter;  final decrementCounter;  final incrementCounter;  Counter(      {Key key,      this.counter: 0,      @required this.decrementCounter,      @required this.incrementCounter})      : super(key: key);  @override  Widget build(BuildContext context) {    return Scaffold(        body: Column(      mainAxisAlignment: MainAxisAlignment.center,      children: <Widget>[        Row(          mainAxisAlignment: MainAxisAlignment.center,          children: [            Text(              'Counter:',              style: Theme.of(context).textTheme.headline4,            ),            Text(              '$counter',              style: Theme.of(context).textTheme.headline4,            ),          ],        ),        FlatButton(          color: Colors.orange,          child:              Text('Increment Counter', style: TextStyle(color: Colors.white)),          onPressed: () {            incrementCounter();          },        ),        FlatButton(          color: Colors.red,          child:              Text('Decrement Counter', style: TextStyle(color: Colors.white)),          onPressed: () {            decrementCounter();          },        )      ],    ));  }}
复制代码


同样,这是一个无状态小部件,因此它没有状态;它只是呈现传递给它的内容。


请注意,我们将渲染计数器的工作移到了这个小部件上。计数器通过 传递给它,递减和递增函数分别通过和传递给它。所有这些都是从父小部件传递的。this.counter``this.decrementCounter``this.incrementCounter``CounterPageState


现在,CounterPageState小部件将如下所示:


class CounterPageState extends State<CounterPage> {  // ...  @override  Widget build(BuildContext context) {    return Scaffold(        // ...        body: Center(            child: Counter(                counter: _counter,                decrementCounter: _decrementCounter,                incrementCounter: _incrementCounter            )        )    );  }}
复制代码


Counter现在由CounterPageState渲染,它以前呈现的 UI 现在正由新的方式处理Counter部件。


在这里,_counter状态被传递给 prop 中的Counter小部件counter。该Counter控件将通过访问计数器counter在它的身上。


此外,_decrementCounter_incrementCounter方法被传递给Counter小部件。这些是从所谓的Counter小部件更新状态_counterCounterPageState小工具,这将导致CounterPageState重建和重新渲染Counter,以显示新更改的状态。

混搭状态管理

在这种方法中,父小部件管理一些状态,而子小部件管理状态的另一方面。为了演示这一点,我们将让我们的Counter小部件保持一个状态,使其成为StatefulWidget.


我们将跟踪 Increment Button Decrement Button 被点击的次数,并在两种状态下保持数字。


现在,让我们使Counter小部件成为有状态的小部件:


class Counter extends StatefulWidget {  final counter;  final decrementCounter;  final incrementCounter;  Counter(      {Key key,      this.counter: 0,      @required this.decrementCounter,      @required this.incrementCounter})      : super(key: key);  @override  CounterState createState() => CounterState();}
复制代码


我们可以看到该createState方法返回一个CounterState对象。我们来看一下这个CounterState类:


class CounterState extends State<Counter> {  var incrButtonClicked = 0;  var decreButtonClicked = 0;  @override  Widget build(BuildContext context) {    return Scaffold(        body: Column(      mainAxisAlignment: MainAxisAlignment.center,      children: <Widget>[        Row(          mainAxisAlignment: MainAxisAlignment.center,          children: [            Text(              'Counter:',              style: Theme.of(context).textTheme.headline4,            ),            Text(              widget.counter.toString(),              style: Theme.of(context).textTheme.headline4,            ),          ],        ),        Column(          mainAxisAlignment: MainAxisAlignment.center,          children: [            Text("'Increment Button' clicked $incrButtonClicked times"),            Text("'Decrement Button' clicked $decreButtonClicked times")          ],        ),        FlatButton(          color: Colors.orange,          child:              Text('Increment Counter', style: TextStyle(color: Colors.white)),          onPressed: () {            widget.incrementCounter();            setState(() {              incrButtonClicked++;            });          },        ),        FlatButton(          color: Colors.red,          child:              Text('Decrement Counter', style: TextStyle(color: Colors.white)),          onPressed: () {            widget.decrementCounter();            setState(() {              decreButtonClicked++;            });          },        )      ],    ));  }}
复制代码


请注意,Counter小部件先前的 UI 在这里。我们添加了incrButtonClickeddecreButtonClicked状态来保存按钮被按下的次数。我们还添加了一个ColumnText部件,以在以主轴为中心的列中显示小部件。这些Text小部件将显示每个按钮被点击的次数。


现在,在onPressed每个按钮的事件处理程序中,我们通过对象调用incrementCounterordecrementCounter方法widget。我们使用widget对象来访问有状态小部件中的父变量。然后,我们调用了setState增加或减少状态变量incrButtonClicked和 的方法decreButtonClicked


所以我们在这里可以看到我们有一个混合匹配的状态管理方法:父小部件处理counter状态,而子小部件处理点击状态。


请参阅下面的演示:


InheritedModelInheritedWidget

该技术使用父小部件和子小部件之间的通信方法。数据设置在父小部件上,子小部件可以从父小部件访问数据,这样小部件状态就可以无缝传递。


这种状态管理类似于Service在 Angular 中使用 s 类,也与 React 的 Context API 有相似之处。

InheritedWidget

InheritedWidget 是 Flutter 中的一个基类,用于在小部件树中传播信息。


这是它的工作原理:一个InheritedWidget包含一个小部件树。现在,树中的小部件可以引用 up 来InheritedWidget访问其中的公共变量,从而在树中传递数据。由 持有的数据InheritedWidget通过其构造函数传递给它。


InheritedWidget当我们必须通过一长串小部件传递数据只是为了在小部件中使用它时,这是非常有用的。例如,我们有这样的小部件树:


 MyApp    |    vCounterPage    |    v虚拟容器1    |    v虚拟容器2    |    v  Counter
复制代码


CounterPage有一个counter与国家incrementCounterincrementCounter方法。我们想counter在 UI 中显示Counter小部件。为此,我们必须将counter状态和两个方法传递给Counter小部件。


首先,从小CounterPage部件中,我们将呈现DummyContainer小部件,将counter和 两个方法作为参数传递给其构造函数。接下来,DummyContainer1将呈现DummyContainer2并将counter状态和两个方法DummyContainer2作为参数传递给构造函数。最后,DummyContainer2将呈现Counter并将计数器和方法传递给它。


有了InheritedWidget,我们就可以随便驾驶了。使用InheritedWidget,我们将在其中设置counter和两个方法。在InheritedWidget将呈现DummyContainer1CounterPage将呈现InheritedWidgetCounterPagecounter和 方法设置为InheritedWidget.


  MyApp    |    vCounterPage    |    v我的继承小部件    |    v虚拟容器1    |    v虚拟容器2    |    v  Counter
复制代码


这就是包含InheritedWidget.


让我们编码吧!我们将从CounterPage


class CounterPage extends StatefulWidget {  CounterPage({Key key, this.title}) : super(key: key);  final String title;  @override  CounterPageState createState() => CounterPageState();  static CounterPageState of(BuildContext context) {    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>().data;  }}
复制代码


我们添加了一个static方法of。此方法使用context来返回InheritedWidget使用方法调用。此方法返回精确类型的小部件树中最近的;在这种情况下,我们需要一个类型。dependOnInheritedWidgetOfExactType<MyInheritedWidget>()``Inherited``W``idget``MyInheritedWidget


现在,在我们的 CounterPageState中,我们将渲染MyInheritedWidget,在其中,我们将渲染DummyContainer1小部件。


class CounterPageState extends State&amp;lt;CounterPage&amp;gt; {    // ...  @override  Widget build(BuildContext context) {    return Scaffold(        appBar: AppBar(          title: Text(widget.title),        ),        body: Center(            child: MyInheritedWidget(                child: DummyContainer1(),                data: this            )        )    );  }}
复制代码


data参数保存this,这意味着的公共属性CounterPageState是可访问MyInheritedWidget经由data支柱。我们这样做是因为我们想要的_counter,而这两种方法_incrementCounter_decrementCounter,由一个被引用InheritedWidget。有了这个,我们可以使用InheritedWidget来访问counter小部件树中任何位置的状态和方法。


让我们创建的MyInheritedWidgetDummyContainer1DummyContainer2小部件。


class MyInheritedWidget extends InheritedWidget {  final CounterPageState data;  MyInheritedWidget({    Key key,    @required Widget child,    @required this.data,  }) : super(key: key, child: child);  @override  bool updateShouldNotify(InheritedWidget oldWidget) {    return true;  }}
复制代码


我们有一个data属性和一个对象CounterPageState。这是我们在CounterPageState. 该updateShouldNotify方法确定是否InheritedWidget将重建其下方的小部件树。如果返回 true,将重建 widget 树;如果返回 false,则状态更改时将不会重新构建小部件树。


class DummyContainer1 extends StatelessWidget {  const DummyContainer1({Key key}) : super(key: key);  @override  Widget build(BuildContext context) {    return DummyContainer2();  }}
复制代码


DummyContainer1小部件呈现DummyContainer2小部件。


class DummyContainer2 extends StatelessWidget {  const DummyContainer2({Key key}) : super(key: key);  @override  Widget build(BuildContext context) {    return Counter();  }}
复制代码


DummyContainer2窗口小部件,反过来,呈现Counter小部件。


现在,让我们看看我们的Counter小部件:


class Counter extends StatefulWidget {  @override  CounterState createState() =&amp;gt; CounterState();}
复制代码


它只实现了以下createState方法:


class CounterState extends State&amp;lt;Counter&amp;gt; {  var incrButtonClicked = 0;  var decreButtonClicked = 0;  var counter;  CounterPageState data;  @override  void didChangeDependencies() {    super.didChangeDependencies();    data = CounterPage.of(context);    counter = data._counter;  }  @override  Widget build(BuildContext context) {    return Scaffold(        body: Column(      mainAxisAlignment: MainAxisAlignment.center,      children: &amp;lt;Widget&amp;gt;[        Row(          mainAxisAlignment: MainAxisAlignment.center,          children: [            Text(              'Counter:',              style: Theme.of(context).textTheme.headline4,            ),            Text(              counter.toString(),              style: Theme.of(context).textTheme.headline4,            ),          ],        ),        Column(          mainAxisAlignment: MainAxisAlignment.center,          children: [            Text("'Increment Button' clicked $incrButtonClicked times"),            Text("'Decrement Button' clicked $decreButtonClicked times")          ],        ),        FlatButton(          color: Colors.orange,          child:              Text('Increment Counter', style: TextStyle(color: Colors.white)),          onPressed: () {            data._incrementCounter();            setState(() {              incrButtonClicked++;            });          },        ),        FlatButton(          color: Colors.red,          child:              Text('Decrement Counter', style: TextStyle(color: Colors.white)),          onPressed: () {            data._decrementCounter();            setState(() {              decreButtonClicked++;            });          },        )      ],    ));  }}
复制代码


请注意,我们从构造函数中删除了 props 。我们用得到CounterPageState data = CounterPage.of(context);变量。从MyInheritedWidget那里,我们可以访问data. 注意我们是如何访问,_counter


_incrementCounter,_decrementCounter数据变量的属性。


这些是存储在MyInheritedWidgetfrom 中的属性CounterPageState,因此一旦我们引用了MyInheritedWidget,我们就可以从小部件树的任何位置获取这些属性。这就是通过InheritedWidget小部件树中的任何位置传递和访问数据的方式。


这是演示:


InheritedModel

InheritedModel工作方式与 相同InheritedWidget:它管理状态并在其小部件树中传播状态。但InheritedModel略有不同,它允许更好地控制更改检测触发器和更新通知,可以设置为在特定数据更改时做出响应。


InheritedModel易于实施。让我们重写上面的Counter示例以使用InheritedModel. 令人惊讶的是,代码几乎相同。


首先,更改MyInheritedWidgetMyInheritedModel


class MyInheritedModel extends InheritedModel&amp;lt;String&amp;gt; {  final CounterPageState data;  MyInheritedModel({    Key key,    @required Widget child,    @required this.data,  }) : super(key: key, child: child);  @override  bool updateShouldNotify(MyInheritedModel old) {    return true;  }  @override  bool updateShouldNotifyDependent(MyInheritedModel old, Set&amp;lt;String&amp;gt; aspects) {    return true;  }  static MyInheritedModel of(BuildContext context, String aspect) {    return InheritedModel.inheritFrom&amp;lt;MyInheritedModel&amp;gt;(context,        aspect: aspect);  }}
复制代码


还是一样; 这里的关键是static方法of。它返回自身的一个实例,因此我们可以使用它来访问其公共属性。是我们想要公开的属性——它是将通过它的小部件树向下传播的状态。请注意,它的值由构造函数中的参数设置。final CounterPageState data;``InheritedModel``this.data


接下来,我们相应地更新我们的CounterState


class CounterState extends State&amp;lt;Counter&amp;gt; {  var incrButtonClicked = 0;  var decreButtonClicked = 0;  var counter;  MyInheritedModel inheritedModel;  @override  Widget build(BuildContext context) {    inheritedModel = MyInheritedModel.of(context, "");    counter = inheritedModel.data._counter;    return Scaffold(        body: Column(      mainAxisAlignment: MainAxisAlignment.center,      children: &amp;lt;Widget&amp;gt;[        Row(          mainAxisAlignment: MainAxisAlignment.center,          children: [            Text(              'Counter:',              style: Theme.of(context).textTheme.headline4,            ),            Text(              counter.toString(),              style: Theme.of(context).textTheme.headline4,            ),          ],        ),        Column(          mainAxisAlignment: MainAxisAlignment.center,          children: [            Text("'Increment Button' clicked $incrButtonClicked times"),            Text("'Decrement Button' clicked $decreButtonClicked times")          ],        ),        FlatButton(          color: Colors.orange,          child:              Text('Increment Counter', style: TextStyle(color: Colors.white)),          onPressed: () {            inheritedModel.data._incrementCounter();            setState(() {              incrButtonClicked++;            });          },        ),        FlatButton(          color: Colors.red,          child:              Text('Decrement Counter', style: TextStyle(color: Colors.white)),          onPressed: () {            inheritedModel.data._decrementCounter();            setState(() {              decreButtonClicked++;            });          },        )      ],    ));  }}
复制代码


在这里,我们有MyInheritedModel inheritedModel;,我们称之为inheritedModel = MyInheritedModel.of(context, "");``build()的方法得到的实例。MyInheritedModel


现在,从inheritedModel我们可以访问属性来获取,以及在属性窗口小部件。final CounterPageState data;``counter``_incrementCounter``_decrementCounter``CounterPageState


从计数器状态接收,然后在显示之前转换为字符串。counter = inheritedModel.data._counter;


_incrementCounter_decrementCounter方法是通过所谓的inheritedModel.data._incrementCounter();和增加inheritedModel.data._decrementCounter();,分别减少按钮点击次数。``


这将是Counter代码:


class Counter extends StatefulWidget {  @override  CounterState createState() =&amp;gt; CounterState();}
复制代码


这里没有什么需要注意的;只需实现该createState方法并返回CounterState小部件的实例。


现在,这是我们的CounterPageState


class CounterPageState extends State&amp;lt;CounterPage&amp;gt; {  int _counter = 0;  void _incrementCounter() {    setState(() {      _counter++;    });  }  void _decrementCounter() {    setState(() {      _counter--;    });  }  @override  Widget build(BuildContext context) {    return Scaffold(        appBar: AppBar(          title: Text(widget.title),        ),        body: Center(            child: MyInheritedModel(                child: DummyContainer1(),                data: this            )        )    );  }}
复制代码


CounterPageState坐骑MyInheritedModel。的实例CounterPageState通过data参数传递给它的构造函数。这就是我们能够访问CounterPageStatefrom 的公共属性的方式MyInheritedModel


这是演示:


结论

我们已经使用 Flutter 的内置机制介绍了状态管理的基础知识。我们首先分析了什么是状态管理,以及它对任何 UI 框架来说是多么重要。接下来,我们查看setState与 React 的useStateHook 的比较。我们通过示例说明了如何setState工作以及如何使用它来构建现实世界的应用程序。


然后我们讨论InheritedWidget并看到了我们如何声明一个状态并将它向下传播到小部件树。树下的小部件可以订阅状态以在状态更改时获取更新。


与 类似InheritedWidget,我们查看了InheritedModel,它沿小部件树向下传播状态。这里的区别在于我们可以选择我们希望在更改时收到通知的状态。

进一步阅读


好的,状态管理的相关基础我也介绍完了,我们继续加油


发布于: 14 小时前阅读数: 5
用户头像

坚果前端

关注

此间若无火炬,我便是唯一的光 2020.10.25 加入

公众号:“坚果前端”,51CTO博客首席体验官,专注于大前端技术的分享,包括Flutter,小程序,安卓,VUE,JavaScript。

评论

发布
暂无评论
Flutter 状态管理概述【Flutter 专题 7】