FlutterAndroid开发经验谈Flutter圈子

Flutter State Management状态管理全面分析

2020-05-21  本文已影响0人  i校长

前言


2019 Google I/O 大会,google就推出Provider,成为官方推荐的状态管理方式之一,Flutter 状态管理一直是个很热门的话题,而且状态管理的库也是超级多,这确实是我们每一个做Flutter开发难以避免的一道坎,既然这么重要,我们如何去理解它,如何使用它,如何做到更好呢?接下来让我告诉你答案

主要内容


一张图告诉你,我要讲的主要内容。下面将围绕这八个方面来讲。七个理论,一个实践。

状态管理是什么


我们知道最基本的程序是什么:

class ThemeBloc {
  final _themeStreamController = StreamController<AppTheme>();

  get changeTheTheme => _themeStreamController.sink.add;

  get darkThemeIsEnabled => _themeStreamController.stream;

  dispose() {
    _themeStreamController.close();
  }
}

final bloc = ThemeBloc();

class AppTheme {
  ThemeData themeData;

  AppTheme(this.themeData);
}
/// 绑定到UI
StreamBuilder<AppTheme>(
        initialData: AppTheme.LIGHT_THEME,
        stream: bloc.darkThemeIsEnabled,
        builder: (context, AsyncSnapshot<AppTheme> snapshot) {
          return MaterialApp(
            title: 'Jetpack',
            theme: snapshot.data.themeData,
            home: PageHome(),
            routes: <String, WidgetBuilder>{
              "/pageChatGroup": (context) => PageChatGroup(),
              "/LaoMeng": (context) => LaoMeng(),
            },
          );
        })

这样一整套代码的逻辑就是我们所说的Flutter状态管理,这样解释大家理解了吗?再细说,算法就是我们如何管理,数据结构就是数据状态,状态管理的本质还是如何通过合理的算法管理数据,如何取,如何接收等,最终展示在UI上,通过UI的变更来体现状态的管理逻辑。

为什么需要


这里就需要明白一个事情,Flutter的很多优秀的设计都来源于React,对于react来说,同级组件之间的通信尤为麻烦,或者是非常麻烦了,所以我们把所有需要多个组件使用的state拿出来,整合到顶部容器,进行分发。状态管理可以实现组件通信、跨组件数据储存。推荐阅读对 React 状态管理的理解及方案对比,那么对于Flutter来说呢?你知道Android、Ios等原生于Flutter最本质的区别吗?来看一段代码:

//android
TextView tv = TextView()
tv.setText("text")
///flutter
setState{
    text = "text"
}

从上面代码我们看出,Android的状态变更是通过具体的组件直接赋值,如果页面全部变更,你是不是需要每一个都设置一遍呢?,而Flutter的变更就简单粗暴,setState搞定,它背后的逻辑是重新build整个页面,发现有变更,再将新的数据赋值,其实Android、Ios与flutter的本质的区别就是数据与视图完全分离,当然Android也出现了UI绑定框架,似乎跟React、Flutter越来越像,所以这也在另一方面凸显出了,Flutter设计的先进性,没有什么创新,但更符合未来感,回过头来,仔细想一想,这样设计有什么弊端?

对了你猜对了:页面如何刷新才是Flutter的关键,做Android的同学肯定也面临着一个问题,页面的重绘导致的丢帧问题,为了更好,我们很多时候都选择了局部刷新来优化对吧,Android、Ios已经很明确的告诉UI要刷新什么更新什么,而对于Flutter来说,这一点很不清晰,虽然Flutter也做了类似虚拟Dom优化重绘逻辑,但这些远远不够的,如何合理的更新UI才是最主要的,这个时候一大堆的状态管理就出来了,当然状态管理也不是仅仅为了解决更新问题。

我再抛出一个问题,如果我有一个widget A,我想在另外一个widget B中改变widget A的一个状态,或者从网络、数据库取到数据,然后刷新它,怎么做?我们来模拟一下,来看代码

糟糕的状态管理代码

class WidgetTest extends StatefulWidget {
  @override
  _WidgetTestState createState() => _WidgetTestState();
}

class _WidgetTestState extends State<WidgetTest> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: <Widget>[
          WidgetA(),
          WidgetB()
        ],
      ),
    );
  }
}

_WidgetAState _widgetAState;
class WidgetA extends StatefulWidget {
  @override
  _WidgetAState createState() {
    _widgetAState = _WidgetAState();
    return _widgetAState;
  }
}
class _WidgetAState extends State<WidgetA> {
  var title = "";
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(title),
    );
  }
}

class WidgetB extends StatefulWidget {
  @override
  _WidgetBState createState() => _WidgetBState();
}

class _WidgetBState extends State<WidgetB> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: RaisedButton(
        onPressed: () {
          _widgetAState.setState(() {
            _widgetAState.title = "WidgetB";
          });
        },
      ),
    );
  }
}

WidgetTest页面有两个widget,分别是WidgetA、WidgetB,WidgetB通过RaisedButton的onPressed来改变WidgetA的Text,怎么做到的呢,直接用WidgetA的_WidgetAState对象提供的setState函数来变更,没什么问题对吧,而且功能实现了,但你仔细思考一下,这有什么问题呢?

如何变好呢
当然回到为什么,因为需要选择一种合适的状态管理方式。

状态管理的目标
其实我们做状态管理,不仅仅是因为它的特点,而为了更好架构,不是吗?

