状态管理

什么是状态管理?

在Flutter 声明式的代码编写范式下,优雅的处理各个Widget 的状态。

为什么要状态管理?

  1. 解耦。在面对复杂的项目时,可以更好的保证项目的可读性、可拓展性
  2. 局部刷新。局部的状态改变时,不需要重建整个页面的Widget,只需要构建局部的,这样能提升性能

页面的Widget 树形结构

数据共享

实际开发中经常会碰到某些需求,需多个Widget共享同一份数据,比如点赞数、评论数、主题等等场景。

示例:

import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  @override
  _TestPageState createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Text('$_count',style: TextStyle(fontSize: 20),),
              BottomWidget(count: _count,callback: changeCount,)
            ],
        ),
      ),
    );
  }

  changeCount(int num){
    setState(() {
      _count = num;
    });
  }
}

class BottomWidget extends StatelessWidget {
  final int count;
  final Function callback;

  BottomWidget({this.count,this.callback});

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        FlatButton(
          onPressed: (){
            callback(count -1);
          },
          child: Icon(Icons.remove),
        ),
        Text('$count'),
        FlatButton(
          onPressed: (){
            callback(count+1);
          },
          child: Icon(Icons.add),
        )
      ],
    );
  }
}

InheritedWidget

是Flutter的一个功能型的Widget,它能有效地将数据在当前Widget中向它的子widget树传递,实现数据共享。需要注意,它是将数据自顶向下共享,意即将InheritedWidget作为根Widget,则它下面的子Widget都可获得该Widget持有的数据。

import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  @override
  _TestPageState createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return CountWidget(
      _count,
      child: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Text('$_count',style: TextStyle(fontSize: 20),),
              BottomWidget(callback: changeCount,)
            ],
          ),
        ),
      ),
    );
  }

  changeCount(int num){
    setState(() {
      _count = num;
    });
  }
}

/// 继承自InheritedWidget,实现updateShouldNotify方法
class CountWidget extends InheritedWidget{
  final int count;

  CountWidget(this.count,{Widget child}):super(child:child);

  static CountWidget of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<CountWidget>();
  }

  @override
  bool updateShouldNotify(CountWidget oldWidget) {
    return count != oldWidget.count;
  }
}

class BottomWidget extends StatelessWidget {
  final Function callback;

  BottomWidget({this.callback});

  @override
  Widget build(BuildContext context) {
    /// 获取共享的数据
    var count = CountWidget.of(context).count;

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        FlatButton(
          onPressed: (){
            callback(count -1);
          },
          child: Icon(Icons.remove),
        ),
        Text('$count'),
        FlatButton(
          onPressed: (){
            callback(count+1);
          },
          child: Icon(Icons.add),
        )
      ],
    );
  }
}

事件通知

以上数据共享处理了多个Widget读取同一份数据的情况,我们还需要处理共享数据被修改的情况,即共享数据被修改时,依赖该数据的Widget应该收到通知。Flutter中的Notification就是处理这种情况的分发机制。需要注意,Notification是一种自底向上传递通知的机制,意即由子Widget向父Widget传递通知。

import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  @override
  _TestPageState createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return CountWidget(
      _count,
      child: NotificationListener<MyNotification>(
        /// 监听通知
        onNotification: (notification) {
          if(notification.msg == 'add'){
            _count ++;
          }else{
            _count --;
          }
          setState(() {});
          return true;
        },
        child: Scaffold(
          body: Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                Text('$_count',style: TextStyle(fontSize: 20),),
                BottomWidget()
              ],
            ),
          ),
        ),
      ),
    );
  }
}

/// 自定义通知
class MyNotification extends Notification {
  MyNotification(this.msg);
  final String msg;
}

class CountWidget extends InheritedWidget{
  final int count;

  CountWidget(this.count,{Widget child}):super(child:child);

  static CountWidget of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<CountWidget>();
  }

  @override
  bool updateShouldNotify(CountWidget oldWidget) {
    return count != oldWidget.count;
  }
}

class BottomWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var count = CountWidget.of(context).count;

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        FlatButton(
          /// 点击时分发通知  
          onPressed: () => MyNotification("sub").dispatch(context),
          child: Icon(Icons.remove),
        ),
        Text('$count'),
        FlatButton(
          onPressed: () => MyNotification("add").dispatch(context),
          child: Icon(Icons.add),
        )
      ],
    );
  }
}

如果两个NotificationListener进行嵌套,子NotificationListeneronNotification回调返回false,则表示不阻止继续向上传递,父NotificationListener会收到通知,如果返回值为true,则会终止传递。

这种通知机制的缺点是明显的,它不能自上向下分发通知,话句话说,父控件产生的通知不能发给子控件。

局部刷新

Flutter 给我们提供了两种局部刷新的方式

  • 自行封装StatefulWidget。即将页面拆分封装成一个个小的StatefulWidget,在需要刷新的区域,调用对应的StatefulWidgetsetState方法更新局部UI。
  • 使用ValueListenableBuilder控件。该控件本质上仍然是对StatefulWidget的封装,仅帮助我们简化代码,无需自己封装而已。

数据的观察者

当数据发送变化时,UI需要监听到这种变化,由此Flutter中提供了一种对数据的观察者模式。Flutter是一种响应式的框架,以数据驱动UI,因此,UI是作为观察者存在,数据则是被观察者。

  • ValueNotifier

这里我们将ValueListenableBuilderValueNotifier 结合起来使用,就能实现局部刷新的功能

ValueNotifier<String> _name = ValueNotifier<String>('');

ValueListenableBuilder(
    builder: (context, value, child) {
      return Text(value);
    },
    valueListenable: _name,
    child: Text('张三'),
  );

自定义状态管理

InheritedWidget + ValueListenableBuilder + ValueNotifier


import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  @override
  _TestPageState createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  ValueNotifier<int> _count = ValueNotifier<int>(0);

  @override
  Widget build(BuildContext context) {
    return CountWidget(
      _count,
      child: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              ValueListenableBuilder(
                builder: (context, value, child) {
                  return Text('$value',style: TextStyle(fontSize: 20),);
                },
                valueListenable: _count,
              ),

              BottomWidget()
            ],
          ),
        ),
      ),
    );
  }
}

class CountWidget extends InheritedWidget{
  final ValueNotifier<int> count;

  CountWidget(this.count,{Widget child}):super(child:child);

  static CountWidget of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<CountWidget>();
  }

  @override
  bool updateShouldNotify(CountWidget oldWidget) {
    return count.value != oldWidget.count.value;
  }
}

class BottomWidget extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    var _count = CountWidget.of(context).count;

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        FlatButton(
          onPressed: () => _count.value--,
          child: Icon(Icons.remove),
        ),
        /// 不会刷新
        Text('${_count.value}'),
        FlatButton(
          onPressed: () => _count.value++,
          child: Icon(Icons.add),
        )
      ],
    );
  }
}

这里有一个问题,ValueNotifier只能监听一个数据,如果我们有多个数据时,则可以模仿ValueNotifier的实现源码,自定义数据模型类继承自ChangeNotifier

跨组件通信

学习了以上许多概念,这里做一个小结,当我们需要跨多个小控件实现数据通信时,可以使用以下三种较常见的方式

  • 使用GlobalKey跨Widget访问
  • 使用ValueNotifier 实现数据通知
  • 使用第三方封装的库event_bus 基于流的跨组件通信
import 'package:event_bus/event_bus.dart';

EventBus eventBus = new EventBus();

/// 定义事件
class MyEvent{
    String text;
    MyEvent(this.text);
}

/// 发送事件
eventBus.fire(new MyEvent('test'));

/// 监听事件
eventBus.on<MyEvent>().listen((MyEvent data) => handle(data.text));

/// 处理事件
void handle(String val) {
 setState(() {
  data = val;
 });
}

状态管理框架


公众号“编程之路从0到1”

20190301102949549

Copyright © Arcticfox 2020 all right reserved,powered by Gitbook文档修订于: 2022-05-01 12:00:54

results matching ""

    No results matching ""