关于flutter:Flutter如何状态管理

41次阅读

共计 11676 个字符,预计需要花费 30 分钟才能阅读完成。

目录介绍

  • 01. 什么是状态治理
  • 02. 状态治理计划分类
  • 03. 状态治理应用场景
  • 04.Widget 治理本人的状态
  • 05.Widget 治理子 Widget 状态
  • 06. 简略混合治理状态
  • 07. 全局状态如何治理
  • 08.Provider 应用办法
  • 09. 订阅监听批改状态

举荐

  • fluter Utils 工具类库:https://github.com/yangchong2…
  • flutter 混合我的项目代码案例:https://github.com/yangchong2…

01. 什么是状态治理

  • 响应式的编程框架中都会有一个永恒的主题——“状态 (State) 治理”

    • 在 Flutter 中,想一个问题,StatefulWidget的状态应该被谁治理?
    • Widget 自身?父 Widget?都会?还是另一个对象?答案是取决于理论状况!
  • 以下是治理状态的最常见的办法:

    • Widget 治理本人的状态。
    • Widget 治理子 Widget 状态。
    • 混合治理(父 Widget 和子 Widget 都治理状态)。
    • 不同模块的状态治理。
  • 如何决定应用哪种治理办法?上面给出的一些准则能够帮忙你做决定:

    • 如果状态是用户数据,如复选框的选中状态、滑块的地位,则该状态最好由父 Widget 治理。
    • 如果状态是无关界面外观成果的,例如色彩、动画,那么状态最好由 Widget 自身来治理。
    • 如果某一个状态是不同 Widget 共享的则最好由它们独特的父 Widget 治理。
    • 如果是多个模块须要专用一个状态,那么该怎么解决呢,那能够用 Provider。
    • 如果批改了某一个属性,须要刷新多个中央数据。比方批改用户城市 id 数据,那么则刷新首页 n 处的接口数据,这个时候能够用订阅监听批改状态

02. 状态治理计划分类

  • setState 状态治理

    • 长处:

      • 简略场景下特地实用,逻辑简略,易懂易实现
      • 所见即所得,效率比拟高
    • 毛病

      • 逻辑与视图耦合重大,简单逻辑下可维护性很差
      • 数据传输基于依赖传递,层级较深状况下不易保护,可读性差
  • InheritedWidget 状态治理

    • 长处

      • 不便数据传输,能够基于 InheritedWidget 达到逻辑和视图解耦的成果
      • flutter 内嵌类,根底且稳固,无代码侵入
    • 毛病

      • 属于比拟根底的类,敌对性不如封装的第三方库
      • 对于性能须要额定留神,刷新范畴如果过大会影响性能
  • Provider 状态治理

    • 长处

      • 功能完善,涵盖了 ScopedModel 和 InheritedWidget 的所有性能
      • 数据逻辑完满融入了 widget 树中,代码构造清晰,能够治理部分状态和全局状态
      • 解决了多 model 和资源回收的问题
      • 对不同场景下应用的 provider 做了优化和辨别
      • 反对异步状态治理和 provider 依赖注入
    • 毛病

      • 使用不当可能会造成性能问题(大 context 引起的 rebuild)
      • 部分状态之前的数据同步不反对
  • 订阅监听批改状态

    • 有两种:一种是 bus 事件告诉(是一种订阅 + 察看),另一个是接口注册回调。
    • 接口回调:因为应用了回调函数原理,因而数据传递实时性十分高,相当于间接调用,个别用在功能模块上。
    • bus 事件:组件之间的交互,很大水平上升高了它们之间的耦合,使得代码更加简洁,耦合性更低,晋升咱们的代码品质。

03. 状态治理应用场景

  • setState 状态治理

    • 适宜 Widget 治理本人的状态,这种很常见,调用 setState 刷新本人 widget 扭转状态。
    • 适宜 Widget 治理子 Widget 状态,这种也比拟常见。不过这种关联性比拟强。