这些不紧紧是状态管理的目的,也是我们做一款优秀应用的基础架构哦。

基本分类


底层逻辑

State

State 是我们常用而且使用最频繁的一个状态管理类,我们经常会用StatefulWidget来管理状态的变化,StreamBuilder继承自StatefulWidget也不用多说它的原理。

为什么会有它呢?请看Flutter渲染步骤。
Widget=>Element=>RenderObject
Widget本身是不变的,那么就需要一个角色来控制它,State就出现了,State的任何更改都会强制整个Widget重新构建

注意
是整个Widget重新构建(而且子Widget也会跟着销毁重建),这个点也是为什么不推荐你大量使用StatefulWidget的原因。如果页面足够复杂,就会导致严重的性能损耗。如何优化呢?建议使用StreamBuilder,它原理上也是State,但它做到了子Widget的局部刷新,不会导致整个页面的重建,是不是就好很多了呢?

State缺点

InheritedWidget

InheritedWidget是一个无私的Widget,它可以把自己的状态数据,无私的交给所有的子Widget,所有的子Widget可以无条件的继承它的状态。就这么一个东西。

注意
它的数据是只读的,虽然很无私,但子widget不能修改,呵呵。
说到它就不得不提InheritedModel,它是InheritedWidget的子类,InheritedModel可以做到部分数据改变的时候才会重建

推荐阅读
inheritedmodel-vs-inheritedwidget
https://juju.one/inheritedwidget-inheritedmodel/

InheritedWidget 缺点

Notification

它是Flutter中跨层数据共享的一种机制,注意,它不是widget,它就是它,不一样的傻叉,它提供了dispatch方法,来让我们沿着context对应的Element节点向上逐层发送通知

推荐阅读
flutter-notifications-bubble-up-and-values-go-down
notification

Notification缺点

Stream

它其实是纯Dart的实现,跟Flutter没什么关系,扯上关系的就是用StreamBuilder来构建一个Stream通道的Widget,像知名的rxdart、BloC、flutter_redux全都用到了Stream的api。所以学习它才是我们掌握状态管理的一个关键

推荐阅读
我自己写的StreamBuilder源码分析
大神写的Stream全面分析

Stream 缺点

缺点恰恰是它的优点,保证了足够灵活,你更可基于它做一个好的设计,满足当下业务的设计。

小结

通过对State、InheritedWidget、Notification、Stream的学习,你是不是觉得,Flutter的状态管理也就这些了呢?不一定哈,我也在不断的学习,如果碰到新的技术,继续分享给你们哦。难道这就完了吗?没有,其实我们只是学了第一步,是什么,如何用,还没有讨论怎么用好呢?需要什么标准吗,当然有,下面通过我的项目实战经验来提出一个基本原则,超过这个原则你就是在破坏平衡,请往下看。

状态管理的使用原则

局部管理优于全局

这个原则来源于,Flutter的性能优化,局部刷新肯定比全局刷新要好很多,那么我们在管理状态的同时,也要考虑该状态到底是局部还是全局,从而编写正确的逻辑。

保持数据安全性

用“_”私有化状态,因为当开发人员众多,当别人看到你的变量的时候,第一反应可能不是找你提供的方法,而是直接对变量操作,那就有可能出现想不到的后果,如果他只能调用你提供的方法,那他就要遵循你方法的逻辑,避免数据被处理错误。

考虑页面重新build带来的影响

很多时候页面的重建都会调用build函数,也就是说,在一个生命周期内,build函数是多次被调用的,所以你就要考虑数据的初始化或者刷新怎么样才能合理。

使用成熟状态管理库弊端

选型原则

所有的框架都有侵入性,你同意吗?不同意请左转,前面有个坑,你可以跳过去。目前侵入性比较高的代表ScopedModel,为啥?因为它是用extend实现的,需要继承实现的基本不是什么好实现,你同意吗?同上。
扩展性就不用说了,如果你选择的框架只能使用它提供的几个入口,那么请你放弃使用它。高性能也是很重要的,这个需要明白它的原理,看它到底如何做的管理。安全性也很重要,看他数据管理通道是否安全稳定。驾驭性,你说你都不理解你就敢用,出了问题找谁?如果驾驭不了也不要用。易用性大家应该都明白,如果用它一个框架需要N多配置,N多实现,放弃吧,不合适。简单才是硬道理。

范围性
这个特点是flutter中比较明显的,框架选型一定要考虑框架的适用范围,到底是适合做局部管理,还是适合全局管理,要做一个实际的考量。

推荐用法

如果是初期,建议多使用Stream、State、Notification来自行处理,顺便学习源码,多理解,多实践。有架构能力的就可以着手封装了,提供更简单的使用方式
如果是后期,当然也是在前面的基础之上,再去考虑使用Provider、redux等复杂的框架,原则上要吃透源码,否则不建议使用。

注意

你以为使用框架就能万事大吉了?性能优化是一个不变的话题,包括Provider在内的,如果你使用不当,照样出现页面的性能损耗严重,所以你又回到了为啥会这样,请你学习上面的底层逻辑,谢谢🙏

总结

通过这期分享,你是不是对Flutter的状态管理有了一个重新的认识呢?如果对你有帮住,请点一下下面的赞哦。谢谢🙏。

上一篇下一篇

猜你喜欢

热点阅读