【Flutter】开源项目系列之FlutterRedux

2,165 阅读2分钟

前言

原文章回顾发现并没有很好讲解Redux,因此重新写关于FlutterRedux使用和源码解析以加深对redux的理解和使用。

对于前端同学Redux并不陌生,它可以对应用全局状态做管理。当项目庞大每个组件都可能会引用全局某一个状态值的时候,Redux就能很好的体现它的价值。通过store管理应用全局状态,当执行Action对状态做更新时对Store的state进行修改,最终再通知到Widget达到唯一状态值的管理和更新的目的。 了解Redux思想后再来看看FlutterRedux是如何实现状态管理的。

示例Demo

如下为一个简单FlutterRedux使用场景:

  • 1、定义一个全局AppState状态管理类,编写Action和Reducer
  • 2、初始化全局Store,初始化state和reducer
  • 3、必须使用StoreProvider嵌套MaterialApp
  • 4、在需要使用数据状态的组件外层嵌套StoreBuilder获取数据
  • 5、通过StoreProvider分发动作实现数据状态改变
   //①初始化
 // State
  class AppState {
    int count = 0;
  }
  // 全局store初始化
  Store<AppState> store;
  // Actions
  enum ReduceActions { Increment, Reducement }
  // Reducer
  AppState counterReducer(AppState state, dynamic action) {
    if (action == ReduceActions.Increment) {
      return state..count += 1;
    } else if (action == ReduceActions.Reducement) {
      return state..count -= 1;
    }
    return state;
  }
  @override
  void initState() {
    super.initState();
    //②初始化
    store =  FlutterRedux.Store<AppState>(counterReducer, initialState: AppState());
  }
 // 全局MaterialApp需要被StoreProvider嵌套
 // ③storeProvider作为根节点
 StoreProvider<AppState>(
      store: store,
      child: MaterialApp(
        ......
        home: MyHomePage(title: 'Flutter Demo Home Page'),
      ),
    );



class ReduxDemo extends StatefulWidget {
  @override
  _ReduxDemoState createState() => _ReduxDemoState();
}

class _ReduxDemoState extends State<ReduxDemo> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: <Widget>[
          //④StoreBuilder用来接收数据状态
          //使用到状态管理的组件用StoreBuilder包含
          StoreBuilder<AppState>(builder: (context, store) {
            return Text("${store.state.count}");
          }),
          Row(
            children: <Widget>[
              RaisedButton(
                child: Text("add"),
                onPressed: () {
                 //⑤分发Action
                  //执行Action方法通过StoreProvider分发
                  StoreProvider.of<AppState>(context)
                      .dispatch(ReduceActions.Increment);
                },
              ),
              RaisedButton(
                child: Text("reduce"),
                onPressed: () {
                    //⑤分发Action
                  StoreProvider.of<AppState>(context)
                      .dispatch(ReduceActions.Increment);
                },
              )
            ],
          )
        ],
      ),
    );
  }
}

源码解析

知道如何使用FlutterRedux之后接着从源码入手更深入了解FlutterRedux是如何实现状态全局管理。可以先从StoreProvider、StoreBuilder、StoreConnector着手慢慢解析学习Redux的奥义所在。

StoreProvider

主要管理store和根组件child,通过updateShouldNotify判断store若有变化则刷新组件。另外MaterialApp必须被StoreProvider嵌套才能在整个应用中实现状态管理。

  • 1、StoreProvider继承自InheritedWidget,可以让当前节点下的Widget获取最近InheritedWidget实例。这里的StoreProvider主要是为了获取StoreProvider.of(context)根节点全局状态Store实例。
  • 2、通过updateShouldNotify接口判断store变化来更新widget,这一点也是InheritedWidget组件的特性。
 const StoreProvider({
    Key key,
    @required Store<S> store,
    @required Widget child,
  })  : assert(store != null),
        assert(child != null),
        _store = store,
        super(key: key, child: child);
......
  @override
  bool updateShouldNotify(StoreProvider<S> oldWidget) =>
      _store != oldWidget._store;

如上述Demo中例如StoreProvider.of(context).dispatch(ReduceActions.Increment);先获取到全局Store实例然后执行dispatch方法分发Action对全局状态更新。

static Store<S> of<S>(BuildContext context, {bool listen = true}) {
    final type = _typeOf<StoreProvider<S>>();
    ///获取到widget最接近的InheritedWidget实例
    final provider = (listen
        ? context.inheritFromWidgetOfExactType(type)
        : context
            .ancestorInheritedElementForWidgetOfExactType(type)
            ?.widget) as StoreProvider<S>;

    if (provider == null) throw StoreProviderError(type);

    return provider._store;
  }

