Web学习笔记-React(Redux)

  1. 1. Redux基本概念
  2. 2. React-Redux基本概念

本文记录 React 的学习过程,内容为 Redux。
之前我们提到过一个问题,就是如果两个兄弟组件要访问对方的数据,需要将数据存放到最近公共祖先上,这样当 DOM 树很复杂时就很麻烦。Redux 就是在整个 DOM 树之外拿出一个地方,用来存储一些全局的值。

1. Redux基本概念

Redux 会将 state 维护成一个树结构,每个节点存一个值,使用一个函数 reducer 维护每个值。Redux 用字典存储子节点,一般被称为 store

我们如果想修改树里面的某一个值,会将整个树重新计算一遍。我们会使用 dispatch 函数,他会递归调用每个 reducer 函数。此外还需要传入一个对象参数,表示需要对哪个节点进行操作,其中有个属性 type,我们会给每个节点定义一个唯一的 type

Redux 的基本概念总结如下:

  • store:存储树结构。
  • state:维护的数据,一般维护成树的结构。
  • reducer:对 state 进行更新的函数,每个 state 绑定一个 reducer。传入两个参数:当前 stateaction,返回新 state
  • action:一个普通对象,存储 reducer 的传入参数,一般描述对 state 的更新类型,其中的 type 属性表示要修改的节点。
  • dispatch:传入一个参数 action,对整棵 state 树操作一遍,即递归调用整棵树的所有 reducer 函数。

我们先创建一个 Redux 项目 redux-app

1
create-react-app redux-app

进入项目根目录,安装相关的模块:

1
npm i redux react-redux @reduxjs/toolkit

假设现在我们只维护一个 state,我们构建一个最简单的朴素版 Redux(和 React 无关):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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>
);

现在来看看如何维护两个节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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>
);

控制台输出如下:

1
2
3
4
{f1: 2, f2: ''}
{f1: 5, f2: ''}
{f1: 5, f2: 'abc'}
{f1: 5, f2: 'abc'}

f_all 也可以不用自己手写实现,可以使用 combineReducers 实现:

1
2
3
4
5
6
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 中的状态更新后调用一次,用来更新组件中的值,即将 storestate 的值绑定到组件的 props 属性上。
    • mapDispatchToProps:组件创建时调用一次,用来将 storedispatch 函数传入组件,即将 dispatch 函数映射到组件的 props 属性上。

为了方便展示,我们定义三个组件:AppNumberstate 从0开始)、Stringstate 从空串开始)。

App 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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>
);

现在我们来看一下如何在 NumberString 组件中调用他们的 state 值,需要用到一个 API:connect,以 Number 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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);

同理我们实现在 String 中操作 Number 中的 state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import React, { Component } from 'react';
import { connect } from 'react-redux';

class String extends Component {
state = { }

handleClickAdd = () => {
this.props.add(5);
}

handleClickSub = () => {
this.props.sub(1);
}

render() {
console.log(this.props.string);
return (
<React.Fragment>
<h3>String: {this.props.string}</h3>
<button onClick={this.handleClickAdd}></button>
<button onClick={this.handleClickSub}></button>
</React.Fragment>
);
}
};

const mapStateToProps = (state, props) => {
return {
string: state.string,
}
};

const mapDispatchToProps = {
add: (val) => {
return {
type: 'add',
val: val,
}
},
sub: (val) => {
return {
type: 'sub',
val: val,
}
}
}

export default connect(mapStateToProps, mapDispatchToProps)(String);

上一章:Web学习笔记-React(路由)

下一章:无。