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:
- user clicks the check box, GestureDetector's onTap will be called back
- 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
- 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
- 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, Contextctx, 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.
- passes the action to the component corresponding effect through onAction for processing
- 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
- 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<_VoidCallback> _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 StoreEnhancerapplyMiddleware (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