/ Flutter

Flutter中的状态管理

Flutter作为出自Google的一个跨平台(iOS,Android)应用开发方案。布局方式上和React或者说React Native非常相似——组件(Widget)化。写起来非常的高效,却有着React Native所不具有的优势: 一套代码到处运行,原生渲染,原生调用,不需要像RN需要桥接。

前端应用除去布局部分,就属状态管理最复杂难搞了。官方文档中只是提及了最基础的部分,因此本文中着重讨论这部分。

下面基本上转述自Google I/O '18上视频Build reactive mobile apps with Flutter,内容较水,推荐大家看视频就够了😄


setSate

是的你没看错,就是和React中一模一样的setStae。Flutter将组件分为StatefulWidget,StatelessWidget,自然有状态的组件使用继承Flutter将组件为StatefulWidget。flutter create app开箱中代码就是实例了setSate

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), 
    );
  }
}

写法也比较简单,适用于组件层比较浅的情况,但是如果需要跨组件共享state的时候,你只能放在它们共有的祖先组件上,然后逐层传递,这样有势必会造成多余的组件更新。

Build-reactive-mobile-apps-with-Flutter--Google-I-2FO--18--0001

InheritedWidget, context

逐层传递state太过于笨重,Flutter官方提供了InheritedWidget Class来去优化这个问题,基本上就是将需要共享的State放在一个继承InheritedWidget的类中,然后在使用的组件widget中直接取用就是。

明眼人一看便知,这就是React中Context。

正如React中有基于context的社区库Redux,正式使用时候InheritedWidget相对比较基础,你需要写一大堆模版类的代码来满足需求,因此推荐使用flutter社区的库scoped_model来方便开发。下面是库官方的例子:

class CounterModel extends Model {
  int _counter = 0;

  int get counter => _counter;

  void increment() {
    _counter++;
    
    notifyListeners();
  }
}

class CounterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new ScopedModel<CounterModel>(
      model: new CounterModel(),
      child: new Column(children: [
        new ScopedModelDescendant<CounterModel>(
                builder: (context, child, model) => new Text(
                    model.counter.toString()),
              ),
        new Text("Another widget that doesn't depend on the CounterModel")
      ])
    );
  }
}

就是两个类ModelScopedModelDescendant,前者用来存储数据,后者用来包裹组件以此来提供state

值得注意的所有被包裹过的组件在状态变化的时候都会重新渲染,这样可能会造成不必要性能损失。ScopedModelDescendant也提供了阻止重新渲染的参数rebuildOnChange: false。**。

稍微了解过React的可以想得到,这个就类似于shouldComponentUpdate,不太建议使用,很容易滥用误用造成难以发现的bug。

StreamBuilder, ReactiveX

正如上文所说,状态管理很难,特别是异步环境下的状态管理更难,难在哪里?不外乎就是能够做到:

  • 方便拿到State
  • 能够将更新高效的通知给依赖组件
  • 能够精准的做到最小更新

想要更好解决这些问题,就需要引入Reactive响应的概念了。引用前端届的RxJS来说:

Observable = lodash for async

Flutter的官方语言Dart中内置了Stream的概念

Stream ~= Observable

因此不言而喻,就是将需要需要管理的State转化为Stream,然后使用Flutter官方的StreamBuilder来订阅所需要数据源,方便快捷,高效。

class MyTimer {
  static Stream<String> timerInterval$ = new Stream.periodic(Duration(seconds: 1)).map((int) => new DateTime.now().toString());
}

class _RealTimeText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return StreamBuilder(
      stream: MyTimer.timerInterval$,
      builder: (context, snapshot) => Text(snapshot.data),
    );
  }
}

Rx相对Stream来说,提供了更多方法,社区中资料也多,dart社区也有RxDart, 正式使用还是少不了。

总结

上面的三种算是主流,官方推荐的Flutter 状态管理的方法了,Rx很强大,但是概念相对复杂,也相对难以掌控,Scope model的方式虽说有缺陷倒也上手容易,已经能很好的解决问题,初学者不妨从它来开始……