一、环境准备
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
;
安装完成之后,启动项目的时候,可能会报错,报错信息如下:
swift[eslint] Plugin "react" was conflicted between "package.json >> eslint-config-react-app >> D:\Table top\training-project\React\training-project\react-redux-pro\node_modules\eslint- config-react-app\base.js" and "BaseConfig >> D:\Table top\training-project\React\training- project\react-redux-pro\node_modules\.pnpm\eslint-config-react-app@7.0.1_@babel+plugin- syntax-flow@7.23.3_@babel+plugin-transform-react- _ymo2r6j4jsijv4xl6xbyks3h6q\node_modules\eslint-config-react-app\base.js".
这时我们找到项目里的
package.json
文件,摁下Ctrl + S
保存,就好了(我的就是这么解决了,如果这样不行,大家可以自行百度一下哈);
1.2 store目录结构设计
- 通常集中状态管理的部分都会单独创建一个
store
目录; - 应用通常会有很多个子
store
模块,所以创建一个modules
目录,在内部编写业务分类的子store
; store
中的入口文件index.js
的作用是组合modules
中所有的子模块,并导出store
;
二、Redux 与 React - 基本使用步骤
- 基于上篇文章,实现点击按钮,数量count变化;
- counterStore模块:里面写主要的逻辑,在当前案例,主要写的是增和减;
- 整体流程如下图所示:(同步修改 )
2.1 使用 React Toolkit 创建 counterStore
-
目标文件:
src/store/modules/counterStore.js
;- 子store:
js// 导入创建 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) const { increment, decrement } = counterStore.actions; // 获取 reducer 函数 const counterReducer = counterStore.reducer; // 以 按需导出 的方式 导出 actionCreater export { increment, decrement }; // 以默认导出的方式导出 reducer函数 export default counterReducer;
-
目标文件:
*src/store/index.js
;- 根store:
js// 导入组合函数 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
中导出;
js// 只需在 inde下.js 中添加此代码即可 export * from './modules/xxx';
- 我们可以将子模块中所有的按需导出,先导入到
-
2.2 为 React 注入 store
recat-redux
负责把Redux
和React
链接起来,内置Provider
组件 (该插件的内置组件) 通过store
参数 把创建好的store
实例 注入到应用中,链接正式建立;
-
目标文件:
src/index.js
;
jsimport React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; // 导入 根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 store={store}> <App /> </Provider> </React.StrictMode> ); reportWebVitals();
2.3 React组件 使用 store 中的数据
- 在 React组件 中使用 store 中的数据,需要用到一个钩子函数
useSelector
:useSelector
作用:
- 把
store
中的数据 映射到 组件中;- ❗ 注意 :
useSelector
只能写在 Hook 内;- 使用样例如下:
-
代码展示:
- 目标文件:
App.js
;
jsimport 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;
- 目标文件:
2.4 React组件 修改 store中的数据
- 在 redux 中,有且仅有 一种 修改store数据 的 方式,提交action;
- React组件 中 修改 store中的数据 需要借助另外一个 hook函数 -
useDispatch
:useDispatch
:
- 作用 :生成提交
action
对象 的dispatch
函数;
-
代码展示:
- 目标文件:
App.js
;
jsximport 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
;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
;jsximport 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
的写法保持不变;