初略讲解Flutter的状态管理

响应式的编程框架中都会有一个永恒的主题——“状态管理”,不管是React/Vue(二者都是支持响应式编程的Web开发框架)仍是Flutter,讨论的问题和解决的思想都是一致的。因此,若是你对React/Vue的状态管理有了解,能够跳过本节。言归正传,咱们想一个问题,Stateful Widget的状态应该被谁管理?是Widget自己?是父Widget?仍是都会?亦或是另外一个对象?答案是:取决于实际状况!编程

如下是管理状态的最多见的方式:redux

  • Widget管理自身的状态;
  • 父Widget管理子Widget的状态;
  • 混合管理(父Widget和子Widget都管理状态)。

如下原则能够帮助你决定如何决定使用哪一种管理方式?bash

  • 若是状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父Widget来管理;
  • 若是状态是有关界面外观效果的,如颜色、动画,则该状态最好由Widget自己来管理;
  • 若是某一个状态是不一样的Widget共享的,则最好由它们共同的父Widget来管理。

在Widget内部管理状态封装性会好一些,而在父Widget中管理会比较灵活。有些时候,若是不肯定到底该由谁来管理状态,那么首选由父Widget来管理(由于灵活会显得更重要一些)。框架

接下来,咱们将经过建立三个简单示例TapboxA、TapboxB和TapboxC来讲明管理状态的不一样方式。这些例子的功能是类似的——建立一个盒子,当点击它时,盒子背景会在绿色与灰色之间切换,状态_active肯定颜色:绿色为true,灰色为falseless

下面的例子将使用GestureDetector来识别点击事件,关于该GestureDetector的详细内容咱们将在后续进行讲解。ide

方式一: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],
        ),
      ),
    );
  }
}
复制代码

方式二:父Widget管理子Widget的状态

对于父Widget来讲,管理状态并告诉其子Widget什么时候更新一般是比较好的方式。例如,IconButton是一个图片按钮,但它是一个无状态的Widget,由于咱们认为父Widget须要知道该按钮是否被点击从而采起相应的处理。动画

在如下示例中,TapboxB经过回调将其状态导出到其父项。因为TapboxB无论理任何状态,所以它的父类为StatefulWidget,TapboxB为StatelessWidget。ui

ParentWidgetState类:this

  • 为TapboxB管理_active状态;
  • 实现_handleTapboxChanged(),当盒子被点击时调用的方法;
  • 当状态改变时,调用setState()更新UI。

TapboxB类:

  • 继承StatelessWidget类,由于全部状态都由其父Widget处理;
  • 当检测到点击时,它会通知父Widget。
// ParentWidget 为 TapboxB 管理状态.

//------------------------ ParentWidget --------------------------------

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 Container(
      child: new TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//------------------------- TapboxB ----------------------------------

class TapboxB extends StatelessWidget {
  TapboxB({Key key, this.active: false, @required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!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],
        ),
      ),
    );
  }
}
复制代码

方式三:混合管理

对于一些Widget来讲,混合管理的方式很是有用,在这种状况下,Widget自身管理一些内部状态,而父Widget管理一些其它外部状态。

在下面TapboxC示例中,按下时,盒子的周围会出现一个深绿色的边框,抬起时,边框消失,点击生效,盒子的颜色改变。TapboxC将其_active状态导出到其父Widget中,但在内部管理其_highlight状态。这个例子有两个状态对象_ParentWidgetCState_TapboxCState

_ParentWidgetCState对象:

  • 管理_active状态;
  • 实现_handleTapboxChanged(),当盒子被点击时调用;
  • 当点击盒子而且_active状态改变时调用setState()方法更新UI。

_TapboxCState对象:

  • 管理_highlight状态;
  • GestureDetector监听全部Tap事件,当用户按下时,添加高亮(深绿色边框),当用户抬起(释放按下)时,会移除高亮;
  • 当按下、抬起或者取消点击时更新_highlight状态,调用setState()方法更新UI;
  • 当按下时,将状态的改变传递给父Widget。
//---------------------------- 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 Container(
      child: 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,
        ),
      ),
    );
  }
}
复制代码

扩展:全局状态管理

当应用中包括一些跨Widget(甚至跨路由)的状态须要同步时,上面介绍的方法就很难胜任了。好比,有一个设置页,里面能够设置应用语言,可是为了让设置实时生效,咱们指望在语言状态发生改变时,APP Widget可以从新构建(build),可是APP Widget和设置页并不在一块儿,那怎么办呢?正确的作法是经过一个全局状态管理器来处理这种“相距较远”的Widget之间的通讯。

目前只要有两种方法:

  1. 实现一个全局的事件总线,将语言状态改变对应为一个事件,而后在APP Widget所在的父Widget initState()方法中订阅语言状态改变的事件,当用户在设置页切换语言状态后,触发语言状态改变事件,而后APP Widget就会收到通知,接着从新构建(build)一下便可。
  2. 使用redux的全局状态包,读者能够在pub上查看其详细信息。

关于全局事件总线的实现,咱们后续进行。

相关文章
相关标签/搜索