目录
1. UI组件:compoents,不能使用任何redux的api,只负责页面的呈现和交互等
2. 容器组件:container,负责和redux通信,数据获取、状态更新,将结果交给UI组件
一、什么是redux?
redux是一个专门用于做状态管理的JS库(不是react插件库)
redux独立所有组件存在,集中管理react应用中多个组件共享的状态
二、redux的应用
2.1 求和案例
2.1.1 纯react版本
- Count组件
import React, { Component } from "react"
import "./index.css"
export default class Count extends Component {
state = { count: 0 }
addNum = () => {
const { value } = this.selectNumber
const { count } = this.state
this.setState({ count: count + value * 1 })
}
//减法
subNum = () => {
const { value } = this.selectNumber
const { count } = this.state
this.setState({ count: count - value * 1 })
}
//奇数加
addNumIfodd = () => {
const { count } = this.state
if (count % 2 !== 0) {
this.addNum()
}
}
//异步加
addNumAsync = () => {
setTimeout(() => this.addNum(), 1000)
}
render() {
const { count } = this.state
return (
<div>
<h3>
当前求和为:<span>{count}</span>
</h3>
<select
name="select"
id=""
ref={(c) => (this.selectNumber = c)}
>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.addNum}>+</button>
<button onClick={this.subNum}>-</button>
<button onClick={this.addNumIfodd}>当前求和为奇数加</button>
<button onClick={this.addNumAsync}>异步加</button>
</div>
)
}
}
h3 {
margin: 10px 20px;
}
select {
margin: 0 20px;
height: 30px;
width: 100px;
}
button {
font-size: 16px;
margin: 0 10px;
padding: 5px 10px;
}
2.1.2 redux版本
npm i redux
(1)测试版本
- store.js
该文件专门用于暴露一个store对象,整个应用只要一个store对象
- 引入createStore,专门用于创建redux中最为核心的store对象
import { createStore } from "redux"
//引入为Count组件服务的reducer
import countReducer from "./count_reducer"
export default createStore(countReducer)
注意:'createStore' is deprecated,createStore被弃用,改用configureStore
configureStore写法
1. 下载依赖
npm install @reduxjs/toolkit
2. configureStore具体写法
import countReducer from "./count_reducer" import { configureStore } from "@reduxjs/toolkit" export default configureStore({ reducer: countReducer, })
- count_reducer.js
- 该文件时用于创建一个为Count组件服务的reducer,reducer的本质是一个函数
- reducer会接到两个参数,分别为:之前的状态 preState,动作对象 action
- reducer用于初始化状态和加工状态
- reducer第一次被调用时store自动触发
- 传递的preState:undefined
- 传递的action:@@REDUX/INIT
const initState = 0
export default function countReducer(preState = initState, action) {
// console.log(preState)
//判断初始化 if (preState === undefined) preState = 0
//从action对象中获取type,data
const { type, data } = action
//根据type决定如何加工数据
switch (type) {
case "add":
// console.log("add")
return preState + data
case "sub":
return preState - data
default:
return preState
}
}
- Count组件
Action
- 描述对 store 数据的更改操作,把数据从应用传到store。
- 它必须包含一个 type 字段,必须与reducer中的type对应。
- dispatch将action发送到reducer ,然后reducer函数根据匹配规则进状态更新
- 加法
addNum = () => {
const { value } = this.selectNumber
//通知redux + value, dispatch分发
store.dispatch({ type: "add", data: value * 1 })
}
- 奇数加
addNumIfodd = () => {
//如何获取count?
const count = store.getState()
if (count % 2 !== 0) {
this.addNum()
}
}
- 页面获取count
问题:rudux只更改状态,页面并未发生更新,所以需要手动
方法一
在Count组件的componentDidMount,通过subscribe订阅,检测redux中状态的变化,只要变化就调用render。但不能直接调用render,通过 setState 之后必调用 render。
componentDidMount() { store.subscribe(() => { //console.log("@") this.setState({}) }) }
方法二
在入口文件index.js,检测store中状态的改变,根据Reaact的DOM的Diffing算法,只会局部更新
import store from "./redux/store" store.subscribe(() => { root.render( <React.StrictMode> <App /> </React.StrictMode> ) })
- Count组件完整代码
import React, { Component } from "react"
import "./index.css"
//用于获取store中的状态
import store from "../../redux/store"
export default class Count extends Component {
addNum = () => {
const { value } = this.selectNumber
// 通知redux加value,dispatch分发
store.dispatch({ type: "add", data: value * 1 })
//并未发生更新,rudux只更改状态,不更新页面
}
//减法
subNum = () => {
const { value } = this.selectNumber
store.dispatch({ type: "sub", data: value * 1 })
}
//奇数加
addNumIfodd = () => {
//如何获取count
const count = store.getState()
if (count % 2 !== 0) {
this.addNum()
}
}
//异步加
addNumAsync = () => {
setTimeout(() => this.addNum(), 500)
}
render() {
return (
<div>
<h3>
当前求和为:<span>{store.getState()}</span>
</h3>
<select
name="select"
id=""
ref={(c) => (this.selectNumber = c)}
>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.addNum}>+</button>
<button onClick={this.subNum}>-</button>
<button onClick={this.addNumIfodd}>当前求和为奇数加</button>
<button onClick={this.addNumAsync}>异步加</button>
</div>
)
}
}
(2)正式版本
- constant.js
该模块式用于定义type类型的常量值,便于管理,防止单词拼写错误。将所有需要的type进行修改。
export const ADD = "add"
export const SUB = "sub"
- count_action.js
该文件为Count组件创建action对象
import { ADD, SUB } from "./constant"
export const addAction = (data) => ({
type: ADD,
data,
})
export const subAction = (data) => ({
type: SUB,
data,
})
- Count组件
//引入actionCreator,用于创建action对象
import { addAction, subAction } from "../../redux/count_action"
- 加法
addNum = () => {
const { value } = this.selectNumber
store.dispatch(addAction(value * 1))
}
补充:同步Action/异步Action
1. Object{},一般对象---同步Action
2. function{},函数类型---异步Action
- 异步Action,也就是返回值为函数,且能开启异步任务
- 延迟的动作不想交给组件自身,想交给Action完成。想要对状态做出改变,但具体数据靠异步完成。
- 异步Action一般都会调用同步Action
- 异步Action不是必须要用的
export const addAsyncAction = (data, time) => { return (dispatch) => { //异步任务 setTimeout(() => { dispatch(addAction(data)) }, time) } }
//异步加 addNumAsync = () => { const { value } = this.selectNumber store.dispatch(addAsyncAction(value * 1, 500)) }
注意:
报错内容:Actions must be plain objects. Instead, the actual type was: 'function'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions.
解决方案一
npm install redux-thunk
- store.js
import { createStore, applyMiddleware } from "redux" import thunk from "redux-thunk" //引入为Count组件服务的reducer import countReducer from "./count_reducer" export default createStore(countReducer, applyMiddleware(thunk))
解决方案二
用configureStore来创建store对象不会发生这种问题
三、react-redux
3.1 react-redux的使用
npm i react-redux
1. UI组件:compoents,不能使用任何redux的api,只负责页面的呈现和交互等
- 通过props获取数据
2. 容器组件:container,负责和redux通信,数据获取、状态更新,将结果交给UI组件
- 靠react-redux的connect函数生成的,使用connect()()创建并暴露一个容器组件(CountContainer)
- connect(mapStateToProps, mapDispatchToProps)(UI组件),connect调用的函数返回值依然是函数
- connect第一次传递两个参数,必须是函数,mapDispatchToProps也可为对象
- mapStateToProps:映射状态,返回值必须是对象
- mapDispatchToProps:映射操作状态的方法,返回值必须是对象
- 返回对象中的key 作为UI组件 props的 key,value作为UI组件的props的 value
- 容器组件的store是靠props传进入的不是直接引入
CountUI组件---页面结构
import React, { Component } from "react"
import "./index.css"
export default class Count extends Component {
render() {
// console.log(this.props)
const { count } = this.props
return (
<div>
<h3>
当前求和为:<span>{count}</span>
</h3>
<select
name="select"
id=""
ref={(c) => (this.selectNumber = c)}
>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.addNum}>+</button>
<button onClick={this.subNum}>-</button>
<button onClick={this.addNumIfodd}>当前求和为奇数加</button>
<button onClick={this.addNumAsync}>异步加</button>
</div>
)
}
}
Count容器组件
1. 引入UI组件
import CountUI from "../../components/Count"
2. 引入connect用于连接UI组件和redux的store
import { connect } from "react-redux"
3. 手动生成Count容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
4. mapStateToProps: 读取 state,把当前 Redux store state 映射到UI组件的 props 中
function mapStateToProps(state) {
return {
count: state,
}
}
5. mapDispatchToProps:将 操作状态的方法 映射到 UI组件 props中
//引入action
import { addAction, subAction, addAsyncAction } from "../../redux/count_action"
function mapDispatchToProps(dispatch) {
return {
add: (data) => {
dispatch(addAction(data))
},
sub: (data) => {
dispatch(subAction(data))
},
addAsync: (data, time) => {
dispatch(addAsyncAction(data, time))
},
}
}
App.jsx --- 容器组件传store到props
import React, { Component } from "react"
import Count from "./containers/Count"
//必须这样传store,通过props传
import store from "./redux/store.js"
export default class App extends Component {
render() {
return (
<div>
<Count store={store} />
</div>
)
}
}
CountUI组件--- 获取数据,展示页面
addNum = () => {
const { value } = this.selectNumber
this.props.add(value * 1)
}
//减法
subNum = () => {
const { value } = this.selectNumber
this.props.sub(value * 1)
}
//奇数加
addNumIfodd = () => {
//如何获取count
const { count } = this.props
if (count % 2 !== 0) {
this.addNum()
}
}
//异步加
addNumAsync = () => {
const { value } = this.selectNumber
this.props.addAsync(value * 1, 500)
}
3.2 案例的优化
3.2.1 mapDispatchToProps的简化
mapDispatchToProps值为两种形式,函数或对象。对象形式可以简化代码
export default connect(
(state) => ({ count: state }),
// mapDispatchToProps简写 react-dedux帮自动分发
{
add: addAction,
sub: subAction,
addAsync: addAsyncAction,
}
)(CountUI)
3.2.1 页面重新渲染问题
connect()()容器组件默认拥有检测rudeux状态改变的能力,所以不用手动重新渲染。
入口文件index.js中检测store状态重新渲染方法可删除。
3.2.3 容器组件的store
因为容器组件将来会有很多,所有不可能一一通过props传递store。我们可在入口文件将 store交由Provider保管,批量给整个应用中传递store对象,只需要写一次。
import store from "./redux/store"
import { Provider } from "react-redux"
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
)
3.2.4 整合UI组件和容器组件
把CountUI组件放到Count容器组件中,删除多余的UI组件
import React, { Component } from "react"
import "./index.css"
//引入action
import { addAction, subAction, addAsyncAction } from "../../redux/count_action"
//引入connect用于连接UI组件和redux store
import { connect } from "react-redux"
class CountUI extends Component {
addNum = () => {
const { value } = this.selectNumber
this.props.add(value * 1)
}
//减法
subNum = () => {
const { value } = this.selectNumber
this.props.sub(value * 1)
}
//奇数加
addNumIfodd = () => {
//如何获取count
const { count } = this.props
if (count % 2 !== 0) {
this.addNum()
}
}
//异步加
addNumAsync = () => {
const { value } = this.selectNumber
this.props.addAsync(value * 1, 500)
}
render() {
// console.log(this.props)
const { count } = this.props
return (
<div>
<h3>
当前求和为:<span>{count}</span>
</h3>
<select
name="select"
id=""
ref={(c) => (this.selectNumber = c)}
>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.addNum}>+</button>
<button onClick={this.subNum}>-</button>
<button onClick={this.addNumIfodd}>当前求和为奇数加</button>
<button onClick={this.addNumAsync}>异步加</button>
</div>
)
}
}
// 核心 connect(映射状态)(映射操作状态的方法)(关联组件)
export default connect(
(state) => ({ count: state }),
// mapDispatchToProps简写 react-dedux帮自动分发
{
add: addAction,
sub: subAction,
addAsync: addAsyncAction,
}
)(CountUI)