Flutter - 狀態(tài)管理

Flutter作為響應(yīng)式開發(fā)的框架,狀態(tài)管理是Flutter中非常重要的一個部分,接下來就來看看Flutter中都有哪些狀態(tài)管理的方式。


State/InheritedWidget

StateInheritedWidgetFlutter狀態(tài)管理中扮演著重要的角色,不少的系統(tǒng)控件都采用了這種方式進行狀態(tài)管理。


State

因為Widget是不可變的,但是State支持跨幀保存數(shù)據(jù),所以Widget可以實現(xiàn)跨幀的狀態(tài)恢復(fù)/刷新。當我們調(diào)用setState((){});方法的時候,State內(nèi)部會通過調(diào)用markNeedsLayout方式,將對應(yīng)的Widget設(shè)置為_diry(臟標記),從而在下一幀執(zhí)行WidgetBinding.darwFrame時調(diào)用performLayout進行更新。


InheritedWidget

InheritedWidgetFlutter常用于數(shù)據(jù)的共享(從上至下),被InheritedWidget包裹起來的child可以通過BuildContext來獲取相對應(yīng)的數(shù)據(jù)。

所以StateInheritedWidget組成了Flutter中最基礎(chǔ)的狀態(tài)管理模式,通過State保存數(shù)據(jù)和管理狀態(tài),通過InheritedWidget來進行數(shù)據(jù)的共享,從而實現(xiàn)了跨頁面的數(shù)據(jù)傳遞。


示例

class InheritedText extends InheritedWidget {
  final String text;

  InheritedText({this.text, Widget child})
      : super(child: child);

  @override
  bool updateShouldNotify(covariant InheritedText oldWidget) {
    return text != oldWidget.text;
  }

  static InheritedText of(BuildContext context) {
    // 此方法已被標記位過期方法,建議使用下面的方法
    // return context.inheritFromWidgetOfExactType(InheritedText);
    return context.dependOnInheritedWidgetOfExactType<InheritedText>() ?? null;
  }
}

class DemoPage extends StatefulWidget {
  @override
  _DemoPageState createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  String _text = "init";

  @override
  Widget build(BuildContext context) {
    return InheritedText(
      text: _text,
      child: Scaffold(
        appBar: AppBar(),
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Builder(builder: (context) {
              return Text(
                InheritedText.of(context)?.text ?? "null",
                style: TextStyle(color: Colors.blue),
              );
            }),
            NextPage(),
          ],
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              _text = "Hello";
            });
          },
        ),
      ),
    );
  }
}

class NextPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text(InheritedText.of(context)?.text ?? ""));
  }
}


Stream

Stream達標事件流或者管道,通過Stream可以快速的實現(xiàn)給予事件驅(qū)動的業(yè)務(wù)邏輯,界面通過訂閱事件,并針對事件進行變換處理(非必須),最后可以實現(xiàn)界面跟著事件流/管道進行更新。如下圖:

image-20210324142246528.png


簡單Demo

如何通過Stream更新StatelessWidget?

class StreamDemoPage extends StatelessWidget {
  final StreamController<int> _controller;

  StreamDemoPage({Key key}): 
    _controller = StreamController<int>(), 
    super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Stream"),),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Text("StatelessWidget 利用 Stream 完成UI刷新"),
          StreamBuilder(
            initialData: 0,
            stream: _controller.stream,
            builder: (context, snapshot) {
              return Center(child: Text("${snapshot.data}"),);
            },
          )
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 導(dǎo)入math package
          // import 'dart:math';
          _controller.add(Random.secure().nextInt(1000));
        },
        child: Icon(Icons.refresh),
      ),
    );
  }
}

效果如下:

stream.gif


Stream的工作流程

Flutter中的StreamStreamController、StreamSink、StreamSubscription都是對外開放的接口抽象,內(nèi)部實現(xiàn)都是私有類。那么他們具體是什么關(guān)系呢?又是怎么實現(xiàn)事件流的呢?查看下圖

stream

Flutter中的事件流用Stream表示,為了能方便控制Stream,官方提供了StreamController作為管理接口;同時StreamController對外提供了StreamSink對象作為事件的輸入口,可以通過sink屬性訪問;又提供Stream對象用于監(jiān)聽和變化事件流;最后通過Stream訂閱得到Subscription,可以管理事件訂閱。

