reduxjs/toolkit(RTK)是 Redux 官方团队推出的一个工具集,旨在简化 Redux 的使用和配置。它于 2019 年 10 月 正式发布,此文章记录一下redux的旧版本如何使用,以及引入等等。
文件目录如下:
步骤
-
安装依赖:
npm install redux react-redux redux-thunk
-
设置 Redux Store:
创建store.js
文件并配置createStore
。
使用combineReducers
合并多个 reducer。
使用applyMiddleware
添加thunk
中间件。 -
创建 Actions:
定义 action 类型和 action 创建函数。 -
创建 Reducers:
根据 action 类型更新 state。 -
连接 React 组件到 Redux Store:
使用Provider
组件将 store 提供给整个应用。
使用connect
函数将组件连接到 Redux store,并映射 state 和 dispatch 方法到组件的 props。 -
使用 Redux DevTools:
配置 Redux DevTools 以方便调试。
1. 安装必要的依赖
安装 redux
、react-redux
和 redux-thunk
(用于处理异步操作)。
npx create-react-app redux-test
cd redux-test
npm install redux react-redux redux-thunk
2. 设置 Redux Store
创建一个 store.js
文件来配置 Redux store
applyMiddleware
用于在 Redux store 中应用中间件,中间件在 Redux 的 action 被分发到 reducer 之前拦截这些 action,可以扩展 Redux 的功能,实现诸如异步操作(使用 redux-thunk 或 redux-saga)、路由控制、日志记录等。
applyMiddleware工作原理
1.Action 分发:
当一个 action 被分发时,它首先会被传递给中间件。
2.中间件链:
中间件按照应用的顺序形成一个链,每个中间件可以处理 action 并决定是否继续传递给下一个中间件或 reducer。
3.处理逻辑:
每个中间件可以执行自定义逻辑,例如记录日志、处理异步操作等。
4.传递 Action:
最终,action 会被传递给 reducer 进行状态更新
src/redux/store.js
/**
* 该文件专门用于暴露一个store对象,整个应用只有一个store对象
* */
// 引入createStore,专门用于创建redux中最为核心的 store
import { createStore,applyMiddleware } from 'redux'
// 引入总的Reducers
import Reducers from './reducers/index'
// 引入redux-thunk插件,用于支持异步action
// redux-thunk 允许你返回一个函数而不是一个普通的 action 对象。 ==>体现在action的返回值上,主要处理异步action
import { thunk } from 'redux-thunk'
// 引入redux-devtools-extension插件,用于支持redux调试工具
import { composeWithDevTools } from "redux-devtools-extension"
// 暴露store
export default createStore(Reducers,composeWithDevTools(applyMiddleware(thunk)))
src/redux/reducers/index.js
combineReducers
汇总所有的reducer变为一个总的reducer
/**
* 该文件用于汇总所有的reducer为一个总的reducer
**/
// 引入combineReducers
import { combineReducers } from "redux";
// 引入为Count组件服务的reducer
import count from "./count";
// 引入Person组件服务的的reducer
import persons from "./person";
// 汇总所有的reducer变成一个总的reducer
export default combineReducers({
count,
persons
})
// export default 全局暴露的对象,可以自定义暴露对象的名称
3. 创建 Actions
src/common/common.js
// 该模块仅用于定义常量,其他模块导入该常量即可
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
export const ADD_INFO = 'personInfo'
src/redux/actions/count.js
import {INCREMENT,DECREMENT} from "../../common/common";
// import store from "./store";
/**
* 该文件专门为Count组件生成action对象
*
**/
/**
* 同步action:action的值为Object类型的一般对象
* */
export const increment = (data)=>({type: INCREMENT, data})
export const decrement = (data)=>({type: DECREMENT, data})
/**
* 异步action:action的值为函数类型,异步action中可以调用同步action,也可以自己写异步代码
**/
export const incrementAsync = (data,time)=>{
// createStore(countReducer,applyMiddleware(thunk))
// applyMiddleware中可以传入多个中间件,中间件是一个函数,该函数的参数是store,所以此处无需传入store
return (dispatch)=>{
// console.log(dispatch);
setTimeout(()=>{
// store.dispatch(incrementAction(data))
dispatch(increment(data))
},time)
}
}
src/redux/actions/person.js
import { ADD_INFO } from "../../common/common";
export const addPerson = (personObj)=>({type:ADD_INFO,personObj})
4. 创建 Reducers
创建 countReducer.js
和 personReducer.js
文件来处理 actions 并更新 state。
src/redux/reducers/count.js
/**
* 1.该文件适用于创建一个为Count组件服务的redux模块中的reducer,reducer的本质是一个函数
* 2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
* **/
import {INCREMENT,DECREMENT} from "../../common/common";
let initialState = 0; // 数据初始化
export default function countReducer(preState =initialState, action) {
// 从action对象中获取:type,data
let { type, data } = action
// console.log(preState,action); // 初次获取0,"@@redux/INITx.1.n.3.n.9"
// 根据type决定如何加工数据
switch (type){
case INCREMENT:
// console.log(preState);
return preState + data*1;
case DECREMENT:
return preState - data*1;
default:
return preState;// 初始化数据·
}
}
src/redux/reducers/person.js
Redux 的 reducer 必须是纯函数,这意味着它不能修改传入的参数(即 preState),而是必须返回一个新的状态对象。
纯函数的定义:
- 不能改变传递过来的参数的原始值
- 只能通过返回值返回结果
- 状态不可变性
// 数据参数
import { ADD_INFO } from "../../common/common"
const initPersonInfo =[] // 初始化列表数据
const personReducer = (preState=initPersonInfo,action)=>{
let {type,personObj} = action // 此处解构
switch (type){
case ADD_INFO: // 若是添加数据
// preState.unshift(personObj) // 修改的原数组,导致preState参数被改写了,
// personReducer就不是纯函数;且preState的指向地址没有发生变化,所以不会引起界面更新
// return preState // vue中也是浅拷贝,但是vue中会进行深拷贝,所以界面更新
// react 不会引起界面更新,因为指向地址并没有发生变化;--浅拷贝--修改引用指针
return [personObj,...preState] // 浅拷贝,此时preState指向新的地址,界面更新
// 界面的更新比较的是两个对象的存储位置,浅比较
default:
return preState
}
}
// 纯函数的定义:1.不能改变 传递过来的参数 的原始值 2.只能通过返回值返回结果
// 不管调用多少次,都只会返回一个结果
export default personReducer
// export default 语句的语法不允许直接在 export default 后面使用 const 或 let 声明变量。
// export default 语句可以直接导出一个表达式或一个函数,但不能直接导出一个带有 const 或 let 声明的变量。
export default
语句可以直接导出一个表达式或一个函数,但不能直接导出一个带有 const 或 let 声明的变量。
5. 连接 React 组件到 Redux Store
src/App.jsx
import React, {Component} from 'react';
// 引入容器组件
import Count from './container/Count/index'
import Person from './container/Person/index'
// 引入store;传递给容器组件
class App extends Component {
render() {
return (
<div>
<Count/>
<Person/>
</div>
);
}
}
export default App;
src/index.js
// 引入核心库
import React from 'react';
// 创建根节点
import { createRoot } from 'react-dom/client';
import store from "./redux/store";
// 使用Provider组件, 将store传递给APP组件,全局状态管理
import { Provider } from "react-redux"
// 引入文件
import App from './App';
// 创建容器
const container = document.getElementById('root'); // 外壳
const root = createRoot(container); // 创建根节点
root.render(
/* 此处需要Provider包裹App,让App所有的后代容器组件都能接收到store */
<Provider store={store}>
<App />
</Provider>
);
src/container/Count/index.jsx
connect
作用:
- 连接组件到 Redux Store:
connect 函数将 React 组件与 Redux store 连接起来,使得组件能够访问 store 中的状态和分发 actions。 - 传递状态到组件:
通过 mapStateToProps 函数,将 Redux store 中的状态映射到组件的 props。 - 传递 dispatch 方法到组件:
通过 mapDispatchToProps 函数,将 dispatch 方法映射到组件的 props,使得组件能够分发 actions。 - 优化渲染性能:
connect 会自动处理组件的订阅和取消订阅,确保组件只在相关状态变化时重新渲染。
mapStateToProps
- mapStateToProps函数 返回的是一个对象;
- 返回的对象中的key就作为UI组件props的key,value就作为传递UI组件props的value
- mapStateToProps用于传递状态
mapStateToDispatch
- mapStateToDispatch函数 返回的是一个对象;
- 返回的对象中的key就作为UI组件props的key,value就作为传递UI组件props的value
- mapStateToDispatch用于传递操作状态的方法
// connect()(CountUI) ==> 容器container
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
import React,{Component} from "react";
/**
* 引入react-redux中connect函数,连接 UI组件和redyx(store)
**/
import { connect } from "react-redux";
// store通过Provider传递引入
// 引入action
import {increment,decrement,incrementAsync} from "../../redux/actions/count"
// 容器通过 store 获取状态数据 ,传递给UI组件,UI组件通过props获取
class Count extends Component{
incrementNum = ()=>{
// 函数
let { value } = this.selectNum
this.props.increment(value)
}
decrementNum = ()=>{
// 函数
let { value } = this.selectNum
this.props.decrement(value)
}
// 奇数时加
incrementOddNum = ()=>{
// 函数
let { value } = this.selectNum
let count = this.props.count
if(count % 2 !== 0){
this.props.increment(value)
}
}
incrementAsync = ()=>{
// 函数
let { value } = this.selectNum // 字符串形式需要转换,否则默认会字符串拼接
this.props.incrementAsync(value,500)
}
render(){
return(
<div>
<h2>我是Count组件</h2>
<h1>求和的数值:{this.props.count}</h1>
<select ref={(c) => {
this.selectNum = c
}}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.incrementNum}>+</button>
<button onClick={this.decrementNum}>-</button>
<button onClick={this.incrementOddNum}>求和为基数时再加+</button>
<button onClick={this.incrementAsync}>异步再加+</button>
</div>
)
}
}
export default connect(
state=>{
// Uncaught Error: Objects are not valid as a React child (found: object with keys {countNum}). If you meant to render a collection of children, use an array instead.
return { count:state.count} // 映射到UI组件的props,返回的数值必须是一个对象且需要取准确的对象,store 统一管理数据
},
{increment, decrement, incrementAsync}
)(Count)
src/container/Person/index.jsx
import React, {Component} from 'react';
import {addPerson} from '../../redux/actions/person'
import {connect} from 'react-redux'
// 使用nanoid 生成唯一id
import {nanoid} from "nanoid";
class Person extends Component {
// 在类组件中,方法默认不会自动绑定 this。
// 在构造函数中手动绑定 this
// constructor(props) {
// super(props);
// this.addUserInfo = this.addUserInfo.bind(this); // 手动绑定this
// }
// addUserInfo (){
// console.log(this.nameNode.value,this.ageNode.value,6665)
// }
// 使用箭头函数来自动绑定 this。
addUserInfo = ()=>{
let name = this.nameNode.value
let age = this.ageNode.value
let id = nanoid()
this.props.addPerson({id,name,age}) // 传递参数
this.nameNode.value = ''
this.ageNode.value = ''
console.log(this.props)
}
render() {
return (
<div>
<h2>我是person组件</h2>
<input ref={c=>this.nameNode = c} type="text" placeholder="请输入姓名" />
<input ref={c=>this.ageNode = c} type="text" placeholder="请输入年龄" />
<button onClick={this.addUserInfo}>添加个人信息</button>
<ul>
{ // 需要花括号遍历数组
this.props.persons.map((item)=>{
return <li key={item.id}>id:{nanoid()}-姓名:{item.name} - 年龄:{item.age}</li>
})
}
</ul>
</div>
);
}
}
export default connect(
state=>({ persons:state.persons}),
{addPerson})(Person)
6. 使用 Redux DevTools
参考链接:https://blog.csdn.net/pikaqiu_komorebi/article/details/145908046
自用
同步action,是指action的值为Object类型的一般对象。
异步action,是指action的值为函数 =⇒主要是因为函数能开启异步任务。
const a = b⇒({data:b})
求和案例_redux精简版
-
去除Count组件自身的状态
-
src下建
-redux ( -store.js -count_reducer.js ) -
store.js:
1)reducer本质就是一个函数,接受:preState,action,返回加工后的状态
2)reducer有两个作用:初始化状态,加工状态
3)reducer被第一次调用时,是store自动触发的,传递prestate是undefined -
count_reducer.js
1)reducer本质就是一个函数,接受:preState,action,返回加工后的状态 2)reducer有两个作用:初始化状态,加工状态 3)reducer被第一次调用时,是store自动触发的,传递prestate是undefined
-
在
index.js
中检测store中状态的改变,一旦发生改变重新渲染
// 监听redux中状态的改变,如redux的状态发生了改变,重新渲染App组件
store.subscribe(()=>{
root.render(<App />); // 渲染应用
})
备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,需要自己去写
求和案例_redux异步action 版
-
明确:延迟的动作不想交给组件的自身,交给action ⇒ 异步动作
-
何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回(非必须)
-
具体编码:
1)yarn add redux-thunk,并配置在store中; 使用redux的中间件 applyMiddleware ,用于添加中间件redux-thunk功能。 2)创建action 的函数不再返回一般对象,而是一个函数,该函数中写异步任务 3)异步任务有结果后,分发一个同步的action 去真正操作数据
-
备注:异步action不是必须要写的,完全可以自己等待异步任务的结果再去分发同步action
求和案例_react-redux基本使用
-
明确两个概念
1)UI组件:不能使用任何redux的api,只负责页面的呈现、交互等 2)容器组件:负责和redux通信,并将结果交给UI组件
-
如何创建一个容器组件 — — — 靠react-redux的connect函数
connect( mapStateToProps,mapDispatchToProps )( UI组件 ) - mapStateToProps:映射状态,返回值是一个对象 - mapStateToProps:映射操作状态的方法,返回值是一个对象 UI组件通过props获取数据和方法;容器组件通过mapStateToProps,mapDispatchToProps传递
-
备注1:容器组件中的store是靠APP的props传进去的,而不是在容器中直接引入
-
备注2:mapDispatchToProps,也可以是一个对象,redux-redux内部调用分发action
-
备注3:
容器组件能够自动检测store数据的变化
,可去除store.subscribe(()⇒{})方法 -
Provider能够给APP中所有的容器传递store,无需手动对容器组件传递store对象
-
合并文件,将容器组件和UI组件放到一起
求和案例_react-redux优化
- 容器组件和UI组件混成一个文件
- 无需手动给容器组件传递store,在index.js中给包裹一个即可。
// 引入核心库
import React from 'react';
// 创建根节点
import { createRoot } from 'react-dom/client';
import store from "./redux/store";
import { Provider } from "react-redux"
// 引入文件
import App from './App';
// 创建容器
const container = document.getElementById('root'); // 外壳
const root = createRoot(container); // 创建根节点root.render(
<Provider store={store}>
<App />
</Provider>
);
- 使用了react-redux后不用自己监测redux中状态的改变,容器组件可自动监测
mapStateToDispatch
也可以简单的写成一个action对象,redux-redux可内部调用分发action- 一个组件要和redux “打交道” 要经过哪几步
1)定义好UI组件——不暴露
2)引入connect生成容器组件,并暴露,写法如下
3)在UI组件中通过this.props.xxxx读取状态和操作方法
connect(
state⇒({key:value}), // 映射状态;value数值要根据总的Reducers取值
(key:xxxAction) // 映射操作状态的方法
)(UI组件)
求和案例_react-redux数据共享版
- 定义一个Person组件,和Count组件通过redux共享数据;
- 为Person组件编写:reducer、action、配置common常量
- 重点:Person的Reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象
// 引入createStore,专门用于创建redux中最为核心的 store
import { createStore,applyMiddleware,combineReducers } from 'redux'
// 引入为Count组件服务的reducer
import countReducer from "./reducers/count"
import personReducer from "./reducers/person"
// 引入redux-thunk插件,用于支持异步action
import { thunk } from 'redux-thunk'
// combineReducers汇总所有的reducer变为一个总的reducer
const allReducers = combineReducers({
countNum:countReducer,
addPerson:personReducer
})
// 暴露store
export default createStore(allReducers,applyMiddleware(thunk))
- 交给store的是总reducer,最后注意在组件中取出状态的时候,取到位
求和案例_react-redux最终版
- 所有变量名称尽量触发对象的简写形式
- reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer