- The core idea of Flux is centralized control, which enables all requests and changes to be sent only through action and distributed uniformly by dispatcher. The advantage is that the View can be kept highly concise, and it doesn’t need to care much about logic, just the incoming data. Centralization also controls all data, making it easy to query when problems occur.
- Flux also has obvious disadvantages, such as too many levels and the need to write too much redundant code repeatedly.
- Flux_GitHub address
A Flux application consists of four parts:
- Dispatcher handles action distribution and maintains dependencies between stores
- Store, which is responsible for storing data and handling data-related logic
- Action triggers the Dispatcher
- View, the View, is responsible for displaying the user interface
- As can be seen from the figure above, Flux is characterized by one-way data flow:
- The user initiates an Action object to the Dispatcher in the View layer
- The Dispatcher receives the Action and asks the Store to make changes accordingly
- Store makes the corresponding update and issues a change event
- The View updates the page after receiving the change event
Simple diagram comparing MVC
- Basic MVC data flow
- Complex MVC data flow
- Basic Flux data flow
- Complex Flux data flows
Compared with MVC mode, Flux has more arrows and ICONS, but there is a key difference: all the scissors point in the same direction, forming a closed loop in the whole system.
Evolution of patterns
- Flux is not so much a subversion of MVC pattern as an innovation of MVC pattern
- In the traditional MVC mode, the View directly modifies the Model in a very straightforward way, which is suitable for small Web applications. However, once there are multiple Models and views in a Web application, the decision relationship between the Model and View may become chaotic and difficult to control. And this pattern prevents the componentized separation of Model and View.
- Comparing the above two complex patterns, we find that the real pain point of the MVC pattern is the lack of an action abstraction related to user interaction
- From the code level, flux is nothing more than a common Event Dispatcher, whose purpose is to extract the controller code snippet in each View component of previous MVC and place it in a more appropriate place for centralized management. The development experience is comfortable, clean, and easy to navigate “one-way flow” mode, and under this scheduling mode, things become clear and predictable.
Flux source code
“shut up and show me the code”
The Dispatcher file code is mainly analyzed hereCopy the code
Dispatcher file initialization
var invariant = require('invariant');
export type DispatchToken = string;
var _prefix = 'ID_';
class Dispatcher<TPayload> {
_callbacks: {[key: DispatchToken]: (payload: TPayload) = > void};
_isDispatching: boolean;
_isHandled: {[key: DispatchToken]: boolean};
_isPending: {[key: DispatchToken]: boolean};
_lastID: number;
_pendingPayload: TPayload;
constructor() {
this._callbacks = {};
this._isDispatching = false;
this._isHandled = {};
this._isPending = {};
this._lastID = 1;
}
register(callback: (payload: TPayload) = > void): DispatchToken {
var id = _prefix + this._lastID++;
this._callbacks[id] = callback;
returnid; }... }Copy the code
- The code is truncated, here we register a DispatchToken function with the prefix ‘ID_’ and the increment ++ to make it unique (multiple ids for one store should be possible).
- Callbacks are a Dictionary of dispatchTokens and function callbacks.
- IsDispatching indicates whether the Dispatcher is in the Dispatch state.
- IsHandled, which uses tokens to check whether a function has been processed.
- IsPending, which uses tokens to check whether a function has been submitted to the Dispatcher.
- LastID, the UniqueID of the function body that was last added to Dispatcher.
- PendingPayload, the parameter that needs to be passed to the calling function.
Dispatch method
dispatch(payload: TPayload): void {
invariant(
!this._isDispatching,
'Dispatch.dispatch(...) : Cannot dispatch in the middle of a dispatch.'
);
this._startDispatching(payload);
try {
for(var id in this._callbacks) {
if (this._isPending[id]) {
continue;
}
this._invokeCallback(id);
}} finally {
this._stopDispatching();
}}
isDispatching(): boolean {
return this._isDispatching;
}
_invokeCallback(id: DispatchToken): void {
this._isPending[id] = true;
this._callbacks[id](this._pendingPayload);
this._isHandled[id] = true;
}
_startDispatching(payload: TPayload): void {
for(var id in this._callbacks) {
this._isPending[id] = false;
this._isHandled[id] = false;
}
this._pendingPayload = payload;
this._isDispatching = true;
}
_stopDispatching(): void {
delete this._pendingPayload;
this._isDispatching = false;
}
Copy the code
- We mainly judge whether the function is in the pending state, and execute the callback in the non-pending state through _invokeCallback. After all execution is finished, we restore the state through _stopDispatching.
- The _startDispatching function is describing how to delete all the state of the registered callback, and mark the state of the Dispatcher to the dispatching principle
- The _invokeCallback function is very simple; set its state to pending before the callback is actually called and set it to handled when it’s done
WaitFor method
waitFor(ids: Array<DispatchToken>): void {
invariant(
this._isDispatching,
'Dispatcher.waitFor(...) : Must be invoked while dispatching.'
);
for (var ii = 0; ii < ids.length; ii++) {
var id = ids[ii];
if (this._isPending[id]) {
invariant(
this._isHandled[id],
'Dispatcher.waitFor(...) : Circular dependency detected while ' +
'waiting for `%s`.',
id
);
continue;
}
invariant(
this._callbacks[id],
'Dispatcher.waitFor(...) : `%s` does not map to a registered callback.',
id
);
this._invokeCallback(id); }}Copy the code
- In a nutshell, the Dispatch method traverses callbacks simply and synchronously. When the waifFor method is encountered during the execution of the callback, the call to the current callback is interrupted, the wairFor method redetermines the traversal order based on the declared dependencies, and the aborted callback will not resume execution until all dependencies have been executed.
- Ps: The following paragraph is from a copy (for reference)
- First, a Invariant judges that currently he must be in a Dispatching state. In brief, if you are not in the Dispatching state, then no function is being executed at all. Then who are you waiting for?
- The function then iterates through the array of DispatchTokens and skips them temporarily if the DispatchToken is pending.
- However, there is A need to check the loop since, imagine the following situation, if A function from B’s Token, B function depends on A’s Token, will cause A “deadlock”. So, when a function’s dependent object is pending, the function has been started, but if it doesn’t get handled in the meantime, the function is stuck as well.
- Check whether the corresponding token callback exists and call the corresponding function of this token.
- Ps: Some of the pictures and instructions here are from a third party, but I can’t recall 😂