總結(jié)一下就是:

  • StreamController 用于控制Stream的過程,提供各種借口用于創(chuàng)建各種事件流
  • StreamSink 事件的輸入口,主要提供了add、addStream等方法
  • Stream 事件源本身,一般用于監(jiān)聽事件流或者對事件流進行變換,如listenwhere、map
  • StreamSubscription 事件訂閱后的得到的對象,可以用于取消訂閱、暫停等操作

作為事件的入口,當通過StreamSink.add添加一個事件是,事件最后會回調(diào)到Stream.listen中傳入的onData方法,從addonData的這個過程,在Stream的內(nèi)部就是通過_zone.runUnaryGuarded進行銜接的,而完成這個銜接的恰好就是StreamSubscription。

stream

1、Streamlisten時傳入了onData方法用于回調(diào),這個回調(diào)方法最終會被傳入StreamSubscription對象里,之后通過zone.registerUnaryCallback注冊得到_onData標識,這個_onData標識屬于當前Zone內(nèi)的全局標識,只要獲取_onData就可以通過Zone直接回調(diào)數(shù)據(jù)到listen中的onData

2、StreamSink在添加事件的時候,會執(zhí)行StreamSubscription中的_sendData方法(會根據(jù)同步還是異步分別調(diào)用add/addPending),然后通過_zone.runUnaryGuarded(_onData, data)執(zhí)行上一步得到的_onData對象,觸發(fā)listen傳入的onData方法,返回數(shù)據(jù)給訂閱者。


上面的流程是同步Stream,那么他的異步流程是怎么樣實現(xiàn)的呢?

前面的部分都是相同的,只是在_sendData方法中略有不同。同步的調(diào)用add,異步的則是調(diào)用addPending

stream 異步


Stream 同步、異步

Stream除了異步執(zhí)行以外,還可以同步執(zhí)行,通過設(shè)置sync字段來控制,內(nèi)部就會通過同步/還是異步返回不同的具體實例對象。

Stream構(gòu)造方法
    // 普通構(gòu)造方法
    factory StreamController(
      {void onListen()?,
      void onPause()?,
      void onResume()?,
      FutureOr<void> onCancel()?,
      bool sync = false}) {
    return sync
        ? _SyncStreamController<T>(onListen, onPause, onResume, onCancel)
        : _AsyncStreamController<T>(onListen, onPause, onResume, onCancel);
  
  /// 廣播的stream
  factory StreamController.broadcast(
      {void onListen()?, void onCancel()?, bool sync = false}) {
    return sync
        ? _SyncBroadcastStreamController<T>(onListen, onCancel)
        : _AsyncBroadcastStreamController<T>(onListen, onCancel);
  }

可以看出會根據(jù)sync返回不同的實例對象,根據(jù)構(gòu)造方法和sync不同,分別有四個實例對象。

SyncStreamController._sendData
  void _sendData(T data) {
    if (_isEmpty) return;
    if (_hasOneListener) {
      _state |= _BroadcastStreamController._STATE_FIRING;
      _BroadcastSubscription<T> firstSubscription =
          _firstSubscription as dynamic;
      firstSubscription._add(data);
      _state &= ~_BroadcastStreamController._STATE_FIRING;
      if (_isEmpty) {
        _callOnCancel();
      }
      return;
    }
    _forEachListener((_BufferingStreamSubscription<T> subscription) {
      subscription._add(data);
    });
  }
ASyncStreamController._sendData
  void _sendData(T data) {
    for (var subscription = _firstSubscription;
        subscription != null;
        subscription = subscription._next) {
      subscription._addPending(new _DelayedData<T>(data));
    }
  }

通過上面的兩個sendData方法的分析,可以看出兩者最大的區(qū)別就是一個是調(diào)用add方法另外一個是addPending方法。

其中addPending方法最終會調(diào)用到stream_impl.dart的schedule方法中

  void schedule(_EventDispatch<T> dispatch) {
    if (isScheduled) return;
    assert(!isEmpty);
    if (_eventScheduled) {
      assert(_state == _STATE_CANCELED);
      _state = _STATE_SCHEDULED;
      return;
    }
    scheduleMicrotask(() {
      int oldState = _state;
      _state = _STATE_UNSCHEDULED;
      if (oldState == _STATE_CANCELED) return;
      handleNext(dispatch);
    });
    _state = _STATE_SCHEDULED;
  }

整理一下異步的Stream發(fā)送流程,如下圖:

image-20210325095747120.png


StreamController的種類

上已經(jīng)了解到Stream分區(qū)異步/同步兩種類型,他們最要的區(qū)別就是混入的接口不一致,同步的混入的是_SyncStreamControllerDispatch、異步混入的是_AsyncStreamControllerDispatch。

  • 同步 _SyncStreamController
  • 異步 _AsyncStreamController
  • 同步廣播 _SyncBroadcastStreamController
  • 異步廣播 _AsyncBroadcastStreamController
image-20210325101249941.png

Stream 變換

Stream支持事件的變換處理,通過Stream變化可以讓事件經(jīng)過篩選和多次處理,從而達到最終效果。

image-20210325114427202.png

一般操作符變換實現(xiàn)對象,都是繼承了_ForwardingStream,在它內(nèi)部的_ForwardingStreamSubscription中,會把上一個Streamlisten添加到新的_handleData回調(diào),之后再回調(diào)里面調(diào)用新的(變換之后的)Stream_handleData,通過這樣子的嵌套回調(diào),讓Stream在多次變換之后一直往后執(zhí)行。


StreamBuilder

StreamBuilder是基于Stream的封裝,能夠讓開發(fā)者快速的根據(jù)Stream構(gòu)建應(yīng)用。比如上面的基于Stream讓StatelessWidget實現(xiàn)刷新效果的Demo。

那么StreamBuilder的內(nèi)部是怎么實現(xiàn)的呢?下面是關(guān)鍵代碼片段

class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {
  StreamSubscription<T> _subscription;
  S _summary;

  /// 進行監(jiān)聽
  @override
  void initState() {
    super.initState();
    _summary = widget.initial();
    _subscribe();
  }

  /// 重新訂閱
  @override
  void didUpdateWidget(StreamBuilderBase<T, S> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.stream != widget.stream) {
      if (_subscription != null) {
        _unsubscribe();
        _summary = widget.afterDisconnected(_summary);
      }
      _subscribe();
    }
  }

  @override
  Widget build(BuildContext context) => widget.build(context, _summary);

  /// 銷毀的是取消訂閱
  @override
  void dispose() {
    _unsubscribe();
    super.dispose();
  }

  /// 訂閱
  void _subscribe() {
    if (widget.stream != null) {
      _subscription = widget.stream.listen((T data) {
        setState(() {
          _summary = widget.afterData(_summary, data);
        });
      }, onError: (Object error) {
        setState(() {
          _summary = widget.afterError(_summary, error);
        });
      }, onDone: () {
        setState(() {
          _summary = widget.afterDone(_summary);
        });
      });
      _summary = widget.afterConnected(_summary);
    }
  }

  /// 取消訂閱
  void _unsubscribe() {
    if (_subscription != null) {
      _subscription.cancel();
      _subscription = null;
    }
  }
}


