React全家桶 - 【Redux】 - 环境准备、使用步骤、提交action传参、异步状态操作
站长
· 阅读数 4
一、环境准备
1.1 安装插件
- 在 React 中使用 Redux,官方要求安装两个插件
Redux Toolkit
和react-redux
; Redux Toolkit
:(RTK)- 官方推荐编写 Redux 逻辑的方式,是一套工具的集合,简化书写方式;
- 简化
store
的 配置方式; - 内置
immer
支持 可变式状态修改; - 内置
thunk
更好的 异步创建;
- 简化
- 安装:
npm install @reduxjs/toolkit
- 官方推荐编写 Redux 逻辑的方式,是一套工具的集合,简化书写方式;
react-redux
:- 用来链接
Redux
和React组件
的中间件; - 安装:
npm install react-redux
;
- 用来链接
- ✅ 统一安装:
npm i @reduxjs/toolkit react-redux
;
1.2 store目录结构设计
- 通常集中状态管理的部分都会单独创建一个
store
目录; - 应用通常会有很多个子
store
模块,所以创建一个modules
目录,在内部编写业务分类的子store
; store
中的入口文件index.js
的作用是组合modules
中所有的子模块,并导出store
;
二、创建 并 注入 store 实例
- 基于上篇文章,实现点击按钮,数量count变化;
- counterStore模块:里面写主要的逻辑,在当前案例,主要写的是增和减;
- 整体流程如下图所示:(同步修改)
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';
- 我们可以将子模块中所有的按需导出,先导入到
- 在后续,我们需要使用对应store模块中的数据,我们都是从
2.2 为 React 注入 store 实例
recat-redux
负责把Redux
和React
链接起来,内置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 内;- 使用样例如下:
- 代码展示:
- 目标文件:
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 10
,add to 20
可以直接把count
值修改到对应的数字,目标count
值是在组件中传递过去的,需要在 提交action
的时候 传递参数;
- ❗ 实现方法 🎯:
- 在
reducers
的 同步修改方法 中添加action
对象参数,在 调用actionCreate
的时候 传递参数,参数会被传递到action
对象payload
属性 上; - 注意:
- 如果没有传递参数,
action.payload = undefined
; - 如果传递了参数,
action.payload = 传递的参数
- 如果没有传递参数,
- 在
- 代码展示:
- 目标文件:
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中的数据,渲染列表;
- 基本流程:
- 与之前同步修改的流程基本一致,就增加了一个修改的配置;
- 基本步骤:
- 创建
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
,铁子们可根据项目要求选择;