Fish Redux中的Dispatch是怎么实现的?

2021年01月01日13:30:22 娱乐 1551

零.前言

我们在使用fish-redux构建应用的时候,界面代码(view)和事件的处理逻辑(reducer,effect)是完全解耦的,界面需要处理事件的时候将action分发给对应的事件处理逻辑去进行处理,而这个分发的过程就是下面要讲的dispatch, 通过本篇的内容,你可以更深刻的理解一个action是如何一步步去进行分发的。

一.从example开始

为了更好的理解action的dispatch过程,我们就先以todo_list_page中一条todo条目的勾选事件为例,来看点击后事件的传递过程,通过断点debug我们很容易就能够发现点击时候发生的一切,具体过程如下:

  1. 用户点击勾选框,GestureDetector的onTap会被回调
  2. 通过buildView传入的dispatch函数对doneAction进行分发,发现todo_component的effect中无法处理此doneAction,所以将其交给pageStore的dispatch继续进行分发
  3. pageStore的dispatch会将action交给reducer进行处理,故doneAction对应的_markDone会被执行,对state进行clone,并修改clone后的state的状态,然后将这个全新的state返回
  4. 然后pageStore的dispatch会通知所有的listeners,其中负责界面重绘的_viewUpdater发现state发生变化,通知界面进行重绘更新

二.Dispatch实现分析

Dispatch在实现的过程中借鉴了Elm。

Dispatch在fish-redux中的定义如下

typedef Dispatch = void Function(Action action);

本质上就是一个action的处理函数,接受一个action,然后对action进行分发。

下面我门通过源码来进行详细的分析

1.component中的dispatch

buildView函数传入的dispatch是对应的component的mainCtx中的dispatch,

_mainCtx和componet的关系如下

component -> ComponentWidget -> ComponentState -> _mainCtx -> _dispatch

而 _mainCtx的初始化则是通过componet的createContext方法来创建的,顺着方法下去我们看到了dispatch的初始化

// redux_component/context.dart DefaultContext初始化方法
 DefaultContext({
 @required this.factors,
 @required this.store,
 @required BuildContext buildContext,
 @required this.getState,
 }) : assert(factors != null),
 assert(store != null),
 assert(buildContext != null),
 assert(getState != null),
 _buildContext = buildContext {
 final OnAction onAction = factors.createHandlerOnAction(this);
 /// create Dispatch
 _dispatch = factors.createDispatch(onAction, this, store.dispatch);
 /// Register inter-component broadcast
 _onBroadcast =
 factors.createHandlerOnBroadcast(onAction, this, store.dispatch);
 registerOnDisposed(store.registerReceiver(_onBroadcast));
 }

context中的dispatch是通过factors来进行创建的,factors其实就是当前component,factors创建dispatch的时候传入了onAction函数,以及context自己和store的dispatch。onAction主要是进行Effect处理。

这边还可以看到,进行context初始化的最后,还将自己的onAction包装注册到store的广播中去,这样就可以接收到别人发出的action广播。

Component继承自Logic

// redux_component/logic.dart
 @override
 Dispatch createDispatch(
 OnAction onAction, Context<T> ctx, Dispatch parentDispatch) {
 Dispatch dispatch = (Action action) {
 throw Exception(
 'Dispatching while appending your effect & onError to dispatch is not allowed.');
 };
 /// attach to store.dispatch
 dispatch = _applyOnAction<T>(onAction, ctx)(
 dispatch: (Action action) => dispatch(action),
 getState: () => ctx.state,
 )(parentDispatch);
 return dispatch;
 }
 static Middleware<T> _applyOnAction<T>(OnAction onAction, Context<T> ctx) {
 return ({Dispatch dispatch, Get<T> getState}) {
 return (Dispatch next) {
 return (Action action) {
 final Object result = onAction?.call(action);
 if (result != null && result != false) {
 return;
 }
 //skip-lifecycle-actions
 if (action.type is Lifecycle) {
 return;
 }
 if (!shouldBeInterruptedBeforeReducer(action)) {
 ctx.pageBroadcast(action);
 }
 next(action);
 };
 };
 };
 }
}

Fish Redux中的Dispatch是怎么实现的? - 天天要闻