整理之后的流程圖如下:

image-20210325135515916.png


RxDart

其實從訂閱和變換的角度就可以看出,Dart中的Stream已經(jīng)有用了ReactiveX的設(shè)計思想,RxDart的出現(xiàn)就是為了能幫助那些了解過ReactiveX的框架的開發(fā)者,能夠快速的根據(jù)之前編寫習慣上手,其實RxDart底層也是基于Stream的一種封裝。下圖就是RxDart和Stream的對應(yīng)關(guān)系

Dart RxDart
StreamController Subject
Stream Observable

下面用一個PublishSubject的發(fā)送和監(jiān)聽做示例:


創(chuàng)建Subject
class PublishSubject<T> extends Subject<T> {
  PublishSubject._(StreamController<T> controller, Stream<T> stream)
      : super(controller, stream);

  /// 工廠方法內(nèi)部,可以很明顯的看到這里就是創(chuàng)建了一個廣播類型的StreamController
  factory PublishSubject(
      {void Function() onListen, void Function() onCancel, bool sync = false}) {
    // ignore: close_sinks
    final controller = StreamController<T>.broadcast(
      onListen: onListen,
      onCancel: onCancel,
      sync: sync,
    );

    return PublishSubject<T>._(
      controller,
      controller.stream,
    );
  }
}


添加事件

因為PublishSubject繼承自Subject,所以add方法在Subject之中:

  @override
  void add(T event) {
    if (_isAddingStreamItems) {
      throw StateError(
          'You cannot add items while items are being added from addStream');
    }

    _add(event);
  }

    /// 可以看到這里就是調(diào)用了controller.add方法
  void _add(T event) {
    onAdd(event);

    _controller.add(event);
  }


監(jiān)聽

