Pride's Blog

Flutter Redux解析

· Pride

有的大的背景前提,在公司做的业务开发用的是Hummer,Hummer并没有提供全局状态管理的工具类,在业务开发中,会产生很多逻辑附带数据的操作,为了避免业务逻辑与数据逻辑交织变得复杂,于是便有了移植类似的功能去Hummer上的想法;在移植前须知其工作原理,就从Flutter Redux入手了。

一、概念§

1.Store§

用来储存、管理全局的页面状态,将页面UI与存储在Store中的状态进行绑定,通过修改Store的状态达到UI自动更新的能力。

/// 创建一个Store
class CountState {
  int count;

  CountState(this.count);

  factory CountState.init() {
    return CountState(0);
  }
}

final store = Store<CountState>(countReducer,
    initialState: CountState.init(),
    middleware: [middleware]);

Store中T就是指存储的状态类型,如上面例子中的CountState。

参数reducer、middleware,下面会提到。

参数initialState,去设置初始的状态。控制页面加载出来时的默认显示内容。

2.Reducer§

reducer本质上是一个函数。它会根据不同的动作指令,去修改调整状态值。

reducer具体定义:

typedef State Reducer<State>(State state, dynamic action);

其中,state是Store中存储的状态,action是特定的动作指令,最后返回一个更新后的状态。

/// 如何创建一个reducer
enum Action {
  INCREMENT,
  DECREMENT
}

CountState countReducer(CountState state, dynamic action) {
  if (action == Action.INCREMENT) {
    state.count += 1;
  }

  if (action == Action.DECREMENT) {
    state.count -= 1;
  }
  return state;
}

3.Middleware§

中间件,作用域位于reducer更新状态之前。本质上也是一个函数。

具体定义:

typedef void Middleware<State>(Store<State> store,dynamic action,NextDispatcher next);

其中,前两个参数与reducer参数一致。next是调用下一个中间件的函数。

middleware(Store<CountState> store, dynamic action, NextDispatcher next) {

  if (action == Action.INCREMENT || action == Action.DECREMENT) {
	...
  }

  //调用下一个中间件
  next(action);
}

4.StoreProvider§

Store的提供者,本质是Widget。一般包裹在根部Widget, 给整个Widget Tree 提供Store。

如何使用StoreProvider:

class MyApp extends StatelessWidget {
 
    
  	Widget build(BuildContext context) {
        return StoreProvider<CountState>(store: store, 
                                         child: ...);
    }
 
}

StoreProvider中T指Store中存储的状态类型。

5.StoreConnector§

StoreConnector也是一个Widget,通过它可以连接到StoreProvider存储的状态。用它来包裹局部Widget, 并将Store.state与Widget绑定。

如何创建一个StoreConnector:

class _MyHomePageState extends State<MyHomePage> {

  
  Widget build(BuildContext context) {
 
    return StoreConnector<CountState, int>(
      converter: (state) => state.state.count,
      builder: (context, count) {
        return //...;
      },
    );
  }
}

StoreConnector<T,ViewModel>中T指Store中存储的状态类型,ViewModel指本Widget需要的状态类型(上面的例子中int就是需要的状态类型)。

T->ViewModel要如何转换呢?

在创建StoreConnector时需要一个入参converter, 它就是负责这个转换逻辑的。它也是一个函数,具体定义:

typedef StoreConverter<S, ViewModel> = ViewModel Function(
  Store<S> store,
);
final StoreConverter<S, ViewModel> converter;

6.Dispatcher§

如何通知状态更新呢?通过store.dispatch:

StoreProvider.of<CountState>(context).dispatch(Action.INCREMENT);
//或者如果能拿到 Store 的话
store.dispatch(Action.INCREMENT)

StoreProvider.of(context) 返回Store,T指状态类型,context是Widget build时的上下文。

二、Redux页面更新流程§

flutter_redux

三、实现原理§

Redux将state与页面UI绑定,通过更新State来更新页面。若页面有多处UI与同一状态值有关联,修改状态能够达到牵一发而动全身的效果。越是复杂的页面,越能体现出Redux的优势。

那Redux是如何实现UI自更新的呢?是通过常用的setState方法吗?dispatch action会导致所有组件都刷新吗?

///Store的部分源码
class Store<State> {
  Reducer<State> reducer;
  //通过它来传递状态的
  final StreamController<State> _changeController;
  State _state;
  List<NextDispatcher> _dispatchers;

  Store(
    this.reducer, {
    State initialState,
    List<Middleware<State>> middleware = const [],//中间件
    bool syncStream: false,
    bool distinct: false,//若是true,`reducer`返回的state与current state相等的话,流程就停止。见方法_createReduceAndNotify
  })
      : _changeController = new StreamController.broadcast(sync: syncStream) {
    _state = initialState;
    _dispatchers = _createDispatchers(
      middleware,
      _createReduceAndNotify(distinct),
    );//这里可以看出,是middleware先运行,最后是reducer
  }
  