04.Widget 治理本人的状态

  • _TapboxAState 类:

    • 治理 TapboxA 的状态。
    • 定义_active:确定盒子的以后色彩的布尔值。
    • 定义 _handleTap() 函数,该函数在点击该盒子时更新 _active,并调用setState() 更新 UI。
    • 实现 widget 的所有交互式行为。
  • 代码如下

    // TapboxA 治理本身状态.
    
    //------------------------- TapboxA ----------------------------------
    
    class TapboxA extends StatefulWidget {TapboxA({Key key}) : super(key: key);
    
      @override
      _TapboxAState createState() => new _TapboxAState();
    }
    
    class _TapboxAState extends State<TapboxA> {
      bool _active = false;
    
      void _handleTap() {setState(() {_active = !_active;});
      }
    
      Widget build(BuildContext context) {
        return new GestureDetector(
          onTap: _handleTap,
          child: new Container(
            child: new Center(
              child: new Text(
                _active ? 'Active' : 'Inactive',
                style: new TextStyle(fontSize: 32.0, color: Colors.white),
              ),
            ),
            width: 200.0,
            height: 200.0,
            decoration: new BoxDecoration(color: _active ? Colors.lightGreen[700] : Colors.grey[600],
            ),
          ),
        );
      }
    }

05.Widget 治理子 Widget 状态

  • 先看一下上面这些是什么

    typedef ValueChanged<T> = void Function(T value);
  • 对于父 Widget 来说,治理状态并通知其子 Widget 何时更新通常是比拟好的形式。

    • 例如,IconButton是一个图标按钮,但它是一个无状态的 Widget,因为咱们认为父 Widget 须要晓得该按钮是否被点击来采取相应的解决。
    • 在以下示例中,TapboxB 通过回调将其状态导出到其父组件,状态由父组件治理,因而它的父组件为StatefulWidget
  • ParentWidgetState 类:

    • 为 TapboxB 治理 _active 状态。
    • 实现_handleTapboxChanged(),当盒子被点击时调用的办法。
    • 当状态扭转时,调用 setState() 更新 UI。
  • TapboxB 类:

    • 继承 StatelessWidget 类,因为所有状态都由其父组件解决。
    • 当检测到点击时,它会告诉父组件。

      // ParentWidget 为 TapboxB 治理状态.
      
      class ParentWidget extends StatefulWidget {
      @override
      _ParentWidgetState createState() => new _ParentWidgetState();
      }
      
      class _ParentWidgetState extends State<ParentWidget> {
      bool _active = false;
      
      void _handleTapboxChanged(bool newValue) {setState(() {_active = newValue;});
      }
      
      @override
      Widget build(BuildContext context) {
      return new Scaffold(
        appBar: new AppBar(title: new Text("Widget 治理子 Widget 状态"),
        ),
        body: new ListView(
          children: [new Text("Widget 治理子 Widget 状态"),
            new TapboxB(
              active: _active,
              onChanged: _handleTapboxChanged,
            ),
          ],
        ),
      );
      }
      }
      
      //------------------------- TapboxB ----------------------------------
      
      class TapboxB extends StatefulWidget{
      
      final bool active;
      final ValueChanged<bool> onChanged;
      
      TapboxB({Key key , this.active : false ,@required this.onChanged});
      
      @override
      State<StatefulWidget> createState() {return new TabboxBState();
      }
      
      }
      
      class TabboxBState extends State<TapboxB>{void _handleTap() {widget.onChanged(!widget.active);
      }
      
      @override
      Widget build(BuildContext context) {
      return new GestureDetector(
        onTap: _handleTap,
        child: new Container(
          child: new Center(
            child: new Text(widget.active ? 'Active' : 'Inactive',),
          ),
          width: 100,
          height: 100,
          decoration: new BoxDecoration(color: widget.active ? Colors.lightGreen[700] : Colors.grey[850],
          ),
        ),
      );
      }
      }