因為PublishSubject繼承自Subject,所以listen方法在Subject之中:


    // 這里也可以發(fā)現(xiàn),onListen也是controller的listen
    @override
  set onListen(void Function() onListenHandler) {
    _controller.onListen = onListenHandler;
  }


銷毀

因為PublishSubject繼承自Subject,所以close方法在Subject之中:

  @override
  Future<dynamic> close() {
    if (_isAddingStreamItems) {
      throw StateError(
          'You cannot close the subject while items are being added from addStream');
    }

    /// 這里也是調(diào)用的controller的close
    return _controller.close();
  }


這里可能就有疑問了,如果只是提供了對Stream的一層封裝,那為什么ReactiveX還要如此大費周章呢?那是因為對于Stream的變換提供了很多的方法。包括bufferbufferCount、bufferTestbufferTime、contactWith、debounce、debounceTime等等,查看更多請點我


一個監(jiān)聽用戶輸入的demo
class RxDartDemoPage extends StatefulWidget {
  @override
  _RxDartDemoPageState createState() => _RxDartDemoPageState();
}

class _RxDartDemoPageState extends State<RxDartDemoPage> {
  PublishSubject _subject;
  TextEditingController _editingController;
  Stream _keywordStream;

  @override
  void initState() {
    super.initState();
    _editingController = TextEditingController();
    _subject = PublishSubject();

    _keywordStream = _subject
        .debounceTime(Duration(milliseconds: 500))
        .map((event) => "搜索關(guān)鍵字: $event");

    _editingController.addListener(() {
      _subject.add(_editingController.text);
    });
  }

  @override
  void dispose() {
    _subject?.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("RxDart")),
      body: Column(
        children: [
          TextField(
            controller: _editingController,
          ),
          SizedBox(height: 40),
          StreamBuilder(
            stream: _keywordStream,
            builder: (context, snapshot) {
              return Center(child: Text(snapshot.data ?? "請輸入關(guān)鍵詞"));
            },
          )
        ],
      ),
    );
  }
}


BLoC

BLoC全稱是Bussiness Logic Component,是谷歌提出的一種設(shè)計模式,BLoC利用了Flutter響應(yīng)式構(gòu)建的特點,通過流的方式實現(xiàn)界面的異步渲染,開發(fā)者可以銅鼓BLoC可以快速實現(xiàn)業(yè)務(wù)與界面的分離效果。

BLoC主要是通過StreamStreamBuilder結(jié)合實現(xiàn),目的就是把UI和邏輯分離。


demo

比如新建工程中的默認實現(xiàn)(點擊加號,數(shù)字加一),如果使用BLoC來實現(xiàn)就是新建一個類,內(nèi)部提供兩個對外開放的內(nèi)容,一個是stream另外一個則是add方法,通過streamStreamBuilder進行關(guān)聯(lián),當數(shù)據(jù)發(fā)生改變時主動更新UI;通過add方法對外公開,提供給按鈕點擊調(diào)用。

class BLoCDemoPage extends StatefulWidget {
  @override
  _BLoCDemoPageState createState() => _BLoCDemoPageState();
}

class _BLoCDemoPageState extends State<BLoCDemoPage> {
  final _CountBLoC _bLoC = _CountBLoC();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("BLoC"),
      ),
      body: Column(
        children: [
          Text("BLoC大多是利用Stream和StreamBuilder實現(xiàn),更多的是一種設(shè)計模式的思路,好處就是分離UI和邏輯層"),
          StreamBuilder(
            initialData: 0,
            stream: _bLoC.countStream,
            builder: (context, snapshot) => Center(
              child: Text(
                "${snapshot.data}",
                style: TextStyle(fontSize: 30, color: Colors.redAccent),
              ),
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: _bLoC.add,
      ),
    );
  }

  @override
  void dispose() {
    _bLoC.dispose();
    super.dispose();
  }
}

class _CountBLoC {
  int _count = 0;
  StreamController<int> _streamController = StreamController<int>();
  /// 提供給外界更新使用
  Stream get countStream => _streamController.stream;

  /// 觸發(fā)更新邏輯
  void add() {
    _count++;
    _streamController.add(_count);
  }

  /// 銷毀
  dispose() {
    _streamController?.close();
  }
}


流程圖

[圖片上傳失敗...(image-682214-1616915696007)]


scoped_model

