likes
comments
collection
share

React全家桶 - 【Redux】 - 环境准备、使用步骤、提交action传参、异步状态操作

作者站长头像
站长
· 阅读数 4

一、环境准备

1.1 安装插件

  • 在 React 中使用 Redux,官方要求安装两个插件 Redux Toolkitreact-redux
  • Redux Toolkit:(RTK)
    • 官方推荐编写 Redux 逻辑的方式,是一套工具的集合,简化书写方式
      • 简化 store配置方式
      • 内置 immer 支持 可变式状态修改
      • 内置 thunk 更好的 异步创建
    • 安装
      • npm install @reduxjs/toolkit
  • react-redux
    • 用来链接 ReduxReact组件 的中间件;
    • React全家桶 - 【Redux】 - 环境准备、使用步骤、提交action传参、异步状态操作
    • 安装
      • npm install react-redux
  • 统一安装
    • npm i @reduxjs/toolkit react-redux

1.2 store目录结构设计

  • React全家桶 - 【Redux】 - 环境准备、使用步骤、提交action传参、异步状态操作
  • 通常集中状态管理的部分都会单独创建一个 store 目录;
  • 应用通常会有很多个子 store 模块,所以创建一个 modules 目录,在内部编写业务分类的子 store
  • store 中的入口文件 index.js 的作用是组合 modules 中所有的子模块,并导出 store

