本文记录 React 的学习过程,内容为 Redux。
之前我们提到过一个问题,就是如果两个兄弟组件要访问对方的数据,需要将数据存放到最近公共祖先上,这样当 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
:
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) => { switch(action.type) { case 'add': return state + action.val; case 'sub': return state - action.val; default: return state; } };
const store = configureStore({ reducer: f1 });
store.subscribe(() => {console.log(store.getState())});
store.dispatch({type: 'add', val: 2}); 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) => { 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) => { return { f1: f1(state.f1, action), f2: f2(state.f2, action), } };
const store = configureStore({ reducer: f_all });
store.subscribe(() => {console.log(store.getState())});
store.dispatch({type: 'add', val: 2}); store.dispatch({type: 'add', val: 3}); store.dispatch({type: 'concat', character: 'abc'});
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
中的状态更新后调用一次,用来更新组件中的值,即将 store
中 state
的值绑定到组件的 props
属性上。
mapDispatchToProps
:组件创建时调用一次,用来将 store
的 dispatch
函数传入组件,即将 dispatch
函数映射到组件的 props
属性上。
为了方便展示,我们定义三个组件:App
、Number
(state
从0开始)、String
(state
从空串开始)。
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) => { 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({ reducer: f_all });
store.subscribe(() => {console.log(store.getState())});
const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <App /> </Provider> );
|
现在我们来看一下如何在 Number
和 String
组件中调用他们的 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) => { 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) => { return { number: state.number, } };
const mapDispatchToProps = { concat: (character) => { return { 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(路由)。
下一章:无。