scoped_modelFlutter中最簡單的第三方狀態(tài)的管理框架,它巧妙的利用了Flutter中的一些特性,只有一個dart的文件情況下,實現(xiàn)了實用的狀態(tài)管理模型。使用它一般需要三步。

  • 1、新建一個類并繼承Model,并且在想要更新UI的時候調(diào)用notifyListenrs
  • 2、使用ScopedModel控件加載Model
  • 3、使用ScopedModelDescendant或者ScopedModel.of<T>(context)加載model內(nèi)的數(shù)據(jù)進行顯示

demo

import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart' as sc;

class ScopeModelDemoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Scope model")),
      body: SafeArea(
        child: Container(
          child: sc.ScopedModel<_CountModel>(
            model: _CountModel(),
            child: sc.ScopedModelDescendant<_CountModel>(
              builder: (context, child, model) {
                return Column(
                  children: [
                    Expanded(child: Center(child: Text(model.count.toString()))),
                    Center(child: FlatButton(
                      onPressed: model.add,
                      color: Colors.blue,
                      child: Icon(Icons.add),
                    ),),
                  ],
                );
              },
            )
          ),
        ),
      ),
    );
  }
}

class _CountModel extends sc.Model {
  static _CountModel of(BuildContext context) =>
      sc.ScopedModel.of<_CountModel>(context);

  int _count = 0;

  int get count => _count;

  void add() {
    _count++;
    notifyListeners();
  }
}


流程圖

在查看ScopedModel的源碼之后,發(fā)現(xiàn)他首先使用AnimatedBuilder包裝起來,AnimatedBuilder繼承了AnimatedWidget,在AnimatedWidget的生命周期中會對Listenable接口添加監(jiān)聽,而Model恰好就實現(xiàn)了Listenable接口,從而可以達到刷新的效果。ScopedModel內(nèi)部除了使用AnimatedBuilder包裝起來之外,還使用_InheritedModel再次進行了包裝,保證了數(shù)據(jù)向下傳遞和共享。

ScopedModel.build方法源碼
@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: model,
    builder: (context, _) => _InheritedModel<T>(model: model, child: child),
  );
}
image-20210325173621768.png


flutter_reduce

redux是一種單向數(shù)據(jù)流架構(gòu)。在Redux中,數(shù)據(jù)都是存儲在單一信源(Store)中,然后數(shù)據(jù)存儲的時候通過Reducer進行更新,而觸發(fā)更新的動作就是Action。之所以說他是單向數(shù)據(jù)流,那是因為redux通過action發(fā)出的行為,通過reducer更新之后并把數(shù)據(jù)保存到store中,在加上Widget之后就變成了一個閉環(huán),如下圖:

流程圖

image-20210326135834721.png


使用流程

  • 1、創(chuàng)建State
  • 2、創(chuàng)建Action
  • 3、創(chuàng)建Reducer
  • 4、創(chuàng)建/保存Store
  • 5、關(guān)聯(lián)Widget
  • 6、發(fā)出Action,觸發(fā)第4步

demo

下面用一個兩個頁面之前的數(shù)據(jù)更新demo進行演示:

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

/// 1、創(chuàng)建State
class ReduxCountState {
  int _count;

  int get count => _count;

  ReduxCountState(this._count);
}

/// 2、創(chuàng)建Action
enum ReduxAction { increment }

/// 3、創(chuàng)建Reducer
ReduxCountState reducer(ReduxCountState state, dynamic action) {
  switch (action) {
    case ReduxAction.increment:
      return ReduxCountState(state.count + 1);
    default:
      return state;
  }
}

/// 攔截器,攔截器介于Action和Reducer之間,如果我們要對一些事件進行攔截,就可以在這里處理
/// 舉個例子:當我們更新用戶信息的時候(假設(shè)有頭像,名稱),需要去刷新,當我們只更新
/// 名稱的時候,由于頭像沒更新,我不希望頭像也倍刷新一次,此時就可以根據(jù)action,進行攔截不響應(yīng)處理
class ReduxCountMiddleware implements MiddlewareClass<ReduxCountState> {
  @override
  call(Store<ReduxCountState> store, action, next) {
    /// 只更新偶數(shù),奇數(shù)不處理
    if (store.state.count % 2 != 0) {
      next(action);
      print("xxxxxxxxx 我是攔截器,偶數(shù)通過");
    } else {
      next(action);
      next(action);
      print("xxxxxxxxx 我是攔截器,過濾奇數(shù)");
    }
  }
}

class FlutterReduxDemoPage extends StatefulWidget {
  @override
  _FlutterReduxDemoPageState createState() => _FlutterReduxDemoPageState();
}

