The original article is on my Github

Why write this article

Spare time I also see a lot of excellent source of open source projects such as the react, redux, vuex, vue, Babel, ant – design, etc., but few are systematically summarized, knowledge is very limited, so I always wanted to write an article on the complete source code read.

The second reason is that in the recent interview process, many candidates have a shallow understanding of Redux, or even a wrong understanding. It’s nice to have someone who really understands Redux’s ideas, not to mention the subtleties of its design.

Hence the birth of this article.

What is the story

Now, before we dive into redux, let’s first look at what redux is and what problems it solves.

Here’s redux’s official explanation:

Redux is a predictable state container for JavaScript apps.

The above concepts are abstract and difficult to understand if you don’t know anything about Redux.

A more accessible explanation (also redux’s official explanation) :

Redux is an implementation of the Flux architecture, inspired by Elm

First, there are two names, Flux and Elm.

flux

Here’s facebook’s official explanation for Flux:

Application Architecture for Building User Interfaces

To be more specific:

An application architecture for React utilizing a unidirectional data flow.

Flux is a data management framework introduced along with React, and its core idea is a single data stream.

A picture is worth a thousand words. Let’s use the picture to understand Flux

View is built through React, and React is data-driven, so solving the data problem solves the problem of view. Data is managed through flux architecture, making the data predictable. This makes the view predictable. Very good ~

Elm

Elm is a language that compiles code to javaScript and is characterized by strong performance and no runtime exceptions. Elm also has a virtual DOM implementation.

The core concept of Elm is to use Model to build the application, that is, Model is the core of the application. Building an application is all about building the Model, how to update the Model, and how to build the mapping of the Model to the View.

More about ELM

After all this, you’ll see that Redux’s job is to manage data. Redux’s data flow can be illustrated with the following diagram:

The core of Redux is a single state. State is stored in the Redux Store in the form of closures that are guaranteed to be read-only. If you want to change state, you can only do so by sending an Action, which is essentially a normal object.

Your app can subscribe to state changes using the subscribe method exposed by Redux. If you use Redux in the React app, it behaves as a react subscribe store change and re-render the view.

The final issue is how to update the view according to the action, and this part is business related. Redux updates state via reducer, which I’ll talk about in more detail later.

The design is very elegant and we will explain it later.

Minimize implementation of REDUX

It’s not hard to write a redux. The redux source code is only about 200 lines. It uses a lot of higher-order functions, closures, function combinations and other knowledge. Make your code look shorter and more structured.

Let’s write a “redux”

implementation

The redux we want to implement mainly has the following functions:

  • Obtaining the application State
  • Send the action
  • Listen for state changes

Let’s take a look at the REdux Store API leak

const store = {
  state: {}, // The global unique state, the internal variable, is obtained by getState()
  listeners: [], // Listeners, for operations such as view updates
  dispatch: (a)= > {}, / / distribution of the action
  subscribe: (a)= > {}, // To subscribe to the state change
  getState: (a)= > {}, / / for the state
}
Copy the code

So let’s implement createStore, which returns the store object, and the store object structure is already written up here. CreateStore is used to initialize the Redux Store and is redux’s most important API. Let’s do it:

createStore

const createStore = (reducer, initialState) = > {
  // internal variables
  const store = {};
  store.state = initialState;
  store.listeners = [];
  
  // api-subscribe
  store.subscribe = (listener) = > {
    store.listeners.push(listener);
  };
  // api-dispatch
  store.dispatch = (action) = > {
    store.state = reducer(store.state, action);
    store.listeners.forEach(listener= > listener());
  };
  
  // api-getState
  store.getState = (a)= > store.state;
  
  return store;
};

Copy the code

Isn’t it surprising that the basic functionality of Redux has been implemented in the 20 or so lines above? So let’s try it out.

use

We can now use our “redux” just like we use our “redux”.

The following examples are taken from the official website

You can copy the following script to the console with the “redux” we implemented above to see how it works. Is it consistent with the official result of Redux?

// reducer
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1
  case 'DECREMENT':
    return state - 1
  default:
    return state
  }
}

let store = createStore(counter)

store.subscribe((a)= >
  console.log(store.getState())
)


store.dispatch({ type: 'INCREMENT' })
/ / 1
store.dispatch({ type: 'INCREMENT' })
/ / 2
store.dispatch({ type: 'DECREMENT' })
/ / 1
Copy the code

You can see that we’ve done the most basic functionality of Redux. If you need to update the view, just update it according to the subscribe we revealed. This explains why Redux is not specifically used for React and why there is react-Redux inventory.

I’ve left out the implementation of applyMiddleware to make it easier for people in different stages to read it, but don’t worry, I’ll explain it in the redux core ideas section below.

REDUX core idea

Redux’s core idea goes beyond that. Personally, I think there are two things that need special attention. One is reducer and the other is Middlewares

