The words written earlier (The sample code is here)
I recommend you to read Mr. BeardThe React. Js Little Book
Redux and React-Redux are not the same thing. Redux is an architectural pattern derived from Flux. See here, or here, or here. React-redux is a concrete implementation of redux and React. When we use React, we often encounter deep-seated nested components and need to pass values. If we use props to pass values, it is obviously very painful. React provides us with the native Context API to solve this problem, but the most common solution is to use the React-Redux library wrapped around the Context API. This article does not cover the specific use of react-redux, but uses a small example to understand what redux is.
Ok, now let’s get down to business and implement our own Redux.
One, the original
First, we create a project with creat-react-app, remove the redundant parts of SRC, keep only index.js, and modify the DOM structure of index.html:
# index.html
<div id="root">
<div id="head"></div>
<div id="body"></div>
</div>
Copy the code
We create an object in index.js to store and manage the data state of our entire application, and render the data on the page using render functions:
const appState = {
head: {
text: 'I am the head',
color: 'red'
},
body: {
text: 'I am the body',
color: 'green'}}function renderHead (state){
const head = document.getElementById('head')
head.innerText = state.head.text;
head.style.color = state.head.color;
}
function renderBody (state){
const body = document.getElementById('body')
body.innerText = state.body.text;
body.style.color = state.body.color;
}
function renderApp (state){
renderHead(state);
renderBody(state);
}
renderApp(appState);
Copy the code
When we run the code and open the page, we can see that we already have ‘I am the head’ in red and ‘I am the body’ in green.
function renderHead (state){
const head = document.getElementById('head')
head.innerText = state.head.text + The '-' + state.body.text;
head.style.color = state.head.color;
state.body.text = 'I am the body modified by head';
}
Copy the code
Second, the dispatch
Now it seems that we have a contradiction in front of us: we need to share data, but the shared data can be arbitrarily modified and cause unexpected problems! To resolve this contradiction, we need a steward who manages the state of the shared data. Any operations on the shared data need to be performed by this steward, so as to avoid the unexpected harm caused by arbitrary modification of the shared data! Let’s redefine a function that acts as our steward to manage our shared data:
function dispatch(state, action) {
switch (action.type) {
case 'HEAD_COLOR':
state.head.color = action.color
break
case 'BODY_TEXT':
state.body.text = action.text
break
default:
break}}Copy the code
Let’s rework the head render function:
function renderHead (state){
const head = document.getElementById('head')
head.innerText = state.head.text + The '-' + state.body.text;
head.style.color = state.head.color;
dispatch(state, { type: 'BODY_TEXT', text: 'I'm the body of head after the call to Dispatch.'})}Copy the code
The Dispatch function takes two parameters, the state to be modified and the value to be modified. At this point, we still modify the state, but with the Dispatch function, we make the change manageable, because any change to the state can be traced to the source of the change in the Dispatch. In this way, we seem to have solved the previous contradiction by creating a globally shared data and strictly controlling any changes to that data. However, in a file where we have to maintain both state and the steward function Dispatch, this file is bound to become verbose and difficult to maintain as the application becomes more complex. Now, let’s separate state and dispatch:
- Save state in a separate file
- Use a separate file to save the changeState comparison for the changeState in dispatch
- Finally, take a file and combine them to generate a globally unique store
In this way, not only the single file becomes more simplified, but also in other applications, we can easily reuse our set of methods. We only need to pass in the state of different applications and modify the corresponding logic stateChange of the state, and we can safely call the Dispatch method. Various operations are performed on the data:
# Change our directory structure, add redux folder+ SRC ++ redux -- state.js // Store the state of the application data -- storechange.js // Maintain a set of logic to modify the store, only responsible for calculation, Returns a new store -- createstore. js // combines state and stateChange to create a store for any application to reference --index.js## Modified each file
# state.js -- global state
export const state = {
head: {
text: 'I am the head',
color: 'red'
},
body: {
text: 'I am the body',
color: 'green'}}# storechange.js -- only computations, store modifications
export const storeChange = (store, action) => {
switch (action.type) {
case 'HEAD_COLOR':
store.head.color = action.color
break
case 'BODY_TEXT':
store.body.text = action.text
break
default:
break}}# createstore.js -- Create a global store
export const createStore = (state, storeChange) => {
const store = state || {};
const dispatch = (action) => storeChange(store, action);
return { store, dispatch }
}
# index.js
import { state } from './redux/state.js';
import { storeChange } from './redux/storeChange.js';
import { createStore } from './redux/createStore.js';
const { store, dispatch } = createStore(state, storeChange)
function renderHead (state){
const head = document.getElementById('head')
head.innerText = state.text;
head.style.color = state.color;
}
function renderBody (state){
const body = document.getElementById('body')
body.innerText = state.text;
body.style.color = state.color;
}
functionrenderApp (store){ renderHead(store.head); renderBody(store.body); } // First render renderApp(store);Copy the code
Through the above file splitting, we can see that not only the individual files are more concise, but also the functions of the files are more clear:
- In state, we only save our shared data
- In storeChange, we will maintain the corresponding logic for changing stores and compute new stores
- In createStore, we create a Store
- In index.js, we only need to worry about the corresponding business logic
Third, the subscribe
Everything seemed fine, but when we called Dispatch to modify the Store after the first rendering, we found that although the data had been changed, the page had not been refreshed, and only after Dispatch had changed the data could the page be refreshed again by calling renderApp().
// renderApp(store) for the first time; dispatch({type: 'BODY_TEXT', text: 'I'm calling dispatch to modify the body.'}) // renderApp(store) is not refreshed after data modification; // Call renderApp page refresh againCopy the code
We don’t want to manually refresh the page every time we change the data. It would be great if we could refresh the page automatically after changing the data. It would have been inappropriate to write renderApp directly in dispatch, and our createStore would have lost its versatility. We can add a collection array to createStore to collect all the methods that need to be executed after the Dispatch call and then iterate through them, thus ensuring the universality of createStore:
# createStore
exportconst createStore = (state, storeChange) => { const listeners = []; const store = state || {}; const subscribe = (listen) => listeners.push(listen); const dispatch = (action) => { storeChange(store, action); listeners.forEach(item => { item(store); })};return { store, dispatch, subscribe }
}
# index.js··· Const {store, dispatch, subscribe} = createStore(state, Notice subscribe((store) => renderApp(store)); renderApp(store); dispatch({type: 'BODY_TEXT', text: 'I'm calling dispatch to modify the body.' });
Copy the code
This way, every time we call Dispatch, the page is refreshed. If we don’t want to refresh the page and just want to alert the listeners, we just need to change the listeners we’ve added:
subscribe((store) => alert('Page refreshed'));
renderApp(store);
dispatch({ type: 'BODY_TEXT', text: 'I'm calling dispatch to modify the body.' });
Copy the code
This ensures that createStore is universal.
Four, optimization
At this point, we seem to have achieved what we were trying to achieve: we have a globally public store, where changes are tightly controlled and pages are automatically refreshed every time a store is modified by Dispatch. However, this is obviously not enough, the above code is still a bit crude and has serious performance problems, as we can see by printing the log in the render function:
# storeChange.js
export const storeChange = (store, action) => {
switch (action.type) {
case 'HEAD_COLOR':
return{... store, head: { ... store.head, color: action.color } }case 'BODY_TEXT':
return{... store, body: { ... store.body, text: action.text } } default:return { ...store }
}
}
# createStore.js
export const createStore = (state, storeChange) => {
const listeners = [];
let store = state || {};
const subscribe = (listen) => listeners.push(listen);
const dispatch = (action) => {
const newStore = storeChange(store, action);
listeners.forEach(item => {
item(newStore, store);
})
store = newStore;
};
return { store, dispatch, subscribe }
}
# index.js
import { state } from './redux/state.js';
import { storeChange } from './redux/storeChange.js';
import { createStore } from './redux/createStore.js';
const { store, dispatch, subscribe } = createStore(state, storeChange);
function renderHead (state){
console.log('render head');
const head = document.getElementById('head')
head.innerText = state.text;
head.style.color = state.color;
}
function renderBody (state){
console.log('render body');
const body = document.getElementById('body')
body.innerText = state.text;
body.style.color = state.color;
}
function renderApp (store, oldStore={}){
if(store === oldStore) return; store.head ! == oldStore.head && renderHead(store.head); store.body ! == oldStore.body && renderBody(store.body); console.log('render app',store, oldStore); } subscribe((store, oldStore) => renderApp(store, oldStore)); renderApp(store); dispatch({type: 'BODY_TEXT', text: 'I'm calling dispatch to modify the body.' });
Copy the code
Above, we changed storeChange so that instead of modifying the original store directly, it returns a new store by evaluating it. We also modified cearteStore to receive the new store returned by storeChange, assign the new store to the previous store after Dispatch modified the data and the page refreshed. When the page is refreshed, we can do some performance optimization by comparing newStore and oldStore to sense what needs to be rerendered. Reopening the console, we can see that when we changed the body, the head was not re-rendered:
The last
Let’s take a look at Redux with a simple code example. The code is still a bit crude, but we’ve implemented some of redux’s core ideas:
- All states in an application are stored in a single store as an Object tree.
- The only way to change a store is to trigger an action, which is an abstraction of the action behavior.
The above is my summary of the React.js little book. Due to the limited space, we will implement our own React-Redux with React in the next chapter.