上面分发的逻辑大概可以通过上图来表示

  1. 通过onAction将action交给component对应的effect进行处理
  2. 当effect无法处理此action,且此action非lifecycle-actions,且不需中断则广播给当前Page的其余所有effects
  3. 最后就是继续将action分发给store的dispatch(parentDispatch传入的其实就是store.dispatch)

2. store中的dispatch

从store的创建代码我们可以看到store的dispatch的具体逻辑

// redux/create_store.dart
 final Dispatch dispatch = (Action action) {
 _throwIfNot(action != null, 'Expected the action to be non-null value.');
 _throwIfNot(
 action.type != null, 'Expected the action.type to be non-null value.');
 _throwIfNot(!isDispatching, 'Reducers may not dispatch actions.');
 try {
 isDispatching = true;
 state = reducer(state, action);
 } finally {
 isDispatching = false;
 }
 final List<_VoidCallback> _notifyListeners = listeners.toList(
 growable: false,
 );
 for (_VoidCallback listener in _notifyListeners) {
 listener();
 }
 notifyController.add(state);
 };

store的dispatch过程比较简单,主要就是进行reducer的调用,处理完成后通知监听者。

3.middleware

Page继承自Component,增加了middleware机制,fish-redux的redux部分本身其实就对middleware做了支持,可以通过StoreEnhancer的方式将middlewares进行组装,合并到Store的dispatch函数中。

middleware机制可以允许我们通过中间件的方式对redux的state做AOP处理,比如fish-redux自带的logMiddleware,可以对state的变化进行log,分别打印出state变化前和变化后的值。

当Page配置了middleware之后,在创建pageStore的过程中会将配置的middleware传入,传入之后会对store的dispath进行增强加工,将middleware的处理函数串联到dispatch中。

// redux_component/component.dart
 Widget buildPage(P param) {
 return wrapper(_PageWidget<T>(
 component: this,
 storeBuilder: () => createPageStore<T>(
 initState(param),
 reducer,
 applyMiddleware<T>(buildMiddleware(middleware)),
 ),
 ));
 }
// redux_component/page_store.dart
PageStore<T> createPageStore<T>(T preloadedState, Reducer<T> reducer,
 [StoreEnhancer<T> enhancer]) =>
 _PageStore<T>(createStore(preloadedState, reducer, enhancer));
// redux/create_store.dart
Store<T> createStore<T>(T preloadedState, Reducer<T> reducer,
 [StoreEnhancer<T> enhancer]) =>
 enhancer != null
 ? enhancer(_createStore)(preloadedState, reducer)
 : _createStore(preloadedState, reducer);

所以这里可以看到,当传入enhancer时,createStore的工作被enhancer代理了,会返回一个经过enhancer处理过的store。而PageStore创建的时候传入的是中间件的enhancer。

// redux/apply_middleware.dart
StoreEnhancer<T> applyMiddleware<T>(List<Middleware<T>> middleware) {
 return middleware == null || middleware.isEmpty
 ? null
 : (StoreCreator<T> creator) => (T initState, Reducer<T> reducer) {
 assert(middleware != null && middleware.isNotEmpty);
 final Store<T> store = creator(initState, reducer);
 final Dispatch initialValue = store.dispatch;
 store.dispatch = (Action action) {
 throw Exception(
 'Dispatching while constructing your middleware is not allowed. '
 'Other middleware would not be applied to this dispatch.');
 };
 store.dispatch = middleware
 .map((Middleware<T> middleware) => middleware(
 dispatch: (Action action) => store.dispatch(action),
 getState: store.getState,
 ))
 .fold(
 initialValue,
 (Dispatch previousValue,
 Dispatch Function(Dispatch) element) =>
 element(previousValue),
 );
 return store;
 };
}

这里的逻辑其实就是将所有的middleware的处理函数都串到store的dispatch,这样当store进行dispatch的时候所有的中间件的处理函数也会被调用。

下面为各个处理函数的执行顺序,

Fish Redux中的Dispatch是怎么实现的? - 天天要闻

首先还是component中的dispatch D1 会被执行,然后传递给store的dispatch,而此时store的dispatch已经经过中间件的增强,所以会执行中间件的处理函数,最终store的原始dispatch函数D2会被执行。

三.总结

通过上面的内容,现在我们可以知道一个action是如何一步步的派送给effect,reducer去进行处理的,我们也可以通过middleware的方式去跟踪state的变化,这样的扩展性给框架本身带来无限可能。

