PartⅦ:Redux
1 redux理解
学习文档
英文文档:https://redux.js.org/
中文文档:http://www.redux.org.cn/
Github:https://github.com/reactjs/redux
redux是什么
redux是一个专门用于做状态管理的JS库
(不是react插件库)
它可以用在react,angular,vue等项目中,但基本与react配合使用
作用:集中式管理react应用中多个组件共享
的状态
什么情况下需要使用redux
某个组件的状态,需要让其他组件可以随时拿到(共享)
一个组件需要改变另一个组件的状态(通信)
总体原则:能不用就不用, 如果不用比较吃力才考虑使用
redux工作流程
2 redux的三个核心概念
action
-
动作的对象
-
包含2个属性
type:标识属性, 值为字符串, 唯一, 必要属性
data:数据属性, 值类型任意, 可选属性
-
例子:{ type: ‘ADD_STUDENT’,data:{name: ‘tom’,age:18} }
reducer
- 用于初始化状态、加工状态,初始化时传入的previousState是undefined
- 加工时,根据旧的state和action, 产生新的state的
纯函数(以下为纯函数概念)
纯函数:
一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)- 必须遵守以下一些约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用Date.now()或者Math.random()等不纯的方法
redux的reducer函数必须是一个纯函数
store
-
将state、action、reducer联系在一起的对象
-
如何得到此对象
?- import {createStore} from ‘redux’
- import reducer from ‘./reducers’
- const store = createStore(reducer)
-
此对象的功能?
-
getState(): 得到state
-
dispatch(action): 分发action, 触发red ucer调用, 产生新的state
-
subscribe(listener): 注册监听, 当产生了新的state时, 自动调用
-
3 redux的核心API
createstore()与applyMiddleware()
createstore()作用:创建包含指定reducer的store对象,目前已被弃用(2022.11)
applyMiddleware()作用:应用上基于redux的中间件(插件库)
store对象
作用: redux库最核心的管理对象
它内部维护着:
-
state
-
reducer
核心方法:
-
getState()
-
dispatch(action)
-
subscribe(listener)
具体编码:
-
store.getState()
-
store.dispatch({type:‘INCREMENT’, number})
-
store.subscribe(render)
combineReducers()
作用:合并多个reducer函数
//代码示例
------------------ redux/reducers/index.js ------------------------------------
/**
* 该文件用于汇总所有的reducer为一个总的reducer
*/
//引入combineReducers,用于汇总多个reducer
import {combineReducers} from 'redux'
//引入为Count组件服务的reducer
import count from './count'
import persons from './person'
//汇总所有的reducer变为一个总的reducer
export default combineReducers({
count,persons
})
4 redux基础求和案例
redux精简版案例
src下准备好redux文件夹,文件下有store.js
和reducer.js
store.js
//新写法,创建redux最为核心的store对象
import { legacy_createStore as createStore } from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
export default createStore(countReducer)
reducer.js
//初始化状态---注意现在状态不放在组件里
const initState = 99
export default function countReducer(preState = initState, action) {
//从action对象中获取:type,data
const { type, data } = action
//根据type决定如何加工数据
switch (type) {
case 'increment':
return preState + data
case 'decrement':
return preState - data
default:
return preState
}
}
Count.jsx组件
//获取redux中的状态getState
<h1>当前求和为:{store.getState()}</h1>
//引入store
import store from '../../redux/store'
//允许组件还有自己的状态
state = { car: '保时捷' }
//redux更新完状态需要重新渲染,这样写不是最佳方案,因为如果组件多的话每个都要写
//可以在入口文件中引入store,将App挂载的时候包裹在store.subscribe里面,状态一变化,App组件都将重新渲染
componentDidMount() {
//监测redux中状态的变化,只要变化,就调用render
store.subscribe(() => {
this.setState({})
})
}
increment = () => {
const { value } = this.selectNumber
store.dispatch({ type: 'increment', data: value * 1 })
}
decrement = () => {
const { value } = this.selectNumber
store.dispatch({ type: 'decrement', data: value * 1 })
}
入口文件index.js
// 状态改变重新渲染 App 组件
store.subscribe(() => {
ReactDOM.render(<App />, document.getElementById('root'))
})
备注:redux只负责管理状态,状态改变驱动页面显示需要我们自己来
redux完整版案例
多了两个文件constant.js
和action.js
constant.js
//该模块用于定义action对象中type类型的常量值,以防单词写错
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
action.js
//该文件专门为Count组件生成action对象
import { INCREMENT, DECREMENT } from "./constant"
export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: DECREMENT, data })
Count.js组件
//不用自己写action对象,直接调用
increment = () => {
const { value } = this.selectNumber
store.dispatch(createIncrementAction(value * 1))
}
decrement = () => {
const { value } = this.selectNumber
store.dispatch(createDecrementAction(value * 1))
}
异步action的redux版本–不在Count组件中设置定时
action有两种形式:{}同步,function异步
store默认不允许action给的值不是一般对象(简单来说就是store最终接收的必须是一个合法的包含type和data的一般对象),给函数会报错滴,这时需要一个中间件来解决redux-thunk
npm i redux-thunk
store.js
//redux中的执行中间件函数applyMiddleware
import { legacy_createStore as createStore, applyMiddleware } from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//引入redux-thunk中间件,用来支持异步action
import thunk from 'redux-thunk'
//暴露store,第二个参数是执行异步的中间件
export default createStore(countReducer, applyMiddleware(thunk))
有了中间件store才会帮你调异步action的函数,异步action中一般都会调用同步action
action.js
import { INCREMENT, DECREMENT } from "./constant"
import store from "./store"
//同步action,action的值是object一般对象
export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: DECREMENT, data })
//异步action,action的值是函数--因为函数可以开启异步任务
export const createIncrementAsyncAction = (data, time) => {
return () => {
setTimeout(() => {
store.dispatch(createIncrementAction(data))
}, time)
}
}
//其实还可以这样写,因为是store来调这异步函数,所以不用引入store
//因为store帮我们调的异步函数,所以知道等下要用到dispatch,store帮你传过来了
import { INCREMENT, DECREMENT } from "./constant"
//同步action,action的值是object一般对象
export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: DECREMENT, data })
//异步action,action的值是函数--因为函数可以开启异步任务
export const createIncrementAsyncAction = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time)
}
}
Count.js组件
incrementAsync = () => {
const { value } = this.selectNumber
// setTimeout(() => {
store.dispatch(createIncrementAsyncAction(value * 1, 500))
// }, 500)
}
5 react-redux
理解
一个react插件库
专门用来简化react应用中使用redux
react-Redux将所有组件分成两大类
UI组件
只负责 UI 的呈现,不带有任何业务逻辑
通过props接收数据(一般数据和函数)
不使用任何 Redux 的 API
一般保存在components
文件夹下,也可以直接写在容器组件中直接加工成容器组件
容器组件
负责管理数据和业务逻辑,不负责UI的呈现
使用 Redux 的 API
一般保存在ontainers
文件夹下
容器组件需要实现很多功能,需要借助东西去生成
注意:store不是在容器组件中直接引入,而是在App组件中通过props传过来的
UI组件是看不见任何redux的AIPI
求和案例react-redux
首先准备好UI组件(一般放在src下的components)和容器组件(一般放在src下的containers)
容器组件Count下的index.js,注意store不在这里引入
//引入Count的UI组件
import CountUI from "../../components/CountUI";
//引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction }
from "../../redux/count_action";
//引入connect用于链接UI组件和redux
import { connect } from 'react-redux'
/* mapStateToProps函数返回的是一个对象
对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value---容器组件把从redux中获取的状态传给UI组件
mapStateToProps用于传递状态
注意这边不需要亲自引入store,react-redux调用mapStateToProps函数时帮我们传入state了
*/
function mapStateToProps(state) {
return { count: state }
}
/* mapDispatchToProps函数返回的是一个对象
对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
mapDispatchToProps用于传递操作状态的方法
*/
function mapDispatchToProps(dispatch) {
return {
jia: (number) => {
//通知redux执行加法
dispatch(createIncrementAction(number))
},
jian: (number) => {
//通知redux执行减法
dispatch(createDecrementAction(number))
},
jiaAsync: (number, time) => {
dispatch(createIncrementAsyncAction(number, time))
}
}
}
//创建Count容器组件
const CountContainer = connect(mapStateToProps, mapDispatchToProps)(CountUI)
export default CountContainer
UI组件
//从props中读取
<h1>当前求和为:{this.props.count}</h1>
//UI组件中不会有任何redux的API
increment = () => {
const { value } = this.selectNumber
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)
}
App.js
import store from './redux/store'
//把store传递给容器组件
<Count store={store} />
优化一:mapDispatchToProps简写
//创建Count容器组件---第二个函数可以写为对象,react-redux会帮我们自动分发dispatch
export default connect(
state => ({ count: state }),
{
jia: createIncrementAction,
jian: createDecrementAction,
jiaAsync: createIncrementAsyncAction
}
)(CountUI)
优化二:Provider组件使用
使用了react-redux之后可以不写监测redux中状态变化的代码,因为connect
创建容器组件的时候就有了监测redux状态变化的能力
所以之前为什么不是自己创建容器组件,因为connect创建的可以实现自动监测redux中状态的变化
//入口文件index.js---可以不用写了
store.subscribe(() => {
root.render(<App />)
})
如果容器组件太多,我们需要在App.jsx中一个一个去传store,太繁琐,有没有什么方法可以优化
在入口文件中用Provider组件,可以只写一次,用到store的容器组件都会被提供store
//入口文件index.js
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
优化三:整合UI组件和容器组件
一个文件可以定义n个组件
定义了UI组件之后直接定义容器组件并使用,最终暴露容器组件
知识点小结
connect()()
作用: 用于包装 UI 组件生成容器组件
使用connect(mapDispatchToProps
,mapDispatchToProps
)(UI组件)
注意点:
- 该方法默认传入
state
与dispatch
- 可以省略
dispatch
直接传入action
方法,该api会自动帮你调用dispatch
mapStateToProps
作用:将外部的数据(即state对象
)转换为UI组件的标签属性
mapStateToProps函数返回的是一个对象;
返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
mapStateToProps用于传递状态
mapDispatchToProps
作用:将分发action的函数
转换为UI组件的标签属性
mapDispatchToProps函数返回的是一个对象;
返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
mapDispatchToProps用于传递操作状态的方法
可以省略dispatch
,直接传入action
,api将会自动调用
dispatch
数据共享—重点
combineReducers
多个组件的数据都放在redux中,redux用对象来存储,需要借助redux提供的combineReducers先进行合并,合并之后的总状态是一个对象
容器组件在reduxc中取状态,交给子组件UI展示
现在有Person和Count组件,进行数据共享
为Person组件创建constant、action、reducer
export const ADD_PERSON = 'add_person'
import { ADD_PERSON } from '../constant'
//创建增加一个人的action动作对象
export const createAddPersonAction = personObj => ({ type: ADD_PERSON, data: personObj })
//对人状态的初始化以及加工
//引入常量
import { ADD_PERSON } from '../constant'
//初始化状态
const initState = [{ id: '001', name: 'tome', age: 18 }]
//加工状态
export default function personReducer(preState = initState, action) {
const { type, data } = action
switch (type) {
case ADD_PERSON:
return [data, ...preState]
default:
return preState
}
}
需要在store.js中进行合并combineReducers
//新写法,创建redux最为核心的store对象
import { legacy_createStore as createStore, applyMiddleware, combineReducers } from 'redux'
//引入为Count组件服务的reducer
import countReducer from './reducers/count'
//引入为Person服务的reducer
import personReducer from './reducers/person'
//引入redux-thunk中间件,用来支持异步action
import thunk from 'redux-thunk'
//借助personReducer汇总所有reducer
const allReducer = combineReducers({
//谁以后可以初始化和加工这个he
he: countReducer,
rens: personReducer
})
//暴露store
export default createStore(allReducer, applyMiddleware(thunk))
Person组件—connect
创建容器链接UI组件和redux
import React, { Component } from 'react'
import { nanoid } from 'nanoid'
import { connect } from 'react-redux'
import { createAddPersonAction } from '../../redux/actions/person'
class Person extends Component {
addPerson = () => {
//获取用户输入
const name = this.nameNode.value
const age = this.ageNode.value
const personObj = { id: nanoid(), name, age }
this.props.jiaRen(personObj)
this.nameNode.value = ''
this.ageNode.value = ''
}
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.addPerson}>添加</button>
<ul>
{
this.props.yiduiren.map((p) => {
return <li key={p.id}>名字{p.name}--年龄{p.age}</li>
})
}
</ul>
</div>
)
}
}
export default connect(
//state就是redux帮我们保存的总状态
state => ({ yiduiren: state.rens }),
{
jiaRen: createAddPersonAction
}
)(Person)
同样的App.js中需要引入Person组件并展示
<Count />
<Person />
纯函数
在reducer
中如果preState是一个数组,不可以用push、unshift
等方法进行修改,如此修改并不会修改其引用,所以diff
并不会判定其发生改变,导致页面无法自动重新渲染
//浅比较,redux不认为是新数组--而且这样子写reducer就不是一个纯函数了--这边就是改写了参数preState的数据
preState.unshift(data)
return preState
//返回新数组可以
return [data, ...preState]
纯函数定义:只要是同样的输入(实参),必定得到同样的输出(返回)
遵守以下一些约束:
- 不得改写传入的参数数据
- 不会产生如何副作用,例如网络请求,输入输出设备
- 不能调用Date.now()或者Math.random()等不纯的方法
redux的reducer函数必须是一个纯函数
6 使用redux调试工具
安装chrome浏览器插件
Redux DecTools
下载工具依赖包
npm i redux-devtools-extension
修改store.js
import {composeWithDevTools} from 'redux-devtools-extension'
/**
* 该文件撰文用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入汇总后的reducer
import reducer from './reducers'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'
//暴露store
export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))
7 求和案例的最终代码
src文件目录
src
–containers
--Count
--index.jsx
--Person
--index.jsx
–redux
--actions
--count.js
--person.js
--reducers
--count.js
--index.js
--person.js
--constant.js
--store.js
–App.jsx
–index.js
index.js
import React from 'react'
import ReactDOM from "react-dom"
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'
ReactDOM.render(
/* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
App.jsx
import React, { Component } from 'react'
import Count from './containers/Count' //引入的Count的容器组件
import Person from './containers/Person' //引入的Person的容器组件
export default class App extends Component {
render() {
return (
<div>
<Count/>
<hr/>
<Person/>
</div>
)
}
}
redux文件
action
文件夹
--------------------------------count.js------------------------------------------
/**
* 该文件专门未Count组件生成对象
*/
import {INCREMENT,DECREMENT} from '../constant'
//声明同步action,就是指action的值为Object类型的一般对象
export const increment=data=>({type:INCREMENT,data})
export const decrement=data=>({type:DECREMENT,data})
//声明异步action,就是指action的值为函数,异步action中一般都会调用同步action
//在外部调用该action方法时需要引入redux-thunk,用于支持异步action
//该方法会自动传入dispatch
export const incrementAsync=(data,time)=>{
return (dispatch)=>{
setTimeout(()=>{
dispatch(increment(data))
},time)
}
}
--------------------------------------person.js-------------------------------
import {ADD_PERSON} from '../constant'
//创建增加一个人的action动作对象
export const addPerson=personObj=>({
type:ADD_PERSON,
data:personObj
})
reducers
文件夹
--------------------------------count.js------------------------------------------
/**
* 1. 该文件时用于创建一个为Count组件服务的reducer.reducer的本质就是一个函数
* 2. reducer函数会接到两个参数,分别为:之前状态(preState),动作对象(action)
*/
import {
INCREMENT,
DECREMENT
} from '../constant'
const initState = 0 //初始化状态
export default function countReducer(preState = initState, action) {
//从action对象中获取:type:data
const {
type,
data
} = action
//根据type决定如何加工数据
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
--------------------------------------person.js-------------------------------
import {ADD_PERSON} from '../constant'
//初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]
export default function personReducer(preState=initState,action){
// console.log('personReducer@#@#@#');
const {type,data} = action
switch (type) {
case ADD_PERSON: //若是添加一个人
//preState.unshift(data) //此处不可以这样写,这样会导致preState被改写了,personReducer就不是纯函数了。
return [data,...preState]
default:
return preState
}
}
--------------------------------------index.js-------------------------------
/**
* 该文件用于汇总所有的reducer为一个总的reducer
*/
//引入combineReducers,用于汇总多个reducer
import {combineReducers} from 'redux'
//引入为Count组件服务的reducer
import count from './count'
import persons from './person'
//汇总所有的reducer变为一个总的reducer
export default combineReducers({
count,persons
})
store.js
/**
* 该文件撰文用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入汇总后的reducer
import reducer from './reducers'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'
//暴露store
export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))
4.constant.js
/**
* 该模块是用于定义action对象中的type类型的常量值,目的只有一个:
* 便于管理的同事防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
containers
Count
文件夹的index.jsx
import React, { Component } from 'react'
//引入action
import {
increment,
decrement,
incrementAsync
} from "../../redux/actions/count"
//引入connect用于链接UI组件与redux
import { connect } from 'react-redux'
//定义UI组件,这个将再connect()()中加工成容器组件,就可以调用到其传入的redux状态与actions
class Count extends Component {
increment = () => {
//获取出入内容
const { value } = this.selectNumber
this.props.increment(value * 1)
}
//减法
decrement = () => {
const { value } = this.selectNumber
this.props.decrement(value * 1)
}
//奇数再加
incrementIfOdd = () => {
const { value } = this.selectNumber
if (this.props.count % 2 !== 0) {
this.props.increment(value * 1)
}
}
//异步加
incrementAsync = () => {
const { value } = this.selectNumber
this.props.incrementAsync(value * 1, 500)
}
render() {
return (
<div>
<h2>我是Count组件,下方组件总人数为:{this.props.personCount}</h2>
<h4>当前求和为:{this.props.count}</h4>
<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的容器组件
//使用connect(传入状态,操作状态方法)(UI组件)
export default connect(
state => ({
count: state.count,
personCount: state.persons.length
}),
{increment, decrement, incrementAsync}
)(Count)
Person
文件夹下的jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addPerson } from '../../redux/actions/person'
import { nanoid } from 'nanoid'
//创建UI组件
class Person extends Component {
addPerson = () => {
const name = this.nameNode.value
const age = this.ageNode.value * 1
const personObj = { id: nanoid(), name, age }
this.props.addPerson(personObj)
this.nameNode.value = ''
this.ageNode.value = ''
}
render() {
return (
<div>
<h2>我是Person组件,上方组件求和为{this.props.count}</h2>
<input ref={c => this.nameNode = c} type="text" placeholder="输入名字" />
<input ref={c => this.ageNode = c} type="text" placeholder="输入年龄" />
<button onClick={this.addPerson}>添加</button>
<ul>
{
this.props.persons.map((p) => {
return <li key={p.id}>{p.name}--{p.age}</li>
})
}
</ul>
</div>
)
}
}
export default connect(
state => ({
persons: state.persons,
count: state.count
}), { addPerson }
)(Person)