Home Flutter redux浅析
Post
Cancel

Flutter redux浅析

Redux中的几个概念

Store

顾名思义,Store是用来存储、管理全局的页面状态的。将页面UI与存储在Store中的状态绑定,通过修改Store的状态达到UI自动更新的效果。

如何创建一个Store:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//自定义的页面状态
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>T就是指存储的状态类型,如上面例子中的CountState

参数reducermiddleware,下面会提到。

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

Reducer

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

reducer具体定义:

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

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

如何创建一个reducer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

Middleware

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

middleware具体定义:

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

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

如何创建一个middlerware:

1
2
3
4
5
6
7
8
9
middleware(Store<CountState> store, dynamic action, NextDispatcher next) {

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

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

StoreProvider

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

如何使用StoreProvider:

1
2
3
4
5
6
7
8
9
class MyApp extends StatelessWidget {
 
    @override
  	Widget build(BuildContext context) {
        return StoreProvider<CountState>(store: store, 
                                         child: ...);
    }
 
}

StoreProvider<T>TStore中存储的状态类型。

StoreConnector

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

如何创建一个StoreConnector:

1
2
3
4
5
6
7
8
9
10
11
12
13
class _MyHomePageState extends State<MyHomePage> {

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

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

T->ViewModel要如何转换呢?

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

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

Dispatcher

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

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

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

Redux页面更新流程

基于上面的这些概念,Redux内部是如何运作的?

Flutter-Redux页面更新流程图

Redux的实现原理

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

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

Store的部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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的部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class StoreConnector<S, ViewModel> extends StatelessWidget {
    
 //...   
 
 StoreConnector({
    Key key,
    @required this.builder,
    @required 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);

  @override
  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源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class _StoreStreamListener<S, ViewModel> extends StatefulWidget {
	//...
	_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);
    

}

最后到了_StoreStreamListener,源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class _StoreStreamListenerState<S, ViewModel>
    extends State<_StoreStreamListener<S, ViewModel>> {
  Stream<ViewModel> stream;
  ViewModel latestValue;

  @override
  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);
    }));
  }

  @override
  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)变换过来的。

整理一下

  • 首先,dispatch action
  • reducer返回一个新的state, 并将它添加到store._changeController
  • StreamBuilderstream收到新的state之后,重新build

StreamBuilder本质上也是通过setState去更新UI的,有兴趣的话可以继续深入源码。

This post is licensed under CC BY 4.0 by the author.

make & cmake 基础

Dart中Iterable、Stream的糖语法