作者:闲鱼技术-卢克

娱乐分类资讯推荐

张译《以法之名》结局烂尾,演员到底有没有“免死金牌”? - 天天要闻

张译《以法之名》结局烂尾,演员到底有没有“免死金牌”?

《以法之名》终于大结局,憋屈到最后一秒。查到最后洪亮发现全家都是狼,万海也死得莫名其妙,还因为烂尾上了热搜。导演发微博承认不足,说演员选择失误正在反省中,这是在说谁呢?剧烂尾编剧导演锅更大,但刚开播时张译演的检察官就不是所有人买账,有观众说张译怎么又来查案,有点腻了。《狂飙》才播出2年,按理说“安欣...
同样演“纨绔子弟”,把章煜奇与吴刚儿子放在一起,差距就出来了 - 天天要闻

同样演“纨绔子弟”,把章煜奇与吴刚儿子放在一起,差距就出来了

扫黑剧中的“纨绔子弟”,向来是块难啃的骨头。演得太浮,像贴了层“嚣张”的标签,演得太沉,又失了那份不谙世事的骄纵。可偏偏有人能把这类角色,演得像是从旧时光里走出来的“真少爷”。当然,也有人用力过猛,成为全剧晃眼的“假靶子”。《以法之名》的章煜奇与《狂飙》中吴刚的儿子,分别演绎出不同的效果。把两人放在...
《以法之名》大结局,欠乔振兴一个追悼会 - 天天要闻

《以法之名》大结局,欠乔振兴一个追悼会

《以法之名》最后四集拍得太仓促了,结局戛然而止。最后一幕,功过相抵的洪亮继续留在十一部任职,也终于被其他同事认可,这让他受宠若惊。可是最后不仅没看到犯人落网后接受审判的样子,很多人很多事也都欠一个交代。
尴尬!特朗普再现“雷人雷语” - 天天要闻

尴尬!特朗普再现“雷人雷语”

当地时间9日,美国总统特朗普在白宫接待了来自加蓬、几内亚比绍、利比里亚、毛里塔尼亚和塞内加尔的非洲五国领导人。 但是在频繁表示对非洲大陆兴趣的同时,特朗普的种种举动却又在时刻展现他对非洲的“漠视”。 据多家外媒报道,在9日会晤开始前,特朗普致欢迎辞。随后,他请毛里塔尼亚总统加兹瓦尼发言,并称后者为“杰出...
62岁歌手黄安回应“退圈出家”:虽不是真的,也有那个意思;曾2次病危,交代遗言清点遗产 - 天天要闻

62岁歌手黄安回应“退圈出家”:虽不是真的,也有那个意思;曾2次病危,交代遗言清点遗产

近日,歌手黄安发文回应“全面退出娱乐圈并出家”传闻,他说虽然不是真的,“但也有那个意思”,并坦言自己已经不再关注娱乐圈、不在意自己歌曲的排名等情况。此前黄安称自己过去30年来曾两度面临病危,更谈到死后的安排。去年,黄安通过微博晒出了多张医院诊断报告,并发文表示:“这是我每天要吃的‘解药’,吃了不会痊愈...
连续2天逆跌,票房会破3.5亿!陈思诚还追不上,国产片靠姜文救场 - 天天要闻

连续2天逆跌,票房会破3.5亿!陈思诚还追不上,国产片靠姜文救场

没想到《侏罗纪世界重生》也救不了暑期档。7月10号内地电影市场异常冷清,全市场只有《侏罗纪世界重生》和《恶意》日票房破千万,这种情况下姜文的《你行你上》正如片名一样有种,直接提档到7月18号首映。或许会给暑期档带来惊喜。连续2天逆跌,破3.5亿?陈思诚还追不上,好莱坞的暑期档冠军稳了!截止7月10号23点,内地电...
新疆偶遇森林北,穿着简单身材很好,本人又瘦又美!汪峰有眼光 - 天天要闻

新疆偶遇森林北,穿着简单身材很好,本人又瘦又美!汪峰有眼光

声明:本文内容均引用权威资料结合个人观点进行撰写,请知悉。在阅读此文之前,麻烦您点击一下“关注”,既方便您进行讨论和分享,又能给您带来不一样的参与感,感谢您的支持。近日,有网友在新疆偶遇了汪峰女友“森林北”。这位一向低调的女子,穿着简约的走