06. 简略混合治理状态

  • 对于一些组件来说,混合治理的形式会十分有用。

    • 在这种状况下,组件本身治理一些外部状态,而父组件治理一些其余内部状态。
  • 在上面 TapboxC 示例中

    • 手指按下时,盒子的四周会呈现一个深绿色的边框,抬起时,边框隐没。点击实现后,盒子的色彩扭转。
    • TapboxC 将其 _active 状态导出到其父组件中,但在外部治理其 _highlight 状态。
    • 这个例子有两个状态对象 _ParentWidgetState_TapboxCState
  • _ParentWidgetStateC 类:

    • 治理_active 状态。
    • 实现 _handleTapboxChanged(),当盒子被点击时调用。
    • 当点击盒子并且 _active 状态扭转时调用 setState() 更新 UI。
  • _TapboxCState 对象:

    • 治理_highlight 状态。
    • GestureDetector监听所有 tap 事件。当用户点下时,它增加高亮(深绿色边框);当用户开释时,会移除高亮。
    • 当按下、抬起、或者勾销点击时更新 _highlight 状态,调用 setState() 更新 UI。
    • 当点击时,将状态的扭转传递给父组件。

      //---------------------------- ParentWidget ----------------------------
      
      class ParentWidgetC extends StatefulWidget {
      @override
      _ParentWidgetCState createState() => new _ParentWidgetCState();
      }
      
      class _ParentWidgetCState extends State<ParentWidgetC> {
      bool _active = false;
      
      void _handleTapboxChanged(bool newValue) {setState(() {_active = newValue;});
      }
      
      @override
      Widget build(BuildContext context) {
      return new Scaffold(
        appBar: new AppBar(title: new Text("简略混合治理状态"),
        ),
        body: new Container(
          child: new ListView(
            children: [new Text("_ParentWidgetCState 状态治理"),
              new Padding(padding: EdgeInsets.all(10)),
              new Text(_active ? 'Active' : 'Inactive',),
              new Padding(padding: EdgeInsets.all(10)),
              new Text("_TapboxCState 状态治理"),
              new TapboxC(
                active: _active,
                onChanged: _handleTapboxChanged,
              )
            ],
          ),
        ),
      );
      }
      }
      
      //----------------------------- TapboxC ------------------------------
      
      class TapboxC extends StatefulWidget {TapboxC({Key key, this.active: false, @required this.onChanged})
        : super(key: key);
      
      final bool active;
      final ValueChanged<bool> onChanged;
      
      @override
      _TapboxCState createState() => new _TapboxCState();
      }
      
      class _TapboxCState extends State<TapboxC> {
      bool _highlight = false;
      
      void _handleTapDown(TapDownDetails details) {setState(() {_highlight = true;});
      }
      
      void _handleTapUp(TapUpDetails details) {setState(() {_highlight = false;});
      }
      
      void _handleTapCancel() {setState(() {_highlight = false;});
      }
      
      void _handleTap() {widget.onChanged(!widget.active);
      }
      
      @override
      Widget build(BuildContext context) {
      // 在按下时增加绿色边框,当抬起时,勾销高亮  
      return new GestureDetector(
        onTapDown: _handleTapDown, // 解决按下事件
        onTapUp: _handleTapUp, // 解决抬起事件
        onTap: _handleTap,
        onTapCancel: _handleTapCancel,
        child: new Container(
          child: new Center(
            child: new Text(widget.active ? 'Active' : 'Inactive',
                style: new TextStyle(fontSize: 32.0, color: Colors.white)),
          ),
          width: 200.0,
          height: 200.0,
          decoration: new BoxDecoration(color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
            border: _highlight
                ? new Border.all(color: Colors.teal[700],
                    width: 10.0,
                  )
                : null,
          ),
        ),
      );
      }
      }

