什么是react-redux

  • 官方文档

  • react-redux是一个由react官方推出的第三方插件,是一个redux的绑定库,它能够使你的React组件Redux store中读取数据,并且向store分发actions以更新数据。其实就是用于连接redux,更方便我们在项目中操作reduxreact-redux提供了connect(用于进行组件连接)和Provider(给全局父组件App包裹用于进行全局的store传参)两个Api

  • react-redux将所有组件分为两大类:UI组件容器组件,其中所有容器组件包裹着UI组件,构成父子关系。容器组件负责和redux交互,里面使用redux API函数,UI组件负责页面渲染,不使用任何redux API。容器组件会给UI组件传递redux中保存对的状态(state)和操作状态的方法(action)。

基本使用

  1. 下载react-redux
npm i react-redux
  1. 在项目入口文件index.js中引入Provider,以及配置好的reduxstore对象,并以类似context的通信形式,将provider包裹在跟组件的最外层
/* --------------- react项目入口文件 ---------------- */

import React from 'react';//1.引入react
// import ReactDOM from 'react-dom'//2.引入react-dom //react17
import * as ReactDOM from 'react-dom/client'; // react18

//导入redux的store配置文件,里面已经配置好了store和action方法
import store from "./store";
//导入react-redux对象 用于分发数据
import { Provider } from "react-redux"

//路由vuex的信息操作, 我已经配置好路由了,所以直接使用路由文件
import Routes from './router';

// ReactDOM.render( // react17
// <h1>欢迎进入React的世界</h1>,
// // 渲染到哪里(函数式编程)
// document.getElementById('root')
// )

// react 18
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
//包裹节点传递store对象,相当于全局变量(类似context)
<Provider store={store}>
<Routes />
</Provider>
)
  1. 在子组件中调用react-redux提供的两个API
/* ------------------- provider ----------------------- */
/*
这个API在上一个代码块中已经使用了,就是用来将跟组件包装成
全局的父组件的,并向调用了connect的子组件传递props的
*/

/* ---------- connect(高阶组件,类似withRouter) ----------- */
/*
connect这一个api是用于子组件连接父组件(provider的提供者)的,
里面接收两个参数:mapStateToProps以及mapDispatchToProps,

mapStateToProps是一个函数,接收state为形参,就是全局的状态,里
面必须有返回值,返回值就是包裹下该子组件得到的属性,一般以对象的形
式返回,用于传递全局的store等状态

mapDispatchToProps同样是一个函数,接收dispatch为形参,就是修改
全局状态的方法,用于在跟组件中(全局父组件)调用action来修改全局状态
*/

import { connect } from 'react-redux';// 引入connect API

// 定义一个属性用于配置redux中state到 组件中的props的映射关系
const mapStateToProps = (state) =>{
return {
...state
}
}

//映射到action的方法,修改state的数据
const mapDispatchToProps = (dispatch)=>{
return {
change: ()=>{
dispatch({type:"Change"});
}
}
}

//最后面跟的是你自定义的组件的名字
connect(mapStateToProps,mapDispatchToProps)(YourComponent)
  1. 在异步操作中使用react-redux
/*
在react-redux的加持下,我们不需要自己去订阅和取消订阅store,
其他的异步执行流程(等待数据请求完成)操作与上几篇博客介绍的使用
步骤基本一致,唯一不同的就是,数据请求的异步action操作在
mapDispatchToProps中执行即可
*/

import axios from 'axios' //引入axios
// 引入异步执行的action操作
//import getDataAction from './getDataAction.js';//获取图片数据方法

// 创建异步操作action函数
function getDataAction(){
/* ------------------- (1)redux-promise中间件处理异步操作 ---------------------*/
// 1.简单写法,因为axios本身就是promise对象,它是基于promise封装的
return axios.get('https://dog.ceo/api/breeds/image/random').then(res => {
console.log('数据请求成功!', res.data.message)
return {
type: 'getData',// 触发修改全局store中状态的字段
value: res.data.message,// 返回的数据
}
})
}

