• React学习(六)— 状态管理Redux


    一、Redux

    和vuex一样,redux的出现是为了管理web应用的公共状态。

    这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。

    在这里插入图片描述

    二、Redux的组成

    2.1 store

    • store 就是保存数据的地方,整个应用只能有一个 store,可以理解为一个存储数据的仓库
    • redux 提供 createStore 这个函数,用来创建一个store 以存放整个应用的 state:
    import { createStore } from 'redux';
    const store = createStore(reducer, [preloadedState], [enhancer]);
    
    • 1
    • 2

    可以看到,createStore 接受 reducer初始 state(可选)和增强器(可选)作为参数,返回一个新的 store 对象.

    2.2 state

    state就是store 对象包含所有数据,如果要获取当前时刻的 state,可以通过 store.getState() 方法拿到:

    import { createStore } from 'redux';
    const store = createStore(reducer, [preloadedState], [enhancer]);
    
    const state = store.getState();
    
    • 1
    • 2
    • 3
    • 4

    2.3 action

    • state 的变化,会导致视图的变化。但是,用户接触不到 state,只能接触到视图。所以,state 的变化必须是由视图发起的。
    • action 就是视图发出的通知,通知store此时的 state 应该要发生变化了。
    • action 是一个对象。其中的 type属性是必须的,表示 action 的名称。其他属性可以自由设置,社区有一个规范可以参考:
    const action = {
      type: 'ADD_TODO',
      payload: 'Learn Redux' // 可选属性 可自定义名称
    };
    
    • 1
    • 2
    • 3
    • 4

    所以action可以理解为视图层向store发送的一个命令(通知),它包含了需要执行的事件(type属性)以及传递的数据(自定义属性)。

    2.4 reducer

    • store 收到 action 以后,必须给出一个新的 state,这样视图才会进行更新。state 的计算(更新)过程则是通过reducer 实现。
    • reducer 是一个函数,它接受 action 和当前 state 作为参数,返回一个新的 state:
    const reducer = function (state = initState, action) {
      // ...
      return new_state;
    };
    
    • 1
    • 2
    • 3
    • 4

    创建store时,第一个参数就是reducer:

    const store = createStore(reducer);
    
    • 1

    那么如何向store发送action呢?

    store.dispatch({
      type: 'ADD_TODO',
      payload: 'Learn Redux'
    });
    
    • 1
    • 2
    • 3
    • 4

    store对象拥有dispath方法发送action,参数就是需要传递的action对象,然后reducer接收到action并处理,返回新的state,视图自动更新。

    三、三大原则

    Redux 可以用这三个基本原则来描述:

    3.1 单一数据源

    整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

    console.log(store.getState())
    
    /* 输出
    {
      visibilityFilter: 'SHOW_ALL',
      todos: [
        {
          text: 'Consider using Redux',
          completed: true,
        },
        {
          text: 'Keep all state in a single tree',
          completed: false
        }
      ]
    }
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.2 State只读

    唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象

    这样确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心竞态条件(race condition)的出现。 Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。

    store.dispatch({
      type: 'COMPLETE_TODO',
      index: 1
    })
    
    store.dispatch({
      type: 'SET_VISIBILITY_FILTER',
      filter: 'SHOW_COMPLETED'
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    所以返回新的state时不能直接修改参数state,而是在不修改参数state的基础上返回新的state。
    例如完成一个新增todo的功能:

    case 'ADD_TODO':
          return state.push({
              text: action.text,
              completed: false
            })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样是不会生效的,应为这样直接修改了state的值,正确的做法应该是:

     case 'ADD_TODO':
    	    return [
    	      ...state,
    	      {
    	        text: action.text,
    	        completed: false
    	      }
    	    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里使用了扩展运算符(…)将数组展开然后和新增的todo合并,对象同样可以使用扩展运算符达到新增属性的目的。

    3.3 使用纯函数修改State

    为了描述 action 如何改变 state tree ,你需要编写 reducers

    Reducer 只是一些纯函数,它接收先前的 stateaction,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。

    function visibilityFilter(state = 'SHOW_ALL', action) {
      switch (action.type) {
        case 'SET_VISIBILITY_FILTER':
          return action.filter
        default:
          return state
      }
    }
    
    function todos(state = [], action) {
      switch (action.type) {
        case 'ADD_TODO':
          return [
            ...state,
            {
              text: action.text,
              completed: false
            }
          ]
        case 'COMPLETE_TODO':
          return state.map((todo, index) => {
            if (index === action.index) {
              return Object.assign({}, todo, {
                completed: true
              })
            }
            return todo
          })
        default:
          return state
      }
    }
    
    import { combineReducers, createStore } from 'redux'
    //合并reducer
    let reducer = combineReducers({ visibilityFilter, todos })
    //利用reducer创建store
    let store = createStore(reducer)
    
    • 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

    四、基于Redux的TodoList

    效果:
    在这里插入图片描述
    todes的reducer:

    const initTodos = [
      {
        id: 1,
        text: "睡觉😴",
        completed: false,
      },
      {
        id: 2,
        text: "吃饭😋",
        completed: false,
      },
      {
        id: 3,
        text: "打豆豆😜",
        completed: true,
      },
    ];
    
    let nextTodoID = 4;
    
    const todos = (state = initTodos, action) => {
      switch (action.type) {
        case "ADD_TODO":
          return [
            ...state,
            {
              id: nextTodoID++,
              text: action.text,
              completed: false,
            },
          ];
        case "TOGGLE_TODO":
          return state.map((todo) =>
            todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
          );
        default:
          return state;
      }
    };
    
    export default todos;
    
    • 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

    filter的reducer:

    const visibilityFilter = (state = "SHOW_ALL", action) => {
      switch (action.type) {
        case "SET_VISIBILITY_FILTER":
          return action.filter;
        default:
          return state;
      }
    };
    
    export default visibilityFilter;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    详细代码地址:
    https://github.com/YancyZhang30/react-redux-todos.git

    五、react-redux

    redux 并不是react专有的,其本身是一个可以结合 react,vue,angular 甚至是原生 javaScript 应用使用的状态库。

    react-redux是react官方提供了 react的redux适配库(这个库是可以选用的,也可以只用redux),使得我们能够更好地在react应用中使用redux来进行全局状态管理,使用react-redux主要是为了解决组件每次使用store中的数据时都必须先使用store.getState()来获取state,然后必须使用store.subscribe()进行订阅的问题。

    react-redux 将所有组件分成 UI 组件和容器组件两大类:
    1、 UI 组件只负责 UI 的呈现,不含有状态(this.state),所有数据都由 this.props 提供,且不使用任何 redux 的 API。
    2、容器组件负责管理数据和业务逻辑,含有状态(this.state),可使用 redux 的 API。

    5.1 connect方法

    react-redux 提供了 connect 方法,用于将 UI 组件生成容器组件,所以如果组件想要使用store中的state,就必须先使用connect方法与store进行连接:

    import {connect} from 'react-redux'
    
    const Counter = (props) => {
        return (
            <div>
                <p>计数器: {props.num}</p>
                <div>
                    <button onClick={props.increatement}></button> | <button onClick={props.decreate}></button><br/>
                </div>
            </div>
        )
    }
    
    //读取数据
    const mapStateToProps=(state)=>{
        return{
            num:state
        }
    }
    
    //进行触发action
    const mapDispathToProps=(dispatch)=>{
        return {
            increatement:()=>{dispatch(
                {
                    type:"inCreateNum",
                    num:10
                }
            )},
            decreate:()=>{dispatch(
                {
                    type:"descment",
                    num:10
                }
            )}
    
        }
    }
    
    // connect将组件与store连接。connect里的参数顺序不能颠倒
    export default connect(mapStateToProps,mapDispathToProps)(Counter)
    
    
    • 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

    connect(mapStateToProps,mapDispathToProps)(Counter)中:

    1. mapStateToProps:mapStateToProps 是一个函数,它的作用就是建立一个从 state对象(外部)到 UI 组件 props对象的映射关系。该函数会订阅 整个应用的 store,每当 state 更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。还可以使用第二个参数(可选),代表容器组件的 props 对象
    2. mapDispathToProps:mapDispatchToProps 是 connect 函数的第二个参数,用来建立 UI 组件的参数到 store.dispatch 方法的映射。
    3. Counter:需要变成容器组件的UI组件,也就是需要连接store的组件。

    5.2 Provider组件

    • 使用 connect 方法生成容器组件以后,需要让容器组件拿到 state 对象,才能生成 UI 组件 的参数。
    • react-redux提供了 Provider 组件,可以让容器组件拿到 state,注意只有被Provider组件包含的组件才能拿到state。

    main.jsx:

    import React from 'react'
    import ReactDOM from 'react-dom/client'
    import { Provider } from 'react-redux'
    import Counter from "./Counter";
    import ShowCounter from "./ShowCounter";
    import reducer from "./store/counter";
    import { legacy_createStore as createStore } from "redux";
    
    const store = createStore(reducer);
    
    
    ReactDOM.createRoot(document.getElementById('root')).render(
        <Provider store={store}>
            <React.StrictMode>
                <Counter></Counter>
                <ShowCounter></ShowCounter>
            </React.StrictMode>
        </Provider>
    )
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    此时Counter组件和ShowCounter组件都可以拿到state。
    ShowCounter.jsx:

    import React from "react";
    import {connect} from "react-redux";
    
    const ShowCounter = (props) => {
        return (
            <div>
                <p>来自计数器的数据:<span style={{color: 'red'}}>{props.num}</span></p>
            </div>
        )
    }
    
    //读取数据
    const mapStateToProps=(state)=>{
        return{
            num:state
        }
    }
    
    
    // connect将组件与store连接。connect里的参数顺序不能颠倒
    export default connect(mapStateToProps)(ShowCounter)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    由于ShowCounter组件并不需要修改store,所以mapDispathToProps参数可以直接省略了。
    效果:
    在这里插入图片描述

  • 相关阅读:
    2022年rhce最新认证—(满分通过)
    一次联合编译器问题排查(备忘)
    java计算机毕业设计校园环境保护监督系统源代码+系统+数据库+lw文档
    PowerCLI 通过vCenter批量更改所有的esxi主机名称和DNS
    NPM配置国内镜像源
    第7章 【MySQL】B+树索引的使用
    计算机毕业设计ssm+vue基本微信小程序的拼车自助服务小程序
    什么是栈顶缓存技术
    从零开始学习 Java:简单易懂的入门指南之IO字节流(三十)
    Spring Boot使用WebSocket模拟聊天
  • 原文地址:https://blog.csdn.net/ZHANGYANG_1109/article/details/125953507