07. 全局状态如何治理

  • 当利用中须要一些跨组件(包含跨路由)的状态须要同步时,下面介绍的办法便很难胜任了。

    • 比方,咱们有一个设置页,外面能够设置利用的语言,咱们为了让设置实时失效,咱们冀望在语言状态产生扭转时,APP 中依赖利用语言的组件可能从新 build 一下,但这些依赖利用语言的组件和设置页并不在一起,所以这种状况用下面的办法很难治理。
    • 这时,正确的做法是通过一个全局状态管理器来解决这种相距较远的组件之间的通信。
  • 目前次要有两种方法:

    • 1. 实现一个全局的事件总线,将语言状态扭转对应为一个事件,而后在 APP 中依赖利用语言的组件的 initState 办法中订阅语言扭转的事件。当用户在设置页切换语言后,咱们公布语言扭转事件,而订阅了此事件的组件就会收到告诉,收到告诉后调用setState(...) 办法从新 build 一下本身即可。
    • 2. 应用一些专门用于状态治理的包,如 Provider、Redux,读者能够在 pub 上查看其详细信息。
  • 举一个简答的案例来实际

    • 本实例中,应用 Provider 包来实现跨组件状态共享,因而咱们须要定义相干的 Provider。
    • 须要共享的状态有登录用户信息、APP 主题信息、APP 语言信息。因为这些信息扭转后都要立刻告诉其它依赖的该信息的 Widget 更新,所以咱们应该应用ChangeNotifierProvider,另外,这些信息扭转后都是须要更新 Profile 信息并进行长久化的。
    • 综上所述,咱们能够定义一个 ProfileChangeNotifier 基类,而后让须要共享的 Model 继承自该类即可,ProfileChangeNotifier定义如下:

      class ProfileChangeNotifier extends ChangeNotifier {
        Profile get _profile => Global.profile;
      
        @override
        void notifyListeners() {Global.saveProfile(); // 保留 Profile 变更
          super.notifyListeners(); // 告诉依赖的 Widget 更新}
      }
    • 用户状态

      • 用户状态在登录状态发生变化时更新、告诉其依赖项,咱们定义如下:

        class UserModel extends ProfileChangeNotifier {
        User get user => _profile.user;
        
        // APP 是否登录(如果有用户信息,则证实登录过)
        bool get isLogin => user != null;
        
        // 用户信息发生变化,更新用户信息并告诉依赖它的子孙 Widgets 更新
        set user(User user) {if (user?.login != _profile.user?.login) {
          _profile.lastLogin = _profile.user?.login;
          _profile.user = user;
          notifyListeners();}
        }
        }

08.Provider 应用办法

8.1 正确地初始化 Provider

  • 如下所示,create 是必须要传递的参数

    ChangeNotifierProvider(create: (_) => MyModel(),
      child: ...
    )
  • 理论开发中如何利用

    builder: (BuildContext context, Widget child) {
        return MultiProvider(providers: [ChangeNotifierProvider(create: (context) => BusinessPattern()),
        ]);
    },
  • 而后看一下 BusinessPattern 是什么?

    class BusinessPattern extends ChangeNotifier {
      PatternState currentState = PatternState.none;
      void updateBusinessPatternState(PatternState state) {if (currentState.index != state.index) {LogUtils.d('以后模式:$currentState');
          LogUtils.d('更新模式:$state');
          currentState = state;
          notifyListeners();}
      }
    }

8.2 如何获取 Provider 取值

  • 一种是 Provider.of(context) 比方:

    Widget build(BuildContext context) {final text = Provider.of<String>(context);
      return Container(child: Text(text));
    }
    • 遇到的问题:因为 Provider 会监听 Value 的变动而更新整个 context 上下文,因而如果 build 办法返回的 Widget 过大过于简单的话,刷新的老本是十分高的。那么咱们该如何进一步管制 Widget 的更新范畴呢?
    • 解决办法:一个方法是将真正须要更新的 Widget 封装成一个独立的 Widget,将取值办法放到该 Widget 外部。

      Widget build(BuildContext context) {return Container(child: MyText());
      }
      
      class MyText extends StatelessWidget {
      @override
      Widget build(BuildContext context) {final text = Provider.of<String>(context);
      return Text(text);
      }
      }
  • Consumer 是 Provider 的另一种取值形式

    • Consumer 能够间接拿到 context 连带 Value 一并传作为参数传递给 builder,应用无疑更加不便和直观,大大降低了开发人员对于管制刷新范畴的工作老本。

      Widget getWidget2(BuildContext context) {return Consumer<BusinessPattern>(builder: (context, businessModel, child) {switch (businessModel.currentState) {
          case PatternState.none:
            return  Text("无模式");
            break;
          case PatternState.normal:
            return Text("失常模式");
            break;
          case PatternState.small:
            return Text("小屏模式");
            break;
          case PatternState.overview:
            return Text("全屏模式");
            break;
          default:
            return Text("其余模式");
            return SizedBox();}
      });
      }
  • Selector 是 Provider 的另一种取值形式

    • Selector 是 3.1 推出的性能,目标是更近一步的管制 Widget 的更新范畴,将监听刷新的范畴管制到最小
    • selector:是一个 Function,传入 Value,要求咱们返回 Value 中具体应用到的属性。
    • shouldRebuild:这个 Function 会传入两个值,其中一个为之前放弃的旧值,以及此次由 selector 返回的新值,咱们就是通过这个参数管制是否须要刷新 builder 内的 Widget。如果不实现 shouldRebuild,默认会对 pre 和 next 进行深比拟(deeply compares)。如果不雷同,则返回 true。
    • builder:返回 Widget 的中央,第二个参数 定义的参数,就是咱们方才 selector 中返回的 参数。

      Widget getWidget4(BuildContext context) {
      return Selector<BusinessPattern, PatternState>(selector: (context, businessPattern) =>
      businessPattern.currentState,
      builder: (context, state, child) {switch (state) {
          case PatternState.none:
            return  Text("无模式");
            break;
          case PatternState.normal:
            return Text("失常模式");
            break;
          case PatternState.small:
            return Text("小屏模式");
            break;
          case PatternState.overview:
            return Text("全屏模式");
            break;
          default:
            return Text("其余模式");
            return SizedBox();}
      }
      );

8.3 批改 Provider 状态

  • 如何调用批改状态治理

    BusinessPatternService _patternService = serviceLocator<BusinessPatternService>();
    // 批改状态
    _patternService.nonePattern();
    _patternService.normalPattern();
  • 而后看一下 normalPattern 的具体实现代码

    class BusinessPatternServiceImpl extends BusinessPatternService {
    
      final BuildContext context;
      BusinessPatternServiceImpl(this.context);
    
      PatternState get currentPatternState =>
          _getBusinessPatternState(context).currentState;
    
      BusinessPattern _getBusinessPatternState(BuildContext context) {return Provider.of<BusinessPattern>(context, listen: false);
      }
    
      @override
      void nonePattern() {BusinessPattern _patternState = _getBusinessPatternState(context);
        _patternState.updateBusinessPatternState(PatternState.none);
      }
    
      @override
      void normalPattern() {BusinessPattern _patternState = _getBusinessPatternState(context);
        _patternState.updateBusinessPatternState(PatternState.normal);
      }
    }

8.4 对于 Provider 刷新

  • 状态发生变化后,widget 只会从新 build,而不会从新创立(重用机制跟 key 无关,如果 key 发生变化 widget 就会从新生成)

09. 订阅监听批改状态

  • 首先定义抽象类。还须要写上具体的实现类

    typedef LocationDataChangedFunction = void Function(double);
    
    abstract class LocationListener {
      /// 注册数据变动的回调
      void registerDataChangedFunction(LocationDataChangedFunction function);
      /// 移除数据变动的回调
      void unregisterDataChangedFunction(LocationDataChangedFunction function);
      /// 更新数据的变动
      void locationDataChangedCallback(double angle);
    }
    
    
    class LocationServiceCenterImpl extends LocationListener {List<LocationDataChangedFunction> _locationDataChangedFunction = List();
    
      @override
      void locationDataChangedCallback(double angle) {_locationDataChangedFunction.forEach((function) {function.call(angle);
        });
      }
    
      @override
      void registerDataChangedFunction(LocationDataChangedFunction function) {_locationDataChangedFunction.add(function);
      }
    
      @override
      void unregisterDataChangedFunction(LocationDataChangedFunction function) {_locationDataChangedFunction.remove(function);
      }
    }
  • 那么如何应用呢?在须要用的页面增加接口回调监听

    _locationListener.registerDataChangedFunction(_onDataChange);
    void _onDataChange(double p1) {// 监听回调解决}
  • 那么如何发送事件,这个时候

    LocationListener _locationListener = locationService();
    _locationListener.locationDataChangedCallback(520.0);

fluter Utils 工具类库:https://github.com/yangchong2…

flutter 混合我的项目代码案例:https://github.com/yangchong2…

正文完
 0