笔记内容转载自 AcWing 的 Web 应用课讲义,课程链接:AcWing Web 应用课。
CONTENTS
之前我们提到过一个问题,就是如果两个兄弟组件要访问对方的数据,需要将数据存放到最近公共祖先上,这样当 DOM 树很复杂时就很麻烦。Redux 就是在整个 DOM 树之外拿出一个地方,用来存储一些全局的值。
1. Redux基本概念
Redux 会将 state 维护成一个树结构,每个节点存一个值,使用一个函数 reducer 维护每个值。Redux 用字典存储子节点,一般被称为 store。
我们如果想修改树里面的某一个值,会将整个树重新计算一遍。我们会使用 dispatch 函数,他会递归调用每个 reducer 函数。此外还需要传入一个对象参数,表示需要对哪个节点进行操作,其中有个属性 type,我们会给每个节点定义一个唯一的 type。
Redux 的基本概念总结如下:
store:存储树结构。state:维护的数据,一般维护成树的结构。reducer:对state进行更新的函数,每个state绑定一个reducer。传入两个参数:当前state和action,返回新state。action:一个普通对象,存储reducer的传入参数,一般描述对state的更新类型,其中的type属性表示要修改的节点。dispatch:传入一个参数action,对整棵state树操作一遍,即递归调用整棵树的所有reducer函数。
我们先创建一个 Redux 项目 redux-app:
create-react-app redux-app
进入项目根目录,安装相关的模块:
npm i redux react-redux @reduxjs/toolkit
假设现在我们只维护一个 state,我们构建一个最简单的朴素版 Redux(和 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>
);
现在来看看如何维护两个节点:
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>
);
控制台输出如下:
{f1: 2, f2: ''}
{f1: 5, f2: ''}
{f1: 5, f2: 'abc'}
{f1: 5, f2: 'abc'}
f_all 也可以不用自己手写实现,可以使用 combineReducers 实现:
import { combineReducers } from '@reduxjs/toolkit';
const f_all = combineReducers({
f1: f1,
f2: f2,
});
2. React-Redux基本概念
现在我们来看一下 Redux 如何与 React 组合起来。我们需要用 Provider 将我们的整个项目包含起来。React-Redux基本概念如下:
Provider组件:用来包裹整个项目,其store属性用来存储 Redux 的store对象。connect(mapStateToProps, mapDispatchToProps)函数:用来将store与组件关联起来,该函数会返回一个函数,返回的函数可以将组件作为输入参数,然后返回一个新的组件,这个新的组件会将state的值绑定到组件的props属性上。mapStateToProps:每次store中的状态更新后调用一次,用来更新组件中的值,即将store中state的值绑定到组件的props属性上。mapDispatchToProps:组件创建时调用一次,用来将store的dispatch函数传入组件,即将dispatch函数映射到组件的props属性上。
为了方便展示,我们定义三个组件:App、Number(state 从0开始)、String(state 从空串开始)。
App 代码如下:
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;
然后我们在 index.js 中实现 React-Redux:
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>
);
现在我们来看一下如何在 Number 和 String 组件中调用他们的 state 值,需要用到一个 API: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);
现在我们再来看一下如何修改 state 的值,需要定义 mapDispatchToProps 对象将 dispatch 映射到 props 里,假设我们要在 Number 中操作 String 里的 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);










