title: Hands-on Implementation of a Simple Redux (Part 1)
date: 2018-02-26 10:14:41
banner: https://cdn.statically.io/gh/YanYuanFE/picx-images-hosting@master/20231128/photo-1432821596592-e2c18b78144f.f3ca3jsspa0.webp
tags:
- Redux
- React
Understanding the principles of Redux helps us to use it better. This article implements the basic API of Redux.
Redux aims to make state changes predictable.
Redux is being used by more and more people, and understanding its principles helps us to use it better. Reading the source code of Redux is a good way to do this. Now, let's implement a simple Redux.
For the complete code of this article, please check out the Github repository: https://github.com/YanYuanFE/redux-app
// clone repo
git clone https://github.com/YanYuanFE/redux-app.git
cd redux-app
// checkout branch
git checkout part-3
// install
npm install
// start
npm start
Basic API#
If you have used Redux before, you should be familiar with its API. The basic API of Redux includes createStore, getState, subscribe, and dispatch. Let's review how Redux is used:
First, Redux exposes the createStore method. We can call createStore and pass in a reducer, like this: const store = createStore(reducer) (we will temporarily ignore middleware). We can use store.getState() to get the state, store.dispatch() to dispatch an action, and store.subscribe() to subscribe to changes in the state. The following is a simple implementation of the Redux API.
To facilitate debugging, we will use the code for implementing a simple counter that we discussed earlier. Create a new file called redux.js in the src directory.
export function createStore(reducer) {
}
In redux.js, we first export the core method of Redux, createStore, and pass in the reducer as a parameter.
In the createStore method, we need to define variables to store the current state and the listeners for state changes.
let currentState;
let currentListeners = [];
Here, we define currentState as the current state, initialized as undefined, and currentListeners as an array to store the listeners, initialized as an empty array.
Next, we define the getState method to get the current state. It simply returns currentState:
function getState() {
return currentState;
}
Then, we define the subscribe function to subscribe to methods that will be executed when the state changes:
function subscribe(listener) {
currentListeners.push(listener);
}
The subscribe method takes a listener function as a parameter and pushes it into the array of listeners.
Next, we define the dispatch method to dispatch an action:
function dispatch(action) {
currentState = reducer(currentState, action);
currentListeners.forEach(v => v());
return action;
}
The dispatch method takes an action as a parameter. It calls the reducer to update the currentState by passing in the current state and the action. When the state changes, it notifies the listeners by executing each subscription function in the array one by one. This implementation uses the publish-subscribe pattern from design patterns. Finally, it returns the action.
As you can see, we have implemented the basic API of Redux. Is that all? Of course not, because Redux has not been initialized yet, and the initial state in the reducer has not taken effect. So we need to manually dispatch an action, and the action.type must be unique. Like this:
dispatch({type: '@@REDUX/INIT'});
Here, we initialize Redux by defining the type as '@@REDUX/INIT'. We use this definition to ensure that the reducer hits the default case and returns the initial state. Finally, according to the usage of Redux, we know that the store must be an object that includes methods like getState, subscribe, and dispatch. Therefore, we need to return these methods.
return { getState, subscribe, dispatch };
So far, we have implemented a super simple Redux. Although it is simple and lacks error handling, it is sufficient for understanding Redux. The following is the complete code:
export function createStore(reducer) {
let currentState;
let currentListeners = [];
function getState() {
return currentState;
}
function subscribe(listener) {
currentListeners.push(listener);
}
function dispatch(action) {
currentState = reducer(currentState, action);
currentListeners.forEach(v => v());
return action;
}
dispatch({type: '@@REDUX/INIT'}); // Initialization
return { getState, subscribe, dispatch }
}
Testing#
Now, let's validate the Redux implementation above by using the previous counter example.
In the previous counter example, open index.js in the src directory and modify the following code:
import { createStore } from './redux';
Replace 'redux' with the newly created redux.js file. Check the browser for the results.
As shown in the image, the result is consistent with Redux and meets our expectations.
Conclusion#
In this article, we implemented a simple Redux and completed the implementation of its basic API. This helps you understand the principles of Redux. In the future, we will gradually expand on this and write the implementation of react-redux and middleware.