二、创建 并 注入 store 实例

  • 基于上篇文章,实现点击按钮,数量count变化;
  • counterStore模块:里面写主要的逻辑,在当前案例,主要写的是增和减;
  • 整体流程如下图所示:(同步修改
    • React全家桶 - 【Redux】 - 环境准备、使用步骤、提交action传参、异步状态操作

2.1 创建 store 实例

2.1.1 创建 子store 实例

  • 目标文件:
    • src/store/modules/counterStore.js;
    • 子store:
// 导入创建 store 的方法
import { createSlice } from '@reduxjs/toolkit';

const counterStore = createSlice({
  // 必填项
  name: 'counter',

  // 初始状态数据
  initialState: {
    count: 0
  },

  // 修改数据 - 同步方法
  reducers: {
    // 增方法
    increment: (state) => {
      state.count++;
    },

    // 减方法
    decrement: (state) => {
      state.count--;
    }
  }
});

// 解构出创建 action 对象的函数(actionCreater)
// 并以 按需导出 的方式 导出 actionCreater
export const { increment, decrement } = counterStore.actions;

// 以默认导出的方式导出 reducer函数
export default counterStore.reducer;

2.1.2 组合子store实例, 创建 根store实例

  • 目标文件:
      • src/store/index.js;
    • 根store:
    // 导入组合函数
    import { configureStore } from '@reduxjs/toolkit';
    // 导入 子store 模块
    import counterReducer from './modules/counterStore';
    
    // 该方法会生成一个 根store
    const store = configureStore({
      reducer: {
        // counter ===> 子store模块名:后面在组件中使用store数据的时候会用到
        counter: counterReducer
      }
    });
    
    export default store;
    
  • 优化
    • 在后续,我们需要使用对应store模块中的数据,我们都是从 ./store/modules/xxx.js 中进行按需导入;如果一个文件中,需要使用不同模块的数据,这样就显得很麻烦;
    • 优化处理
      • 我们可以将子模块中所有的按需导出,先导入到 ./store/index.js 中,然后从 index.js 中导出;
      // 只需在 inde下.js 中添加此代码即可
      export * from './modules/xxx';
      

2.2 为 React 注入 store 实例

recat-redux 负责把 ReduxReact 链接起来,内置 Provider组件(该插件的内置组件) 通过 store 参数 把创建好的 store实例 注入到应用中,链接正式建立;

  • 目标文件:
    • src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

// 导入 根store
import store from './store';
// 导入 react-redux 的内置组件 Provider
import { Provider } from 'react-redux';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    {/* 使用 Provider内置组件,将 App根组件 包裹起来,使用 store 参数,将 根store实例 传入组件 */}
    {/* 配合 路由 使用的时候,需使用 Provider 组件包裹路由的 RouterProvider 组件*/}
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

三、Redux 与 React - 基本使用步骤

3.1 React组件 使用 store 中的数据

  • 在 React组件 中使用 store 中的数据,需要用到一个钩子函数 useSelector
  • useSelector 作用:
    • store中的数据 映射到 组件中;
    • 注意
      • useSelector 只能写在 Hook 内;
  • 使用样例如下: React全家桶 - 【Redux】 - 环境准备、使用步骤、提交action传参、异步状态操作
  • 代码展示:
    • 目标文件:App.js
    import logo from './logo.svg';
    import './App.css';
    import { useSelector } from 'react-redux';
    
    function App() {
      // 使用 store 中的数据
      const { count } = useSelector((state) => state.counter);
    
      return (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p>{count}</p>
          </header>
        </div>
      );
    }
    
    export default App;
    

3.2 React组件 修改 store中的数据

  • redux 中,有且仅有 一种 修改store数据 的 方式,提交action
  • React组件修改 store中的数据 需要借助另外一个 hook函数 - useDispatch
  • useDispatch
    • 作用:生成提交 action对象dispatch函数
  • 代码展示:
    • 目标文件:App.js
    import logo from './logo.svg';
    import './App.css';
    import { useSelector, useDispatch } from 'react-redux';
    // DONE 导入创建action对象的方法
    import { decrement, increment } from './store';
    
    function App() {
      // DONE 使用 store 中的数据
      const { count } = useSelector((state) => state.counter);
    
      // DONE 得到 dispatch 函数
      const dispatch = useDispatch();
    
      return (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <hr />
            <div>
              {/* DONE 调用 dispatch 函数提交 action */}
              {/* dispatch函数需要的参数是 action 对象,action对象是通过 decrement / increment (actionCreate)方法得到的 */}
              {/* decrement / increment 方法一定要执行,只有执行了才能得到 action 对象 */}
              <button onClick={() => dispatch(decrement())}>-</button>
              <button onClick={() => dispatch(increment())}>+</button>
              <p>{count}</p>
            </div>
          </header>
        </div>
      );
    }
    
    export default App;
    

三、Redux 与 React - 提交action传参

  • 在上述案例中,我们虽然使用 dispatch 提交了 action对象,但是没有传参,现在就来看一下提交action的同时传递参数的情况;
  • 本节也是结合小案例去看;
  • 需求
    • 组件中有两个按钮,add to 10add to 20可以直接把 count 值修改到对应的数字,目标 count 值是在组件中传递过去的,需要在 提交action 的时候 传递参数;
  • 实现方法 🎯:
    • reducers同步修改方法 中添加 action对象参数,在 调用actionCreate 的时候 传递参数,参数会被传递到 action对象 payload属性 上
    • 注意
      • 如果没有传递参数,action.payload = undefined
      • 如果传递了参数,action.payload = 传递的参数
  • 代码展示:
    • React全家桶 - 【Redux】 - 环境准备、使用步骤、提交action传参、异步状态操作
    • 目标文件:src/store/modules/counterStore.js
      // 导入创建 store 的方法
      import { createSlice } from '@reduxjs/toolkit';
      
      const counterStore = createSlice({
        name: 'counter',
      
        // 初始状态数据
        initialState: {
          count: 0
        },
      
        // 修改数据 - 同步方法
        // 支持直接修改
        reducers: {
          /** 加法 */
          increment: (state, { payload }) => {
            state.count += payload || 1;
          },
      
          /** 减法 */
          decrement: (state) => {
            state.count--;
          },
          
          /** 增加指定数量 */
          addToNum(state, action) {
            state.count += action.payload;
          }
        }
      });
      
      // 解构出创建 action 对象的函数(actionCreater)
      const { increment, decrement, addToNum } = counterStore.actions;
      
      // 获取 reducer 函数
      const counterReducer = counterStore.reducer;
      
      // 以 按需导出 的方式 导出 actionCreater
      export { increment, decrement, addToNum };
      
      // 以默认导出的方式导出 reducer函数
      export default counterReducer;
      
    • 目标文件:src/App.js
      import logo from './logo.svg';
      import './App.css';
      import { useSelector, useDispatch } from 'react-redux';
      // DONE 导入创建action对象的方法
      import { decrement, increment, addToNum } from './store';
      
      function App() {
        // DONE 使用 store 中的数据
        const { count } = useSelector((state) => state.counter);
      
        // DONE 得到 dispatch 函数
        const dispatch = useDispatch();
      
        return (
          <div className="App">
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <hr />
              <div>
                {/* DONE 调用 dispatch 函数提交 action */}
                {/* dispatch函数需要的参数是 action 对象,action对象是通过 decrement / increment (actionCreate)方法得到的 */}
                <button onClick={() => dispatch(decrement())}>-</button>
                <button onClick={() => dispatch(increment())}>+</button>
                <p>{count}</p>
              </div>
              <hr />
              <div>
                <p>{count}</p>
                <button onClick={() => dispatch(increment(10))}>add to 10</button>
                <button onClick={() => dispatch(addToNum(20))}>add to 20</button>
              </div>
            </header>
          </div>
        );
      }
      
      export default App;
      

四、Redux 与 React - 异步状态操作

  • 之前做的都是同步操作,都是同步修改state,但是实际的开发中,我们还有异步的操作(等到接口获取数据之后进进行操作);
  • 还是用小案例的形式来说异步操作状态;
  • 需求
    • 请求接口,等接口返回数据之后,将数据使用redux保存起来;
    • 使用store中的数据,渲染列表;
  • 基本流程:
    • 与之前同步修改的流程基本一致,就增加了一个修改的配置;
    • React全家桶 - 【Redux】 - 环境准备、使用步骤、提交action传参、异步状态操作
  • 基本步骤
    • 创建 store 的写法保持不变,配置同步修改状态的方法;
    • 单独封装一个函数,在函数欸不 return 一个函数,在新函数中:
      • 封装异步请求,获取数据;
      • 调用同步 actionCreater 传入异步数据生成一个 action 对象,并使用 dispatch 提交;
    • 组件中 dispatch 的写法保持不变;

五、持久化Redux数据

  • 现存问题
    • Redux 存入数据之后,如果 刷新浏览器,数据就会丢失;
    • 持久化就是防止数据丢失;
    • 不管对token如何赋值,只要刷新了浏览器,tokenInfo中的属性值就会恢复为空串;
    initialState: {
       tokenInfo: {
           token: '',
           refresh_token: ''
       }
    }
    
  • 问题原因
    • Redux基于浏览器内存存储方式刷新时 状态恢复为 初始值
  • 解决问题
    • 获取并存储数据:
      • 将获取到的数据存入 Redux + localStorage.setItem(xxx)
    • 初始化数据:
      • localStorage.getItem(xxx) ? localStorage.getItem(xxx) : 初始值
  • 并不一定是要localStorage,铁子们可根据项目要求选择;