class _FlutterReduxDemoPageState extends State<FlutterReduxDemoPage> {
  /// 4、創(chuàng)建Store
  final store = Store<ReduxCountState>(
    reducer,
    /// 攔截奇數(shù)
    middleware: [ReduxCountMiddleware()],
    initialState: ReduxCountState(0),
  );

  @override
  Widget build(BuildContext context) {
    return StoreProvider<ReduxCountState>(
      store: store,
      child: Scaffold(
          appBar: AppBar(
            title: Text("redux"),
          ),
          body: Center(
            /// 5、關(guān)聯(lián)Widget
            child: StoreConnector<ReduxCountState, String>(
              converter: (store) => store.state.count.toString(),
              builder: (context, val) => Text(
                val,
                style: TextStyle(fontSize: 30, color: Colors.red),
              ),
            ),
          ),
        /// 6、觸發(fā)
        floatingActionButton: StoreConnector<ReduxCountState, VoidCallback>(
          converter: (store) {
            return () => store.dispatch(ReduxAction.increment);
          },
          builder: (context, callback) {
            return FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: callback,
            );
          },
        ),
        ),
    );
  }
}


redux.gif

總結(jié)

redux內(nèi)部是通過InheritedWidgetStream以及StreamBuilder的自定義封裝。由于他的單向數(shù)據(jù)流思想,開發(fā)者的每個操作都只是一個Action,而這個行為所觸發(fā)的邏輯完全有middlewarereducer決定,這樣子的設(shè)計模式一定程度上將業(yè)務(wù)和UI進行了隔離,并且規(guī)范了整個事件流過程中的調(diào)用模式。

工具很強大,但是需要花費一定的學習成本,但是如果你之前是前端開發(fā)者,那么使用redux還是很得心應(yīng)手的。但如果你之前更多的是App 開發(fā),那可能Provider會更接近于你的使用方式。


Provide/Provider

Provider之前官方推薦的狀態(tài)管理方式之一就是provide,它的特點不復(fù)雜、好理解、可控度高,但是后面就被Provider給替代了。


流程圖

1、被設(shè)置到ChangeNotifierProviderChangeNotifier會被執(zhí)行addListener,添加listener

2、listener內(nèi)部會調(diào)用StateDelegateStateSetter方法,從而調(diào)用到StatefulWidgetsetState

3、當執(zhí)行ChangeNotifiernotifyListeners,最終就會觸發(fā)setState更新

image-20210326164059178.png


使用流程

  • 1、創(chuàng)建ChangeNotifier的子類,實現(xiàn)相關(guān)內(nèi)部邏輯

  • 2、使用包括但不限于的:ChangeNotfierProviderMultiProviderprovider封裝好的實體類把ChangeNotifier的子類加入到provider之中

  • 3、使用ConsummerProvider.of(context)等引用provider的值進行關(guān)聯(lián)

    如果只是想獲取provider的值,并不想根據(jù)狀態(tài)進行更新需要使用Provider.of<T>(context, listen: false)來獲取到Provider

  • 4、調(diào)用ChangeNotifier的子類的notifyListeners方法觸發(fā)更新


demo

之所以說簡單,我們通過官方的數(shù)字累加Demo就可以看出

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class ProviderDemoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => _CountProvider(),
      child: Scaffold(
        appBar: AppBar(title: Text("Provider")),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Builder(
              builder: (context) {
                return Text(
                  Provider.of<_CountProvider>(context).count.toString(),
                  style: TextStyle(fontSize: 30, color: Colors.orangeAccent),
                );
              },
            ),
            Consumer<_CountProvider>(
              builder: (context, provider, child) {
                return Center(
                  child: Text(
                    provider.count.toString(),
                    style: TextStyle(fontSize: 30, color: Colors.orangeAccent),
                  ),
                );
              },
            )
          ],
        ),
        floatingActionButton: Builder(
          builder: (context) => FloatingActionButton(
            onPressed: () {
              Provider.of<_CountProvider>(context, listen: false)?.add();
            },
            child: Icon(Icons.add),
          ),
        ),
      ),
    );
  }
}

class _CountProvider extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void add() {
    _count++;
    notifyListeners();
  }
}


總結(jié)

Provider提供了一種簡單、不復(fù)雜,可控性好的狀態(tài)管理方式,通過MultiProviders可以在一個頁面中插入多個Provider,在配合Consumer使用可以實現(xiàn)顆粒度級別的刷新,避免造成不必要的性能浪費。也是目前主流的狀態(tài)管理方式之一。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容