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
  1. To create a store
  2. Write the API exposed to the middleware in the middlewareAPI object and pass it to each middleware
  3. callcomposeCombined 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 🌈