Flutter-状态管理

1、什么是状态管理

大到整个app的状态,用户使用app是登陆状态,仍是游客状态;小到一个按钮的状态,按钮是点击选中状态仍是未点击状态等等,这些都是状态管理。编程

2、命令式编程和声明式编程状态管理的区别

  • iOS是如何管理状态的,通常都是获取这个控件而后设置你想要的状态
  • 当你的 Flutter 应用的状态发生改变时(例如,用户在设置界面中点击了一个开关选项)你改变了状态,这将会触发用户界面的重绘。去改变用户界面自己是没有必要的(例如 widget.setText )—你改变了状态,那么用户界面将从新构建。

3、状态管理中的声明式编程思惟

Flutter 应用是 声明式 的,这也就意味着 Flutter 构建的用户界面就是应用的当前状态。bash

一旦你的界面状态发生改变,就会触发界面的从新绘制,绘制出你想要的界面,而不是像iOS的OC语言那样去获取须要改变状态的控件,而后修改它架构

4、短时 (ephemeral) 和应用 (app) 状态的区别

Flutter中的状态管理又分为短时状态和应用状态。app

  • 短时状态,就是在单个页面须要保持的状态,好比页面数据加载到了第几页,关注按钮是已关注仍是未关注等,都是在单个页面须要保持的状态。widget树中其余部分不须要访问这种状态。不须要去序列化这种状态,这种状态也不会以复杂的方式改变。换句话说,不须要使用状态管理架构(例如 ScopedModel, Redux)去管理这种状态。你须要用的只是一个 StatefulWidget。

在下方你能够看到一个底部导航栏中当前被选中的项目是如何被被保存在 _MyHomepageState 类的 _index 变量中。在这个例子中,_index 是一个短时状态。less

class MyHomepage extends StatefulWidget {
  @override
  _MyHomepageState createState() => _MyHomepageState();
}

class _MyHomepageState extends State<MyHomepage> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      currentIndex: _index,
      onTap: (newIndex) {
        setState(() {
          _index = newIndex;
        });
      },
      // ... items ...
    );
  }
}
复制代码

在这里,使用 setState() 和一个变量就能达到管理状态的目的。你的 app 中的其余部分不须要访问 _index。这个变量只会在 MyHomepage widget 中改变。并且,若是用户关闭并重启这个 app,_index会被重置而不会继续保持原来的状态。ide

  • 应用状态,若是你想在你的应用中的多个部分之间共享一个非短时的状态,而且在用户会话期间保留这个状态,咱们称之为应用状态(有时也称共享状态)。 应用状态的一些例子:函数

    一、用户选项
    
      二、登陆信息
    
      三、一个社交应用中的通知
    
      四、一个电商应用中的购物车
    
      五、一个新闻应用中的文章已读/未读状态
    复制代码

5、共享状态管理

在 Flutter 中,通常是将存储状态的对象置于 widget 树中对应 widget 的上层,当它发生改变的时候,它对应的widget会从上层开始重构。由于这个机制,因此 widget 无需考虑生命周期的问题—它只须要针对 上层存储数据的对象 声明所需显示内容便可。当内容发生改变的时候,旧的 widget 就会消失,彻底被新的 widget 替代。 Flutter原生提供了两个方法来管理共享状态:测试

5.1 --InheritedWidget

class ADCounterWidget extends InheritedWidget {
  // 1. 共享的数据
  final int counter;

  // 2. 定义构造方法
  ADCounterWidget({this.counter, Widget child}): super(child: child);

  // 3. 找到当前Widget树中最近的InheritedWidget
  static ADCounterWidget of(BuildContext context) {
    // 沿着Element树, 去找到最近的ADCounterElement, 从Element中取出Widget对象
    return context.dependOnInheritedWidgetOfExactType();
  }

  // 4. 要不要回调State中的didChangeDependencies方法
  @override
  bool updateShouldNotify(ADCounterWidget oldWidget) {
    return oldWidget.counter != counter;
  }
}

复制代码
  • 上面定义了一个of方法,该方法经过context开始去查找父级的HYDataWidget
  • updateShouldNotify方法是对比新旧HYDataWidget,是否须要对更新相关依赖的Widget
class HYHomePage extends StatefulWidget {
  @override
  _HYHomePageState createState() => _HYHomePageState();
}

class _HYHomePageState extends State<HYHomePage> {
  int data = 100;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedWidget"),
      ),
      body: HYDataWidget(
        counter: data,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              HYShowData()
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          setState(() {
            data++;
          });
        },
      ),
    );
  }
}

复制代码

建立HYDataWidget,而且传入数据(这里点击按钮会修改数据,而且出发从新build)优化

5.2 --Provider

Provider库有三个主要用到的类:ui

  • ChangeNotifier:真正数据(状态)存放的地方
  • ChangeNotifierProvider:Widget树中提供数据(状态)的地方,会在其中建立对应的ChangeNotifier
  • Consumer:Widget树中须要使用数据(状态)的地方

第一步 在程序的最顶层建立本身的ChangeNotifier

  • 将ChangeNotifierProvider放到了顶层,这样方便在整个应用的任何地方可使用CounterProvider
  • 在ChangeNotifier中建立一个私有的_counter,而且提供了getter和setter
  • 在setter中咱们监听到_counter的改变,就调用notifyListeners方法,通知全部的Consumer进行更新
void main() {
  runApp(ChangeNotifierProvider(
    create: (context) => CounterProvider(),
    child: MyApp(),
  ));
}

class CounterProvider extends ChangeNotifier {
  int _counter = 100;
  intget counter {
    return _counter;
  }
  set counter(int value) {
    _counter = value;
    notifyListeners();
  }
}

复制代码

第二步 在首页中使用Consumer引入和修改状态

  • 在body中使用Consumer,Consumer须要传入一个builder回调函数,当数据发生变化时,就会通知依赖数据的Consumer从新调用builder方法来构建
  • 在floatingActionButton中使用Consumer,当点击按钮时,修改CounterNotifier中的counter数据
class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("列表测试"),
      ),
      body: Center(
        child: Consumer<CounterProvider>(
          builder: (ctx, counterPro, child) {
            return Text("当前计数:${counterPro.counter}", style: TextStyle(fontSize: 20, color: Colors.red),);
          }
        ),
      ),
      floatingActionButton: Consumer<CounterProvider>(
        builder: (ctx, counterPro, child) {
          return FloatingActionButton(
            child: child,
            onPressed: () {
              counterPro.counter += 1;
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

复制代码

Consumer的builder方法有三个参数:

  • context,每一个build方法都会有上下文,目的是知道当前树的位置
  • ChangeNotifier对应的实例,也是咱们在builder函数中主要使用的对象
  • child,目的是进行优化,若是builder下面有一颗庞大的子树,当模型发生改变的时候,咱们并不但愿从新build这颗子树,那么就能够将这颗子树放到Consumer的child中,在这里直接引入便可(注意我案例中的Icon所放的位置)

第四步 建立一个新的页面,在新的页面中修改数据

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二个页面"),
      ),
      floatingActionButton: Consumer<CounterProvider>(
        builder: (ctx, counterPro, child) {
          return FloatingActionButton(
            child: child,
            onPressed: () {
              counterPro.counter += 1;
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

复制代码
相关文章
相关标签/搜索