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.