// 设置mapStateToProps,获取全局store中的状态
const mapStateToProps = (state) =>{
return {
data:state.dataReducer.data,//判断是否存在图片的url
}
}

// 设置mapDispatchProps,修改全局的状态
const mapDispatchToProps = (dispatch) =>{
// 返回值需以对象的形式
return {
showImg:()=>{
// 异步操作的action直接执行即可
dispatch(getDataAction())
}
}
}

export default connect(mapStateToProps,mapDispatchToProps)(yourComponent)

案例展示:

  • 还是上一篇博客的案例: 进入info页面显示底部区域,退出info页面关闭底部区域展示,进入about页面,请求狗图数据(异步操作,这里使用redux-promise),关闭底部区域

1. 入口文件(index.js)

/* --------------- react项目入口文件 ---------------- */

import React from 'react'//1.引入react
// import ReactDOM from 'react-dom'//2.引入react-dom //react17
import * as ReactDOM from 'react-dom/client'; // react18

import App from './02-advance/05-redux/home.js' // 引入App父组件
//导入react-redux对象 用于分发数据
import { Provider } from "react-redux"

import store from './store/index.js';

// ReactDOM.render( // react17
// <h1>欢迎进入React的世界</h1>,
// // 渲染到哪里(函数式编程)
// document.getElementById('root')
// )

// react 18
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
//包裹节点传递store对象,相当于全局变量(类似context)
<Provider store={store}>
<App/>
</Provider>

);// 渲染我们自己定义的app组件(直接使用自闭合标签包裹组件名即可,切记组件名一定要大写)

2. store.js(全局状态管理)

// 1. 引入redux
import {applyMiddleware, combineReducers, createStore, compose } from 'redux'

// 2. 引入其他的小Reducer
import pageNameReducer from './reducer/pageNameReducer'//处理关于页面标题的reducer
import isShowBottomReducer from './reducer/isShowBottomReducer'//处理底部区域展示的reducer
import dataReducer from './reducer/dataReducer.js'//处理axios异步请求数据的reducer

// 3. 引入中间件
import ReduxThunk from 'redux-thunk'//引入redux-thunk中间件
import ReduxPromise from 'redux-promise'//引入redux-promise中间件

// 4. 合并reducer
const reducer = combineReducers({
pageNameReducer,
isShowBottomReducer,
dataReducer
})

/*
由于创建的state是不能直接放到创建的store中的, 需要通过reducer将数据添加到store中,
因此创建store时必须创建reducer;reducer函数的返回值, 会作为store之后存储的state,
只要调用dispatch就会重新执行reducer函数,reducer是一个纯函数,不可以直接修改state,
*/

// 5. 创建全局store(配置redux开发者工具)
//配置redux开发者工具
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer,composeEnhancers(// composeEnhancers(高级模式,可监视同步与异步)
applyMiddleware(ReduxThunk,ReduxPromise)//并应用中间件
))

// 6. 导出store
export default store

3. home.js(首页页面,路由页面,使用mapStateToProps获取全局状态)

import React, { Component } from 'react'
// 引入路由相关的组件
import { HashRouter as Router, Route, Redirect, Switch, NavLink } from 'react-router-dom'
// import store from '../../store/index.js'//不再需要引入全局store
// 引入对应的组件
import About from './about.js'//关于页
import Info from './info.js'//个人信息页
import Footer from './footer.js'//底部区域
import NotFound from './404.js'//404页面

import { connect } from 'react-redux';

class home extends Component {

/* 不用再在组件中维护自身状态和监听(subscribe)全局store的变化了 */

render() {
console.log('是否展示底部区域',this.props.store.isShowBottomReducer.isShow);
return (
<div>
<h1>首页</h1>
<p>(去到信息页关闭底部,去到关于页开启底部)</p>
<div style={{ width: '100%', height: '400px', backgroundColor: 'yellowgreen' }}>
<Router>
<ul>
<li><NavLink to={'/home/info'}>info</NavLink></li>
<li><NavLink to={'/home/about'}>about</NavLink></li>
</ul>
{/* 使用Switch来解决每次页面刷新,重定向功能执行的bug(模糊匹配) */}
<Switch>
<Route path="/home/about" component={About} />
<Route path="/home/info" component={Info} />

<Redirect from="/" to="/home/info" exact />
{/* 匹配不到的页面则展示404 */}
<Route component={NotFound} />
</Switch>
</Router>
</div>
{/* 通过判断全局store中的isShow来执行是否展示Footer组件 */}
{this.props.store.isShowBottomReducer.isShow && <Footer />}
</div>
)
}
}

