preface
Redux can be said to be a big Boss of front-end data flow control. There are numerous class libraries derived from Redux, which shows that just mastering the use of Redux is not enough. It is necessary to go deep into the source code and understand the details Source code, the source itself is not long, more is to experience the idea of flow management.
The source code for Redux is written in Ts. For those unfamiliar with Ts, check out the TypeScript website
use
As always, familiarize yourself with how Redux works before looking at the source code. Of course, you should go to Redux’s official website or Github repository to look at the documentation.
The first thing you see is the same sentence
Redux is a predictable state container for JavaScript apps.
Predictable is a key concept for Redux
In general,Redux data flow is very simple. One state, one action and one reducers are used to compile the above three and then connect them together through some built-in apis to generate a store, which can be explained through the official website π§
1. action
An action is an object that describes an event that can change state. The convention is to define a type attribute on the object
const THREEPM = {
type: "DrinkTea".payload: "xxxxx"};Copy the code
That’s it. I don’t care whether it’s a function that returns an object or an asynchronous action, whatever
2. state
State is also just an object that holds the state of the program without any constraints
const state = {
todo: []; }Copy the code
As simple as that, I don’t care whether it’s written in the reducers parameter list or with immuableJS
3. reducers
The main function of this guy is to describe what happens when the action is triggered. That is, update the state according to the action. The constraint is that you must return a new state every time and cannot change the original state
That is, (state, action) => newState;
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case "counter/incremented":
return { value: state.value + 1 };
case "counter/decremented":
return { value: state.value - 1 };
default:
returnstate; }}Copy the code
associated
It’s as simple as that, so how do we relate to them? Even easier, pass the Reducers into createStore and generate a store to manage all three
import { createStore } from "redux";
const store = createStore(reducers);
Copy the code
The generated store is the boss, responsible for the communication and management of the three younger brothers
According to the diagram at the beginning, the process must be as follows
store.dispatch(action) -> reducers handle -> update state
As for how to monitor more state, how to conduct asynchronous operation, how to disperse the state into several reducers, and how to merge the state of several reducers into the overall state, I will not repeat it here.
createStore
The usage section opens up an entry point for us, because it uses only one API,createStore, where the so-called dark magic happens.
Source index. Ts
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes,
};
Copy the code
Let’s take a look at the interface API exported by the entire library. There are only six interfaces for developers. As you can see, a strange thing has been mixed in: π§βοΈ, what is __DO__NOT__USE__ActionTypes? Are defined as follows
Source utils/actionTypes
const randomString = () = >
Math.random().toString(36).substring(7).split("").join(".");
const ActionTypes = {
INIT: `@@redux/INITThe ${/* #__PURE__ */ randomString()}`.REPLACE: `@@redux/REPLACEThe ${/* #__PURE__ */ randomString()}`.PROBE_UNKNOWN_ACTION: () = > `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`};export default ActionTypes;
Copy the code
These action types are mainly used for internal event identifiers. Let’s not use them.
Back to createStore
export default function createStore<
S.A extends Action.Ext = {},
StateExt = never>( reducer: Reducer<S, A>, enhancer? : StoreEnhancer<Ext, StateExt> ): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext;export default function createStore<
S.A extends Action.Ext = {},
StateExt = never>( reducer: Reducer<S, A>, preloadedState? : PreloadedState<S>, enhancer? : StoreEnhancer<Ext, StateExt> ): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext;export default function createStore<
S.A extends Action.Ext = {},
StateExt = never>( reducer: Reducer<S, A>, preloadedState? : PreloadedState<S> | StoreEnhancer<Ext, StateExt>, enhancer? : StoreEnhancer<Ext, StateExt> ): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext;Copy the code
CreateStore has three overloads that return the intersection of generic Store and Ext, but have different reducer parameters. Reducer is mandatory, and enhancer and preloadedState are available
Enhancer Optional enhancer, which is a third party plug-in interface, such as middleware enhancer, etc
PreloadedState Optional initialization state
CreateStore internal processes
Inside the function, first of all, javascript features, parameters, according to different parameters will be executed different steps
[with intensifier] :
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext;
Copy the code
[No enhancer] :
//1. Initialize internal attributes
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() = > void|) []null = []
let nextListeners = currentListeners
let isDispatching = false
//2. Internal method definition
/ / a little
//3. Dispatch an action type that is not allowed to be used
dispatch({ type: ActionTypes.INIT } as A)
//4. ηζstore
const store = {
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
return store
}
Copy the code
Dispatch, subscribe
Complete observer mode, SUBSCRIBE to notify listener, dispatch to Notify listener, just before dispatch to notify, currentState was passed to reducer to obtain the latest state
Dispatch from
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
Copy the code
The subscribe excerpts
nextListeners.push(listener);
Copy the code
There really is no dark magic π
Of course, the core of Redux is the predictability mentioned at the beginning, because both state and Reducer have two versions, one pre and one CUR, and reducer changes every time State is changed immediately and updated. It will not be changed uniformly after accumulation, which may not be the best performance, but everything is realistic and predictable, and the design idea is firm.
Enhancers and middleware
enhancer
Many people know that Redux has a middleware feature, but they don’t know that middleware is just one type of enhancers. Enhancers are higher-order functions that enhance store functionality and are optional in the createStore parameter
export type StoreEnhancer<Ext = {}, StateExt = never> = (
next: StoreEnhancerStoreCreator<Ext, StateExt>
) = > StoreEnhancerStoreCreator<Ext, StateExt>;
Copy the code
As you can see, the intensifier is receiving storeEnhancerStoreCreator type, the return value is also storeEnhancerStoreCreator type storeEnhancerStoreCreator types are defined as follows
export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <
S = any,
A extends Action = AnyAction
>(reducer: Reducer
, preloadedState? : PreloadedState
,>) = > Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext;
Copy the code
And storeEnhancerStoreCreator is receiving reducer and preloadedState as a parameter to return to the store, because if you have incoming call createStore enhancer, implement enhancer and createStore as reference The number of incoming.
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext;
Copy the code
Middleware enhancer
Fake handles aside,Redux comes with a familiar enhancer, applyMiddleware, in use
import { applyMiddleware } from "redux";
const middleware = [thunk];
conststore = createStore(reducer, applyMiddleware(... middleware));Copy the code
ApplyMiddleware has a number of type overloads, all of which take several middleware as arguments and return a storeEnhancer, only one of which is intercepted here
export default function applyMiddleware(. middlewares: Middleware[]) :StoreEnhancer<any>;
Copy the code
Since it is passed to createStore first as an enhancer, the return value is naturally an enhancer
The core code is as follows:
const store = createStore(reducer, preloadedState);
let dispatch: Dispatch = () = > {
throw new Error(
"Dispatching while constructing your middleware is not allowed. " +
"Other middleware would not be applied to this dispatch."
);
};
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ... args) = >dispatch(action, ... args), };const chain = middlewares.map((middleware) = > middleware(middlewareAPI));
dispatch = compose<typeofdispatch>(... chain)(store.dispatch);return {
...store,
dispatch,
};
Copy the code
- To create a store
- Write the API exposed to the middleware in the middlewareAPI object and pass it to each middleware
- call
compose
Combined total middleware
Compose: compose(f, g, h) compose(f, G, h) args) => f(g(h(… Args))), for more information in the middleware section of the official website
The end of the
The above is the interpretation of Redux core code, my ability is limited, it is hard to avoid mistakes, please forgive me!
In general, the internal implementation of Redux is simple and extensible, which is probably why there are so many libraries on it. I will also analyze the source code of several redux-related libraries, such as Redux-Thunk, Redux-Saga, etc
Thanks for reading π