The content of the notes is reproduced from AcWing's Web Application Course handout, course link: AcWing Web Application Course .
We mentioned a problem before, that is, if two sibling components want to access each other's data, they need to store the data in the nearest common ancestor, which is very troublesome when the DOM tree is complex. Redux is a place outside the entire DOM tree to store some global values.
1. Basic concepts of Redux
Redux will state
maintain it as a tree structure, each node stores a value, and uses a function reducer
to maintain each value. Redux uses dictionaries to store child nodes, generally called store
.
If we want to modify a certain value in the tree, the entire tree will be recalculated. We'll use dispatch
a function, which will call each function recursively reducer
. In addition, an object parameter needs to be passed in to indicate which node needs to be operated. There is an attribute in it type
, and we will define a unique one for each node type
.
The basic concepts of Redux are summarized as follows:
store
:Storage tree structure.state
: The data maintained is generally maintained in a tree structure.reducer
:state
Function to update, onestate
is bound to eachreducer
. Pass in two parameters: currentstate
andaction
, return newstate
.action
: A common object that storesreducer
the incoming parameters of and generally describesstate
the update type of , wheretype
the attribute represents the node to be modified.dispatch
: Pass in a parameteraction
andstate
operate the entire tree once, that is, recursively call allreducer
functions of the entire tree.
Let's first create a Redux project redux-app
:
create-react-app redux-app
Enter the project root directory and install the relevant modules:
npm i redux react-redux @reduxjs/toolkit
Assuming that we only maintain one now state
, we build the simplest plain version of Redux (nothing to do with React):
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import {
configureStore } from '@reduxjs/toolkit';
const f1 = (state = 0, action) => {
// reducer函数
switch(action.type) {
case 'add':
return state + action.val;
case 'sub':
return state - action.val;
default:
return state;
}
};
const store = configureStore({
// 将f1函数构建成一棵状态树
reducer: f1
});
store.subscribe(() => {
console.log(store.getState())}); // 每次dispatch完之后会执行一遍该函数
store.dispatch({
type: 'add', val: 2}); // 修改state
store.dispatch({
type: 'add', val: 3});
console.log(store.getState()); // 返回整棵树的值
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
</React.StrictMode>
);
Now let's see how to maintain two nodes:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import {
configureStore } from '@reduxjs/toolkit';
const f1 = (state = 0, action) => {
// reducer函数
switch(action.type) {
case 'add':
return state + action.val;
case 'sub':
return state - action.val;
default:
return state;
}
};
const f2 = (state = '', action) => {
switch(action.type) {
case 'concat':
return state + action.character;
default:
return state;
}
};
const f_all = (state = {
}, action) => {
// 组合了f1与f2
return {
f1: f1(state.f1, action),
f2: f2(state.f2, action),
}
};
const store = configureStore({
// 将f_all函数构建成一棵状态树
reducer: f_all
});
store.subscribe(() => {
console.log(store.getState())}); // 每次dispatch完之后会执行一遍该函数
store.dispatch({
type: 'add', val: 2}); // 修改f1的state
store.dispatch({
type: 'add', val: 3});
store.dispatch({
type: 'concat', character: 'abc'}); // 修改f2的state
console.log(store.getState()); // 返回整棵树的值
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
</React.StrictMode>
);
The console output is as follows:
{f1: 2, f2: ''}
{f1: 5, f2: ''}
{f1: 5, f2: 'abc'}
{f1: 5, f2: 'abc'}
f_all
You don’t need to write it yourself, you can use combineReducers
:
import {
combineReducers } from '@reduxjs/toolkit';
const f_all = combineReducers({
f1: f1,
f2: f2,
});
2. Basic concepts of React-Redux
Now let's take a look at how Redux is combined with React. We need to Provider
wrap up our entire project with . The basic concepts of React-Redux are as follows:
Provider
Component: Used to wrap the entire project, and itsstore
properties are used to store Reduxstore
objects.connect(mapStateToProps, mapDispatchToProps)
Function: used tostore
associate with a component. This function will return a function. The returned function can take the component as an input parameter and then return a new component. This new component will bind thestate
value of to the component'sprops
property.mapStateToProps
:store
Called once every time the status in is updated to update the value in the component, that is, the valuestore
instate
is bound to the component'sprops
attribute.mapDispatchToProps
: Called once when the component is created, it is used to pass the functionstore
ofdispatch
the component into the component, that is,dispatch
to map the function to the component'sprops
attribute.
For the convenience of display, we define three components: App
, Number
( state
starting from 0), String
( state
starting from the empty string).
App
code show as below:
import React, {
Component } from 'react';
import Number from './number';
import String from './string';
class App extends Component {
state = {
}
render() {
return (
<React.Fragment>
<Number />
<hr />
<String />
</React.Fragment>
);
}
}
export default App;
Then we index.js
implement React-Redux in:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import {
configureStore } from '@reduxjs/toolkit';
import {
combineReducers } from '@reduxjs/toolkit';
import {
Provider } from 'react-redux';
import App from './components/app';
const f1 = (state = 0, action) => {
// reducer函数
switch(action.type) {
case 'add':
return state + action.val;
case 'sub':
return state - action.val;
default:
return state;
}
};
const f2 = (state = '', action) => {
switch(action.type) {
case 'concat':
return state + action.character;
default:
return state;
}
};
const f_all = combineReducers({
number: f1,
string: f2,
});
const store = configureStore({
// 将f_all函数构建成一棵状态树
reducer: f_all
});
store.subscribe(() => {
console.log(store.getState())}); // 每次dispatch完之后会执行一遍该函数
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={
store}>
<App />
</Provider>
);
Now let's take a look at how to call their values in Number
and components . We need to use an API: , taking as an example :String
state
connect
Number
import React, {
Component } from 'react';
import {
connect } from 'react-redux';
class Number extends Component {
state = {
}
render() {
console.log(this.props);
return (
<React.Fragment>
<h3>Number: {
this.props.number}</h3>
</React.Fragment>
);
}
};
const mapStateToProps = (state, props) => {
// 第一个参数state包含整个状态树的树结构
return {
number: state.number,
}
};
export default connect(mapStateToProps)(Number);
Now let's take a look at how to modify state
the value of . We need to define mapDispatchToProps
an object to dispatch
map to props
. Suppose we want Number
to operate String
in state
:
import React, {
Component } from 'react';
import {
connect } from 'react-redux';
class Number extends Component {
state = {
}
handleClick = () => {
this.props.concat('abc');
}
render() {
console.log(this.props);
return (
<React.Fragment>
<h3>Number: {
this.props.number}</h3>
<button onClick={
this.handleClick}>添加</button>
</React.Fragment>
);
}
};
const mapStateToProps = (state, props) => {
// 第一个参数state包含整个状态树的树结构
return {
number: state.number,
}
};
const mapDispatchToProps = {
concat: (character) => {
return {
// 会返回一个对象,这个对象就是dispatch中用到的action,会将返回值作用到整个状态树中
type: 'concat',
character: character,
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Number);