Reducer and reduce

Reducer can be said to be the essence of redux. Let’s look at it first. Reducer is required to be a pure function.

  • This is critical because reducer is not a thing defined in redux. It’s a method that the user sends in.
  • The reducer should be a pure function while the state is predictable. Look for the reducer to be a predictable state container for JavaScript apps while the task goes predictable.

In daily work, we also use the Reduce function, which is a high order function. Reduce has always been a very important concept in the field of computing.

Reducer and Reduce have very similar names. Is this a coincidence?

Let’s first look at the reducer function signature:

fucntion reducer(state, action) {
    const nextState = {};
    // xxx
    return nextState;
}
Copy the code

Take a look at the reduce function signature

[].reduce((state, action) = > {
    const nextState = {};
    // xxx
    return nextState;
}, initialState)
Copy the code

You can see that they’re almost identical. The main difference is that reduce requires an array and then accumulates the changes. The Reducer does not have such an array.

To be more precise, the change in the cumulative time of reduce is the change in the cumulative space of reduce.

How do I understand the reducer is a change in cumulative time?

Every time we call dispatch(action), we call the Reducer and update the return value of the Reducer to store.state.

Each dispatch process is actually a push(action) process in space, like this:

[action1, action2, action3].reduce((state, action) = > {
    const nextState = {};
    // xxx
    return nextState;
}, initialState)

Copy the code

Therefore, reducer is actually a time accumulative operation based on space and time.

middlewares

We don’t have much to say about middleware, but check out more information here.

Here’s what middleware can do:

store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}
Copy the code

The above code prints information before and after dispatch, which is the simplest middleware implementation. If you add compose, you can execute multiple middleware sequentially.

Redux applyMiddleware redux applyMiddleware redux applyMiddleware redux applyMiddleware redux applyMiddleware redux applyMiddleware redux applyMiddleware

// Use reduce for compose.
function compose(. funcs) {
  if (funcs.length === 0) {
    return arg= > arg
  }

  if (funcs.length === 1) {
    return funcs[0]}return funcs.reduce((a, b) = >(... args) => a(b(... args))) }// applyMiddleware source code
function applyMiddleware(. middlewares) {
  return createStore= >(... args) => {conststore = createStore(... args)let dispatch = (a)= > null;
    let chain = [];

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (. args) = >dispatch(... args) } chain = middlewares.map(middleware= > middleware(middlewareAPI))
    // Make middlewares a function
    // Middlewares from front to backdispatch = compose(... chain)(store.dispatch)return {
      ...store,
      dispatch
    }
  }
}

/ / use
let store = createStore(
  todoApp,
  // applyMiddleware() tells createStore() how to handle middleware
  applyMiddleware(logger, dispatchAndLog)
)
Copy the code

Redux’s source code for Middleware is as simple as that. But it takes a bit of thought to fully understand it.

Redux first generates an original store(not enhanced) through the createStore, and then finally rewrites the dispatch of the original store, inserting middleware logic between calls to the native Reducer (the middleware chain is executed sequentially). The code is as follows:

function applyMiddleware(. middlewares) {
  return createStore= >(... args) => {conststore = createStore(... args)// let dispatch = xxxxx;
    return {
      ...store,
      dispatch
    }
  }
}
Copy the code

Compose: compose(f, g, h) : compose(f, g, h) : compose(f, g, h) :

function(. args) { f(g(h(... args))) }Copy the code

So a chain might look something like this:

chain = [
  function middleware1(next) {
    GetState and Dispath are internally accessible through closures

  },
  function middleware2(next) {
    GetState and Dispath are internally accessible through closures},... ]Copy the code

With the above concept of compose, we can see that each middleware input is a function with the parameter next, and the first middleware to access next is actually the native Store dispatch. For example: dispatch = compose(… Chain) (store. Dispatch). Starting with the second middleware, the next is actually the action => retureValue returned by the previous middleware. Did you notice that the function signature is the function signature of Dispatch?

Output is a function that takes action, and the returned function is signed action => retureValue to be used as the next middleware. This lets middleware selectively call the next middleware(next).

There’s a lot of Redux Middleware in the community, and the classic one is Redux Thunk, written by Dan himself. The core code is only two lines. And this is where Redux gets really good. Based on redux’s excellent design, there are many excellent third party redux mid-point in the community, such as redux-dev-tool, redux-log, redux-Promise, and so on. I would like to make a redux Thunk resolution if I have the opportunity.

conclusion

This article focuses on what a redux is and what it does. I then implemented a minimal redux in less than 20 lines of code. Finally, the core design reducer and Middlewares of Redux are explained in depth.

Some of redux’s classic resources are getting Started with Redux and You Might Not Need Redux. Learning it will help you understand Redux and how to use redux to manage application state.