// 设置mapStateToProps,配置redux中state到 组件中的props的映射关系
// 其实就是让子组件可以获取到全局的状态,这样就不用在组件本身设置和维护状态了
const mapStateToProps = (state) =>{
return {
store:state
}
}

export default connect(mapStateToProps)(home)

4. info.js(信息页面,使用mapDispatchToProps修改全局状态)

import React ,{useEffect}from 'react'
// import store from '../../store/index.js'//不再需要引入store
import { connect } from 'react-redux';

function Info(props) {//设置props接收全局store中的状态
console.log('输出全局props:',props);
// 模拟生命周期
useEffect(()=>{
console.log('进入info组件');
// 进入info组件修改全局store中的isShow为true(显示底部区域)
props.isShowBottom(true)
return ()=>{
console.log('退出info组件');
// 离开info组件修改全局store中的isShow为false(关闭底部区域)
props.isShowBottom(false)
}
},[])

return (
<div>个人信息页</div>
)
}

// mapDispatchProps函数需要返回出去一个函数 这个函数中用dispatch传递一个action
const mapDispatchToProps = (dispatch) =>{
// 返回值需以对象的形式
return {
isShowBottom:(status)=>{
dispatch({
type:'changeBottom',
isShow:status
})
}
}
}

// 不需要用到store中的状态因此就不需要传递 mapStateToProps
export default connect(null,mapDispatchToProps)(Info)

5. about.js(关于页面,使用mapStateToProps,mapDispatchToProps以及异步操作)

import React ,{useEffect}from 'react'
// import store from '../../store/index.js'//不再需要引入store
import getDataAction from '../../store/actionCreator/getDataAction.js';//获取图片数据方法
import { connect } from 'react-redux';

function About(props) {
// 使用reducer合并则需要多获取对应的小reducer在获取对应的状态
console.log('组件创建时全局store中的图片数据',props);
// 模拟生命周期
useEffect(()=>{//组件挂载
console.log('进入About组件');
// 不需要再组件订阅时创建订阅全局store
// 判断全局store中是否有图片数据
if(props.data === ''){
props.showImg()
console.log('全局store中没有数据!');
}else{
console.log('使用缓存中的数据!');
}
return ()=>{//组件销毁
console.log('退出About组件');
// 不需要再组件销毁时取消订阅全局store
}
},[])

return (
<div>
{/* 展示图片 */}
<img src={`${props.data}`} alt=""/>
</div>
)
}

// 设置mapStateToProps,获取全局store中的状态
const mapStateToProps = (state) =>{
return {
data:state.dataReducer.data,//判断是否存在图片的url
}
}

// 设置mapDispatchProps,修改全局的状态
const mapDispatchToProps = (dispatch) =>{
// 返回值需以对象的形式
return {
showImg:()=>{
dispatch(getDataAction())
}
}
}

export default connect(mapStateToProps,mapDispatchToProps)(About)

6. 异步操作action

// 接口地址:随机生成一个狗图(vpn):https://dog.ceo/api/breeds/image/random
// 1. 引入axios
import axios from 'axios'

/* 2. 使用redux中间件结合actionCreator创建的action实现异步处理操作 */
function getDataAction() {

/* ------------------- (1)redux-promise中间件处理异步操作 ---------------------*/
// 1.简单写法,因为axios本身就是promise对象,它是基于promise封装的
return axios.get('https://dog.ceo/api/breeds/image/random').then(res => {
console.log('数据请求成功!', res.data.message)
return {
type: 'getData',// 触发修改全局store中状态的字段
value: res.data.message,// 返回的数据
}
})
}

export default getDataAction // 将创建的action暴露出去

结果展示

image