  Stream<State> get onChange => _changeController.stream;
    
  //将reducer包装成NextDispatcher
  NextDispatcher _createReduceAndNotify(bool distinct) {
    return (dynamic action) {
      final state = reducer(_state, action);
	  //distinct为true且前后状态一样,直接return
      if (distinct && state == _state) return;

      _state = state;
      //将新的状态添加到stream中
      _changeController.add(state);
    };
  }
    
  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();
  }
  
  //dispach action之后,依次运行middleware,最后运行reducer
  void dispatch(dynamic action) {
    _dispatchers[0](action);
  }
    
}

在发起store.dispatch后,会依次运行middleware,最后运行reducer。并且,会将new state添加到_changeController中。Store的工作到这里就结束了,好像看不出是哪里通知了子视图刷新的。往下看StoreConnector.

///StoreConnector的部分源码

class StoreConnector<S, ViewModel> extends StatelessWidget {
    
 //...   
 
 StoreConnector({
    Key key,
     this.builder,
     this.converter,
    this.distinct = false,
    this.onInit,
    this.onDispose,
    this.rebuildOnChange = true,
    this.ignoreChange,
    this.onWillChange,
    this.onDidChange,
    this.onInitialBuild,
  })  : assert(builder != null),
        assert(converter != null),
        super(key: key);

  
  Widget build(BuildContext context) {
    return _StoreStreamListener<S, ViewModel>(
      store: StoreProvider.of<S>(context),
      builder: builder,
      converter: converter,
      distinct: distinct,
      onInit: onInit,
      onDispose: onDispose,
      rebuildOnChange: rebuildOnChange,
      ignoreChange: ignoreChange,
      onWillChange: onWillChange,
      onDidChange: onDidChange,
      onInitialBuild: onInitialBuild,
    );
  }
}

/// _StoreStreamListener源码

class _StoreStreamListener<S, ViewModel> extends StatefulWidget {
	//...
	_StoreStreamListener({
        Key key,
         this.builder,
         this.store,
         this.converter,
        this.distinct = false,
        this.onInit,
        this.onDispose,
        this.rebuildOnChange = true,
        this.ignoreChange,
        this.onWillChange,
        this.onDidChange,
        this.onInitialBuild,
      }) : super(key: key);
}
/// 最后到了_StoreStreamListener,源码
class _StoreStreamListenerState<S, ViewModel>
    extends State<_StoreStreamListener<S, ViewModel>> {
  Stream<ViewModel> stream;
  ViewModel latestValue;

  
  void initState() {
    _init();

    super.initState();
  }

  void _init() {
    if (widget.onInit != null) {
      widget.onInit(widget.store);
    }
	//将原state转成需要使用的状态类型
    latestValue = widget.converter(widget.store);

    if (widget.onInitialBuild != null) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        widget.onInitialBuild(latestValue);
      });
    }
	//这就是Store中_changeController的流。跟Store的状态联系了起来。
    var _stream = widget.store.onChange;

    //将状态流经过一系列的变形过滤
    if (widget.ignoreChange != null) {
      _stream = _stream.where((state) => !widget.ignoreChange(state));
    }

    stream = _stream.map((_) => widget.converter(widget.store));

    //如果设置了distinct标识符,若前后两转换值一致的话,就丢弃。通过它可达到组件只关注与之有关的状态变化。不用任何的状态过来都去刷新。
    if (widget.distinct) {
      stream = stream.where((vm) {
        final isDistinct = vm != latestValue;

        return isDistinct;
      });
    }

    stream =
        stream.transform(StreamTransformer.fromHandlers(handleData: (vm, sink) {
      latestValue = vm;

      if (widget.onWillChange != null) {
        widget.onWillChange(latestValue);
      }

      if (widget.onDidChange != null) {
        WidgetsBinding.instance.addPostFrameCallback((_) {
          widget.onDidChange(latestValue);
        });
      }

      sink.add(vm);
    }));
  }

  
  Widget build(BuildContext context) {
     //StoreConnector.rebuildOnChange的默认值是true。所以,最后是使用了StreamBuilder包裹子Widget,且将stream传入其中。
    return widget..rebuildOnChange
        ? StreamBuilder<ViewModel>(
            stream: stream,
            builder: (context, snapshot) => widget.builder(
                  context,
                  snapshot.hasData ? snapshot.data : latestValue,
                ),
          )
        : widget.builder(context, latestValue);
  }
}

StreamBuilder接收一个Stream,一旦Stream中有新的状态过来,就会重新build返回新的widget。而这个Stream是经过store.onchange(store._changeController.stream)变换过来的。