StoreBuilder

当有组件需要获取全局状态时使用StoreBuilder嵌套可获取全局Store数据状态。另外也可以使用StoreConnector对组件做嵌套自由度更高自定义程度更高。对于在开发过程中选择StoreBuilder还是StoreConnector后面做详细分析。

class StoreBuilder<S> extends StatelessWidget {
    
 const StoreBuilder({
    Key key,
    @required this.builder,
    this.onInit,
    this.onDispose,
    this.rebuildOnChange = true,
    this.onWillChange,
    this.onDidChange,
    this.onInitialBuild,
  })  : assert(builder != null),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    return StoreConnector<S, Store<S>>(
      builder: builder,
      converter: _identity,
      rebuildOnChange: rebuildOnChange,
      onInit: onInit,
      onDispose: onDispose,
      onWillChange: onWillChange,
      onDidChange: onDidChange,
      onInitialBuild: onInitialBuild,
    );
  }   
}

_StoreStreamListener

StoreBuilder是StatelessWidget组件,内部主要由StoreConnector实现。而StoreConnector内部又是由_StoreStreamListener实现,所以直接跳过前两者直接看看_StoreStreamListener源码。可以看到_StoreStreamListener实质上则是通过StreamBuilder对数据监听。

class _StoreStreamListener<S, ViewModel> extends StatefulWidget {
  final ViewModelBuilder<ViewModel> builder; //创建视图组件接口
  final StoreConverter<S, ViewModel> converter; //获取最终组件所需要的ViewModel
  final Store<S> store; //全局状态管理数据源
  final bool rebuildOnChange;//判断是否有StreamBuilder形式更新组件数据
  final bool distinct;//是否ViewModel根据不同刷新
  final OnInitCallback<S> onInit;//初始化时获取全局Store
  final OnDisposeCallback<S> onDispose;//组件dispose回调带全局Store
  final IgnoreChangeTest<S> ignoreChange; 
  final OnWillChangeCallback<ViewModel> onWillChange; 
  final OnDidChangeCallback<ViewModel> onDidChange;
  final OnInitialBuildCallback<ViewModel> onInitialBuild;

  const _StoreStreamListener({
    Key key,
    @required this.builder,
    @required this.store,
    @required this.converter,
    this.distinct = false,
    this.onInit,
    this.onDispose,
    this.rebuildOnChange = true,
    this.ignoreChange,
    this.onWillChange,
    this.onDidChange,
    this.onInitialBuild,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _StoreStreamListenerState<S, ViewModel>();
  }
}
  • 1、在_StoreStreamListener初始化initState获取全局store数据状态
  • 2、通过converter获取最终变化值
  • 3、若有onInitialBuild则在刷新帧中获取初始化的值。
  • 4、最后创建Stream,通过StreamBuilder获取数据流更新视图
///_StoreStreamListener初始化中可以
 @override
  void initState() {
    ///
    if (widget.onInit != null) {
      widget.onInit(widget.store);
    }
    ///
    if (widget.onInitialBuild != null) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        widget.onInitialBuild(latestValue);
      });
    }
    ///
    latestValue = widget.converter(widget.store);
    _createStream();
    super.initState();
  }
   @override
  Widget build(BuildContext context) {
    return widget.rebuildOnChange
        ? StreamBuilder<ViewModel>(
            stream: stream,
            builder: (context, snapshot) => widget.builder(
              context,
              latestValue,
            ),
          )
        : widget.builder(context, latestValue);
  }
  ......
  ///④ 主要对store数据状态进行监听若是所需的数据状态发生变化就做一定处理后再返回
  void _createStream() {
  stream = widget.store.onChange
      .where(_ignoreChange)
      .map(_mapConverter)
      // Don't use `Stream.distinct` because it cannot capture the initial
      // ViewModel produced by the `converter`.
      .where(_whereDistinct)
      // After each ViewModel is emitted from the Stream, we update the
      // latestValue. Important: This must be done after all other optional
      // transformations, such as ignoreChange.
      .transform(StreamTransformer.fromHandlers(handleData:_handleChange));
  }
  

以上源码阅读大致了解FlutterRedux是如何对数据状态进行管理、状态变化以及数据更新的。

Store

store主要由Reducer<State>、StreamController、State、List<NextDispatcher>组成。

  • 1、changeController是StreamController.broadcast多订阅的形式,通过Stream发送state变化。
  • 2、state就是Store全局状态管理。
  • 3、_dispatchers用在dispatch分发action去更新state。
