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的變化,這樣的擴展性給框架本身帶來無限可能。

作者:閑魚技術-盧克

娛樂分類資訊推薦

吳鎮宇16歲兒子吳費曼因變化太大登上熱搜,本人曬搞怪照:如果不喜歡現在的我可以離開 - 天天要聞

吳鎮宇16歲兒子吳費曼因變化太大登上熱搜,本人曬搞怪照:如果不喜歡現在的我可以離開

5月25日,演員吳鎮宇兒子吳費曼曬出多張照片分享生活,引發關注。照片中,費曼留着長發,偶爾對着鏡頭搞怪,十分隨性。吳費曼寫道:「如果不喜歡現在的我可以離開。不用嘗試殺掉現在的我,為了你們理想中的我。謝謝大家的評價,但還是別浪費口水在我這個人上了。」此前,有網友偶遇吳費曼,並曬出照片,隨後話題詞「16歲費...
毛偉傑突破瓶頸期,黃山還有戲!閻相闖和森老師馳援鯤城OR退役? - 天天要聞

毛偉傑突破瓶頸期,黃山還有戲!閻相闖和森老師馳援鯤城OR退役?

對於升班馬大連英博來說,間歇期非常寶貴。有傷的球員們抓緊時間恢復,沒傷的調整身心,球隊將進一步總結第一階段得失,演練技戰術。廖錦濤沒能入選國家隊,也是塞翁失馬,一方面這段時間給他累夠嗆,好好休息、恢復。另一方面,個人也沉澱和總結,未來還有很長的路要走。除了廖錦濤,英博多名球員努力提升,都有望成為未來...
「上海出品」《狂野時代》獲得第78屆戛納電影節主競賽單元「特別提及」殊榮 - 天天要聞

「上海出品」《狂野時代》獲得第78屆戛納電影節主競賽單元「特別提及」殊榮

「我和我可愛的演員們,還有所有主創,感謝戛納電影節,感謝評委,感謝那些在電影背後默默付出的人。」北京時間今天凌晨1時,由畢贛執導,易烊千璽、舒淇領銜主演的電影《狂野時代》摘得第78屆戛納電影節主競賽單元評審團特別獎,獲獎後畢贛導演如是說,手拿獎狀,他最後還特別感謝了家人的支持。三天前,由華策集團上海華...
「邕」抱「刀迷」!快來解鎖演唱會專屬寵粉福利 - 天天要聞

「邕」抱「刀迷」!快來解鎖演唱會專屬寵粉福利

熱點新聞2.5天休假模式真的來了,一地試行!女子菜市買菜獲「天價贈品」!價值4萬元……當廣西的山歌與刀郎的音樂碰撞,一場音樂盛宴激情上演。5月24日晚,「山歌響起的地方·刀郎2025巡迴演唱會」南寧站在廣西體育中心唱響,2025年南寧演唱會嘉年華系列活動也同步展開。來自全國各地的「刀迷」齊聚南寧這個「天下民歌眷戀...
中國影片亮相戛納獲好評,導演畢贛得特別獎 - 天天要聞

中國影片亮相戛納獲好評,導演畢贛得特別獎

第78屆戛納國際電影節24日晚在法國南部城市戛納閉幕,中國導演畢贛因執導《狂野時代》獲得戛納電影節特別獎。《狂野時代》由畢贛執導,易烊千璽、舒淇主演,是今年唯一入圍戛納電影節主競賽單元的華語片。電影講述了在一個人類已經不再做夢的世界裏,一個「怪物」依然整日沉迷於夢的幻覺中,只有一個女人能夠看清幻覺,並潛...
《碟中諜8:最終清算》來了,會是阿湯哥的終極一搏嗎? - 天天要聞

《碟中諜8:最終清算》來了,會是阿湯哥的終極一搏嗎?

距離《碟中諜7》上映已經過去兩年,5月30日,《碟中諜8:最終清算》將在中國內地上映。這一部延續了前作驚心動魄的劇情,由湯姆·克魯斯飾演的伊森·亨特將直面該系列最強敵人——無形無相卻掌控一切的AI智體。當人工智能的滅世陰謀悄然降臨,阿湯哥率領IMF特工小隊背水一戰,以凡人之軀抗衡擁有全球網絡控制權的致命對手。...
被導演嫌丑跑龍套13年!張藝謀當年一句話,如今捧出百花獎得主 - 天天要聞

被導演嫌丑跑龍套13年!張藝謀當年一句話,如今捧出百花獎得主

高葉,一個跑龍套跑了13年,因為一部戲差點丟了性命。卻在開播時抹去了名字,演戲拚命,打拚演技的一個配角。卻憑着一部《狂飆》爆火,並最終憑《第二十條》榮獲百花最佳女配角獎。本可以躺平,卻愛上了演戲高葉的家庭很富有,父母都是做生意的,她算是含着
成都:在問答之間,叩響未來之門 - 天天要聞

成都:在問答之間,叩響未來之門

愛因斯坦說,提出一個問題往往比解決一個問題更重要。因為那需要有創造性的想像力,而且標誌着科學的真正進步。那麼,當普通市民、學子直面科學家,他們好奇什麼?他們詢問什麼?他們又會獲得什麼?有個答案是肯定的。對好奇心與創造力的悉心呵護,定能讓科學的種子深深埋下。成都市昨日(5月24日)舉辦的「科學大講堂·我...