网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
一个组件需要改变另一个组件的状态(通信)
总之:redux能不用就不用,当你项目中不用比较吃力的时候,才考虑使用。
其原理图如下所示:
由上图的原理图可以看出redux有三个核心概念:
actionCreators:动作的对象,包含两个属性,type:标识属性,值为字符串,是唯一必要属性;data:数据属性,值类型任意,可选属性。
Reducers:用于初始化状态、加工状态,加工时,根据旧的state和action,产生新的state的纯函数。
Store:将state、action、reducer联系在一起的对象。
redux的安装与使用
如果你是刚创建的项目而且想使用redux,推荐使用官方给出的方法,在创建react脚手架的同时把redux也下载好,也可以同时搭配TS来使用:
如果是已经成型的项目,直接终端运行如下命令即可:
# NPM
npm install redux
# Yarn
yarn add redux
可以看到当我们安装完成后,redux已经来到了4.2版本,然而该版本将原始 createStore API 标记为 @deprecated(废弃),为了兼容低版本代码,官方不会真正的删除createStoreAPI,只是将该 API 标记为已弃用(@deprecated)。并且添加了一个全新的 configureStore API,此外该版本鼓励用户迁移到 Redux Toolkit。
Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法。它围绕 Redux 核心,并包含对于构建 Redux 应用必不可少的软件包和功能。Redux Toolkit 建立在最佳实践中,简化了大多数 Redux 任务,防止了常见错误,并使编写 Redux 应用程序更加容易。但是许多老教程仍然使用过时的Redux手写模式,如果你使用老版本并且想消除删除线,可以切换 legacy_createStore 模式导出的 API,此种模式下没有@deprecation标签。
本文会先讲解一下老版本的使用,然后再简单提及一下 Redux Toolkit 的使用,如下:
在src目录下新建 redux 文件夹,里面存放着加工数据的reducer和接收加工数据的store文件夹:
// store.js文件,该文件专门用于暴露一个store对象,整个应用只有一个store对象
// 切换 legacy_createStore 模式导出的 API,此种模式下没有(警告)@deprecation标签。
import { legacy_createStore as createStore } from "redux";
// 引入处理count的reducer
import countReducer from './countReducer.js'
// 暴露store
export default createStore(countReducer)
// countReducer.js文件,该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
// reducer函数会接到两个参数,分别是:之前的状态(preState),动作对象(action)
export default function countReducer(preState,action){
// 从action中获取:type、data
const {type,data} = action
// 根据type决定如何加工数据
switch (type) {
case 'increment': // 如果是加
return preState + data
case 'decrement': // 如果是减
return preState - data
default:
return 0 // 页面的初始数据
}
}
创建好redux相关文件之后,我们直接要在使用store状态的组件中引入即可,因为redux只处理数据状态,并不会重新渲染,所以需要我们进行手动渲染设置,如下:
import React, { Component } from 'react'
// 引入store,用于获取redux中保存状态
import store from '../../redux/store.js'
export default class Count extends Component {
componentDidMount(){
// 检测redux中状态的变化,只要变化就调用render
store.subscribe(()=>{
// this.setState({}) // 什么也不传值,只调用也能重新调用render渲染页面
this.forceUpdate() // 强制刷新
})
}
increment = () =>{
const {value} = this.selectNumber
// 通过dispatch来分配任务
store.dispatch({type:'increment',data:value*1})
}
decrement = () =>{
const {value} = this.selectNumber
store.dispatch({type:'decrement',data:value*1})
}
incrementIfOdd = () =>{
const {value} = this.selectNumber
const count = store.getState()
if(count%2!==0){
store.dispatch({type:'increment',data:value*1})
}
}
incrementAsync = () =>{
const {value} = this.selectNumber
setTimeout(()=>{
store.dispatch({type:'increment',data:value*1})
},500)
}
render() {
return (
<div style={{padding:'15px'}}>
{/* 通过getState这个API获取数据 */}
<h1>当前求和为:{store.getState()}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
如果要使用redux状态的组件有很多,每个组件都写componentDidMount来重新渲染页面会很繁琐,推荐将监听store变化的函数写在入口文件,如下:
import React from 'react';
import ReactDOM from "react-dom";
import App from './App'
import store from './redux/store.js'
ReactDOM.render(<App />,document.getElementById('root'))
// 检测redux中状态的变化,只要变化就调用render
store.subscribe(()=>{
ReactDOM.render(<App />,document.getElementById('root'))
})
接下来使用actionCreators来告知store要传入的动作的对象,在redux文件夹下新建count_action文件,如下:
// count_action.js文件,该文件专门为Count组件生成action对象
// 完整书写
export const createIncrementAction = (data)=>{
return {type:'increment',data}
}
// 精简书写
export const createDecrementAction = data => ({type:'decrement',data})
将创建好的actionCreators引入要使用store的Count组件,将原本的dispatch要写的对象,替换掉
在日常开发中,如果文件的数量太多,要书写很多常量,可以在redux文件夹下新建一个文件来专门处理常量问题,放置容易写错的type值,防止程序员因为粗心而将单词写错,也便于以后的管理:
处理好常量之后,在使用type和data的对象的actionCreators和Reducer文件引入即可:
处理异步action
同步action就是我们平常在调用actionCreators时,dispatch传入的type和data对象。
什么是异步action呢?举个例子:component组件相当于顾客,actionCreators相当于服务员,store相当于老板,reducers相当于厨师,顾客去饭店点餐,告诉服务员,5分钟后再上菜,掐时间的是服务员,这个就属于异步action,当然顾客也能去饭店等五分钟再告诉服务员上菜,而这需要顾客自己掐时间,也能达到相同的效果,但是顾客为啥要掐时间呢?服务员是服务我们的,应该需要他去劳作,哈哈,大致就是这个意思,但是怎么做呢?如下:将action文件设置一个处理异步任务的函数。
// count_action.js文件,该文件专门为Count组件生成action对象
import { INCREMENT,DECREMENT } from "./constant"
import store from '../redux/store'
// 完整书写
export const createIncrementAction = (data)=>{
return {type:INCREMENT,data}
}
// 精简书写
export const createDecrementAction = data => ({type:DECREMENT,data})
// 异步的action
export const createIncrementAsyncAction = (data,time) =>{
// 函数柯里化,将函数的返回值暴露出来
return () =>{
setTimeout(()=>{
store.dispatch(createIncrementAction(data))
},time)
}
}
我们在调用函数的时候,直接传入具体实参即可:
而我们在调用这个异步任务后,控制台爆出如下错误: 其告诉我们action只能接收对象不能接收函数,如果要接收函数需要使用第三方中间件,并且名字在控制台也告诉了我们:redux-thunk
npm安装完 redux-thunk之后,在store文件中引入并使用即可,如下:
// store.js文件,该文件专门用于暴露一个store对象,整个应用只有一个store对象
// 切换 legacy_createStore 模式导出的 API,此种模式下没有(警告)@deprecation标签。
import { legacy_createStore as createStore,applyMiddleware } from "redux";
// 引入处理count的reducer
import countReducer from './countReducer.js'
// 引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
// 暴露store
export default createStore(countReducer,applyMiddleware(thunk))
注意:异步action不是必须要写的,完全可以自己等待异步任务的结果然后再去分发同步action。当我们想要对状态进行操作,但是具体的数据靠异步任务返回的时候可以采用异步action。
react-redux的使用
facebook公司发现,许多的react开发人员都倾向于使用redux进行集中式状态管理,所以其公司自行推出了react-redux插件库,便于react和redux进行协调开发,react-redux模型图如下:
根据原理图,我们还需要创建一个容器组件,将以前直接在UI组件库里面调用redux的API进行抽离出来,所有与redux相关的API全部放置在容器组件中进行,如下:
在书写容器组件代码之前,需要npm安装一下react-redux这个包,安装完成之后,我直接给出以下代码,用容器组件来连接UI组件,如下:
// 引入Count的UI组件
import CountUI from '../../components/Count'
import { createIncrementAction,createDecrementAction,createIncrementAsyncAction } from '../../redux/count_action'
// 引入connect用于连接UI组件于redux
import { connect } from 'react-redux'
/*
1.mapstateToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key, value就作为传递给UIT组件props的value
3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
return {count:state}
}
/*
1.mapDispatchToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key, value就作为传递给UI组件props的value
3.mapDispatchToProps用于传递操作状态的方法
*/
function mapDispatchToProps(dispatch){
return {
jia:(number)=>{dispatch(createIncrementAction(number))},
jian:(number)=>{dispatch(createDecrementAction(number))},
jiaAsync:(number,time)=>{dispatch(createIncrementAsyncAction(number,time))}
}
}
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
当然这个容器组件也可以采用简写方式,因为react-redux这个第三方库会自动帮助我们分发任务所以我们不需要在写dispatch,因为是函数传参,函数也能直接用箭头函数写在参数上:
// 引入Count的UI组件
import CountUI from '../../components/Count'
import { createIncrementAction,createDecrementAction,createIncrementAsyncAction } from '../../redux/count_action'
// 引入connect用于连接UI组件于redux
import { connect } from 'react-redux'
/*
1.mapstateToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key, value就作为传递给UIT组件props的value
3.mapStateToProps用于传递状态
*/
// function mapStateToProps(state){
// return {count:state}
// }
// 简写
// const mapStateToProps = state => ({count:state})
/*
1.mapDispatchToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key, value就作为传递给UI组件props的value
3.mapDispatchToProps用于传递操作状态的方法
*/
// function mapDispatchToProps(dispatch){
// return {
// jia:(number)=>{dispatch(createIncrementAction(number))},
// jian:(number)=>{dispatch(createDecrementAction(number))},
// jiaAsync:(number,time)=>{dispatch(createIncrementAsyncAction(number,time))}
// }
// }
// 简写
// const mapDispatchToProps = dispatch => ({
// jia:(number)=>{dispatch(createIncrementAction(number))},
// jian:(number)=>{dispatch(createDecrementAction(number))},
// jiaAsync:(number,time)=>{dispatch(createIncrementAsyncAction(number,time))}
// })
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count:state}),
// dispatch => ({
// jia:(number)=>{dispatch(createIncrementAction(number))},
// jian:(number)=>{dispatch(createDecrementAction(number))},
// jiaAsync:(number,time)=>{dispatch(createIncrementAsyncAction(number,time))}
// })
// 因为react-redux会帮助我们自动分发dispatch
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction
}
)(CountUI)
因为UI组件是被容器组件给包裹住的,所有需要我们在App.jsx中引入容器组件而不是UI组件,并且需要我们在容器组件中以props的形式传入store对象,以此来在容器组件中能使用store的API:
那如果容器组件如果有很多的情况下,每个容器组件都需要props传入store未免有些太过繁琐了,这里我们可以借助 Provider 组件来一次性处理所有问题,如下:
根据以上的操作,我们在UI组件中就不需要来进行redux中store相关API的书写了,直接书写容器组件中定义的操作状态的方法即可,如下:
import React, { Component } from 'react'
export default class Count extends Component {
increment = () =>{
const {value} = this.selectNumber
// 通过dispatch来分配任务
this.props.jia(value*1)
}
decrement = () =>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
incrementIfOdd = () =>{
const {value} = this.selectNumber
if(this.props.count % 2 !==0){
this.props.jia(value*1)
}
}
incrementAsync = () =>{
const {value} = this.selectNumber
this.props.jiaAsync(value*1,500)
}
render() {
// console.log(this.props);
return (
<div style={{padding:'15px'}}>
{/* 通过getState这个API获取数据 */}
<h1>当前求和为:{this.props.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
有了容器组件的出现,传统的需要在强制去调用render刷新页面已经不在需要了,如下:
写到这大家可能会有点疑惑,如果我们要是有20UI组件的话,是不是还需要写20个容器组件,这样不是导致我们的文件数过大吗,没错是这样,那么有什么办法来解决呢,其实我们完全可以将容器组件和UI组件整合在一起,因为react-redux并没有禁止我们将这两个组件写成一个啊,所有我们将之前的UI组件代码粘贴到容器组件中,如下:(完全没问题)
// 引入Count的UI组件
import { createIncrementAction,createDecrementAction,createIncrementAsyncAction } from '../../redux/count_action'
// 引入connect用于连接UI组件于redux
import { connect } from 'react-redux'
import React, { Component } from 'react'
class Count extends Component {
increment = () =>{
const {value} = this.selectNumber
// 通过dispatch来分配任务
this.props.jia(value*1)
}
decrement = () =>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
incrementIfOdd = () =>{
const {value} = this.selectNumber
if(this.props.count % 2 !==0){
this.props.jia(value*1)
}
}
incrementAsync = () =>{
const {value} = this.selectNumber
this.props.jiaAsync(value*1,500)
}
render() {
// console.log(this.props);
return (
<div style={{padding:'15px'}}>
{/* 通过getState这个API获取数据 */}
<h1>当前求和为:{this.props.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count:state}),
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction
}
)(Count)
当我们要进行多组件的redux集中状态管理怎么办,要知道我们之前书写store的时候,只传入一个组件的,如果要进行多组件的redux需要借助redux的一个API,combineReducers用于管理多个组件并将其封装成一个对象,如下:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
syncAction
}
)(Count)

当我们要进行多组件的redux集中状态管理怎么办,要知道我们之前书写store的时候,只传入一个组件的,如果要进行多组件的redux需要借助redux的一个API,combineReducers用于管理多个组件并将其封装成一个对象,如下:
[外链图片转存中...(img-ONws6j2t-4701946794403)]
[外链图片转存中...(img-fm65WXby-4701946794403)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618545628)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**