• Web学习笔记-React(Redux)


    笔记内容转载自 AcWing 的 Web 应用课讲义,课程链接:AcWing Web 应用课

    之前我们提到过一个问题,就是如果两个兄弟组件要访问对方的数据,需要将数据存放到最近公共祖先上,这样当 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

    create-react-app redux-app
    
    • 1

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

    npm i redux react-redux @reduxjs/toolkit
    
    • 1

    假设现在我们只维护一个 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>
    );
    
    • 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 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
    • 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

    控制台输出如下:

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

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

    import { combineReducers } from '@reduxjs/toolkit';
    
    const f_all = combineReducers({
      f1: f1,
      f2: f2,
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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 代码如下:

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

    然后我们在 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>
    );
    
    • 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

    现在我们来看一下如何在 NumberString 组件中调用他们的 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    现在我们再来看一下如何修改 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);
    
    • 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
  • 相关阅读:
    二分图的判定&最大匹配
    基于Echarts实现可视化数据大屏蓝色HTML土地交易大数据分析模板(1页)
    Maven的详细安装步骤说明
    ApplicationListener和@EventListener
    MySQL数据库触发器
    11.6rhce
    【Java并发入门】03 互斥锁(上):解决原子性问题
    查找和排序 + 集合 + 单例模式【Java 基础_简单学习】
    uniapp分包 解决分多个包的问题
    nginx 的进程建通信机制-共享内存/channel/信号
  • 原文地址:https://blog.csdn.net/m0_51755720/article/details/132795163