class Store<State> {
 
  Reducer<State> reducer; 
  final StreamController<State> _changeController;
  State _state;
  List<NextDispatcher> _dispatchers;
  
  Store(
    /// action处理的地方
    this.reducer, {
    /// 需要初始化的State
    State initialState,
    /// 中间件处理的地方
    List<Middleware<State>> middleware = const [],
    /// 是否同步
    bool syncStream = false,
    /// 
    bool distinct = false,
  }) : 
   /// 
   _changeController = StreamController.broadcast(sync: syncStream) {
    /// 
    _state = initialState;
    /// 
    _dispatchers = _createDispatchers(
      middleware,
      _createReduceAndNotify(distinct),
    );
  }
  typedef dynamic NextDispatcher(dynamic action);
  /// ③创建ispatcher,入参中间件和action方法
  List<NextDispatcher> _createDispatchers(
    List<Middleware<State>> middleware,
    NextDispatcher reduceAndNotify,
  ) {
    final dispatchers = <NextDispatcher>[]..add(reduceAndNotify);
   
    // Convert each [Middleware] into a [NextDispatcher]
     /// 中间件可以看做是责任链模式,向下一个中间件传递
    for (var nextMiddleware in middleware.reversed) {
      final next = dispatchers.last;

      dispatchers.add(
        (dynamic action) => nextMiddleware(this, action, next),
      );
    }
    return dispatchers.reversed.toList();
  }  
  /// ③创建NextDispatcher过程,返回一个参入action方法,通过reducer处理后返回state,然后判断state和distinct标记位判断state是否发生变化。若有变化则通过_changeController以stream形式发生。
  NextDispatcher _createReduceAndNotify(bool distinct) {
    return (dynamic action) {
      final state = reducer(_state, action);

      if (distinct && state == _state) return;

      _state = state;
      _changeController.add(state);
    };
  }

通过源码可知Store管理state数据状态,创建责任链形式的 List<NextDispatcher>。当使用dispatch方法触发NextDispatcher时,通过reducer获取action更新state中的数据状态若有中间件则进一步处理数据最终通过StreamController<State>通知到需要更新的组件。

最后通过图片概述总结redux工作流程:

  • 1、全局定义Store和StoreProvider(InheritedWidget)组件。
  • 2、状态组件(StoreBuilder或是StoreConnector)持有全局定义Store实例,通过onChage方法订阅Stream的数据变化监听。
  • 3、StoreProvider实例做dipatcher分发Action操作后更新state。
  • 4、StreamController发送新State数据流。
  • 5、状态组件已经订阅Stream数据更新监听,获取到新State更新UI。

如何选择StoreBuilder与StoreConnector

  • 1、StoreBuilder比起StoreConnector使用上更简单更直接。
  • 2、StoreConnector具备更高自定义,通过ViewModel组合出想要的数据结构。
  • 3、StoreConnector比起StoreBuilder更高效,可控制刷新粒度更高。

StoreBuilder

只要state有更新StoreBuilder的builder就会做刷新操作。在使用上StoreBuilder更直观更直接只关注于state状态变化。

 StoreBuilder<AppState>(
            builder: (context, store) {
              return Text("${store.state.count}");
            },
          ),

StoreConnector

当ViewModel重写==和hashCode并且StoreConnector设distinct为true时,只有当ViewModel数据改变才会对builder做刷新。

StoreConnector<AppState, DemoViewChangeModel>(
  converter: DemoViewChangeModel.fromStore,
  builder: (context, vm) {
    print("StoreConnector DemoViewChangeModel");
    return Text("${vm.change}");
  },
  onInit: (store) {},
  onInitialBuild: (vm) {},
  onDidChange: (vm) {},
  onDispose: (store) {},
  distinct: true,
  rebuildOnChange: true,
),

class DemoViewChangeModel {
  bool change;

  DemoViewChangeModel({this.change});

  static DemoViewChangeModel fromStore(Store<AppState> store) {
    return DemoViewChangeModel(
      change: store.state.change,
    );
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is DemoViewChangeModel &&
          runtimeType == other.runtimeType &&
          change == other.change;

  @override
  int get hashCode => change.hashCode;
}

如何选择StoreBuilder和StoreConnector关键在于开发者如何使用。两者没有优劣之分,重要在于对组件是否以全局状态做管理还是个体独立只做局部刷新组件。

🚀完整代码看这里🚀

参考