How is Dispatch implemented in Fish Redux?

zero. Foreword

When we use fish-redux to build an application, the interface code (view) and event processing logic (reducer, effect) are completely decoupled. When the interface needs to process the event, the action is distributed to the corresponding event The processing logic is used for processing, and the distribution process is the dispatch described below. Through the content of this article, you can have a deeper understanding of how an action is distributed step by step.

1. Starting from the example

In order to better understand the dispatch process of action, let’s take the check event of a todo entry in todo_list_page as an example to see the delivery process of the event after clicking. It is easy to debug through the breakpoint You can find everything that happens when you click. The specific process is as follows:

  1. user clicks the check box, GestureDetector's onTap will be called back
  2. through the dispatch function passed in buildView to distribute the doneAction. It is found that the doneAction cannot be processed in the effect of todo_component, so it will The dispatch delivered to the pageStore continues to distribute. The dispatch of
  3. pageStore will deliver the action to the reducer for processing, so the _markDone corresponding to doneAction will be executed, the state will be cloned, and the state of the cloned state will be modified, and then this brand new The state returns to
  4. and the dispatch of the pageStore will notify all listeners. The _viewUpdater responsible for interface redrawing finds that the state has changed and notifies the interface to redraw and update

2. Dispatch implementation analysis

Dispatch borrows Elm in the implementation process. The definition of

Dispatch in fish-redux is as follows:

typedef Dispatch = void Function(Action action);

is essentially an action processing function, accepts an action, and then distributes the action. Below we use the source code to conduct a detailed analysis of the dispatch

buildView function in

1.component. The dispatch passed in is the dispatch in the mainCtx of the corresponding component. The relationship between

_mainCtx and componet is as follows:

component -> Component -> ComponentState -> _mainCtState -> The initialization of _dispatch

and _mainCtx is created by the createContext method of componet. Following the method, we see the initialization of dispatch

// redux_component/context.dart DefaultContext initialization method
 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));
 }
The dispatch in 

context is created through factors. Factors are actually the current component. When the factors create dispatch, the onAction function is passed in, as well as the dispatch of the context itself and the store. onAction is mainly for Effect processing. You can also see from

that at the end of context initialization, you also register your onAction package to the store broadcast, so that you can receive action broadcasts sent by others.

Component inherits from Logic

// redux_component/logic.dart
 @override
 Dispatch createDispatch(
 OnAction onAction, Context 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(onAction, ctx)(
 dispatch: (Action action) => dispatch(action),
 getState: () => ctx.state,
 )(parentDispatch);
 return dispatch;
 }
 static Middleware _applyOnAction(OnAction onAction, Context ctx) {
 return ({Dispatch dispatch, Get 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);
 };
 };
 };
 }
}

The logic distributed above can probably be represented by the above figure.

  1. passes the action to the component corresponding effect through onAction for processing
  2. When the effect cannot handle this action, and this action is not lifecycle-actions, and does not need to be interrupted, it will be broadcast to the current Page All the remaining effects
  3. finally continue to distribute the action to the store's dispatch (parentDispatch is actually store.dispatch)

2. The dispatch

in the store We can see the specific logic of the store's 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 _notifyListeners = listeners.toList(
 growable: false,
 );
 for (_VoidCallback listener in _notifyListeners) {
 listener();
 }
 notifyController.add(state);
 };
The dispatch process of 

store is relatively simple, mainly to call the reducer, and notify the listener after the processing is completed.

3.middleware

Page inherits from Component and adds a middleware mechanism. The redux part of fish-redux actually supports middleware. Middlewares can be assembled through StoreEnhancer and merged into the dispatch function of Store. The

middleware mechanism allows us to perform AOP processing on the state of redux through middleware. For example, the logMiddleware that comes with fish-redux can log the change of state and print out the value before and after the state change.

After the page is configured with middleware, the configured middleware will be passed in during the process of creating the pageStore. After passing in, the store's dispath will be enhanced and processed, and the middleware processing function will be connected to the dispatch.

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

So you can see here that when the enhancer is passed in, the job of createStore is proxied by the enhancer, and a store processed by the enhancer will be returned. When PageStore is created, the enhancer of the middleware is passed in.

// redux/apply_middleware.dart
StoreEnhancer applyMiddleware(List middleware) {
 return middleware == null || middleware.isEmpty
 ? null
 : (StoreCreator creator) => (T initState, Reducer reducer) {
 assert(middleware != null && middleware.isNotEmpty);
 final Store 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 middleware) => middleware(
 dispatch: (Action action) => store.dispatch(action),
 getState: store.getState,
 ))
 .fold(
 initialValue,
 (Dispatch previousValue,
 Dispatch Function(Dispatch) element) =>
 element(previousValue),
 );
 return store;
 };
}
The logic here of 

is actually to string all the middleware processing functions to the store dispatch, so that all the middleware processing functions will be called when the store dispatches. The following is the execution order of each processing function,

will be executed first in the dispatch D1 in the component, and then passed to the store's dispatch, and at this time the store's dispatch has been enhanced by the middleware, so the middleware processing function will be executed , And finally the original dispatch function D2 of the store will be executed.

III. Summary

Through the above content, now we can know how an action is sent step by step to the effect, reducer to process, we can also track state changes through middleware, and this scalability gives the framework It brings unlimited possibilities.

Author: Busy fish technology - Luke