7.1 使用redux编写应用
7.1.1 工作流程
概要总结
1、什么是redux
2、redux的三个核心概念
3、redux的工作流程
一、什么是redux
redux是专门用于集中式管理状态的javascript库,他并不是react的插件库。
比如你有多个组件A-E都想要用同一个组件D中的状态:
1、像以前我们可以通过父子组件通信的方式让父组件进行传递状态,或者是让兄弟组件之间通过订阅发布进行通信
2、当我们使用了redux就可以直接通过让redux进行统一的状态管理,谁想要用状态就自己去拿,省去了第一种方法的层层传递
二、redux的三个核心概念
1、actions
actions英文直译过来就是行动、动作的意思,那么我们就可以猜到他表示的是“怎么做”,简单来说actions就是一个对象,actions里面有两个属性分别为type和data:
type:标识属性,值为字符串且唯一,必要属性(你想要做什么事情
data:数据属性,值为任意类型,可选属性(你要做事情的数据
那我们浅浅举个栗子:比如计算器你可以进行加1减2等操作,那么加减乘除这个操作就是你的type,数字就是你的数据
2、store
store有且只能有一个,他相当于一个最高指挥家,他负责把action动作交给对应的reducer进行执行,也就是说将state、action和reducer联系在一起的对象。
3、reducer
reducer用于将store发过来的action完成并将结果返回给store,他接收两个参数preState(旧状态)和action(动作)并返回一个newState(新状态)。
比如像计算器我们需要在原来的数据上进行加1的操作,那么旧状态旧对应原来的数据,action对应加1的操作,返回的新状态就是计算器加完之后重新返回的结果。
三、redux的工作流程
1、首先我们要确定我们要做什么
2、让Action Creators创建action(也就是你要做的事情)
3、通过dispatch将action分发出去
4、store对要使用的reducer进行绑定,然后将action分发到对应的reducer上
5、在reducer上进行相应的action操作并返回结果给store
6、组件就可以通过store的API像store进行获取操作返回的结果
7.1.2 求和案例—纯react版
概要总结
1、使用纯react实现求和功能
需求:
1、根据下拉框数字进行相应的运算操作
2、计算当前的求和值
一、创建计数器组件
import React, {Component} from 'react';
class Count extends Component {
state = {count: 0}
// 加法
increment = () => {
const {value} = this.selectNumber
const {count} = this.state
this.setState({count: count + value * 1})
}
// 减法
decrement = () => {
const {value} = this.selectNumber
const {count} = this.state
this.setState({count: count - value * 1})
}
// 奇数再加
incrementIfOdd = () => {
const {value} = this.selectNumber
const {count} = this.state
if (count % 2 !== 0) {
this.setState({count: count + value * 1})
}
}
// 异步加
incrementAsync = () => {
const {value} = this.selectNumber
const {count} = this.state
setTimeout(() => {
this.setState({count: count + value * 1})
}, 500)
}
render() {
return (
<div>
<h1>当前求和为:{this.state.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>
);
}
}
export default Count;
7.1.3 求和案例—redux精简版
概要总结
1、安装redux依赖
2、搭建redux
3、store的3个方法:getState()、dispatch()、subscribe()
4、统一订阅管理
一、安装redux依赖
npm i redux
二、搭建redux
1、创建Store
store通过redux的createStore进行创建,需要传入一个reducer。
/* * 该文件专门用于暴露一个store对象,整个应用只有一个store对象 * */
// 引入createStore,专门用于创建redux中最为核心的store对象
import {createStore} from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'
// 暴露store
export default createStore(countReducer)
2、创建Reducer
(1)reducer的使用
根据redux流程图,reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)。
首先要从action取出类型type和数据data,然后根据类型进行数据加工。
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: break; } }
注意:reducer不处理任何逻辑问题,因为它只管数据处理,逻辑判断应该在reducer之前就已经处理完成。
(2)初始化状态
正常来说,reducer是在具体操作的时候会执行,但还有一种场景,就是在初始化的时候,reducer也会被调用,而preState为undefined,action只返回一个type,这个type是一个随机值,保证跟我们代码的type判断绝不匹配。
所以我们可以定义一个默认状态值,在初始化的时候返回。
const initState = 0
export default function countReducer(preState = initState, action) {
console.log(preState, action)
// 从action对象中获取:type、data
const {type, data} = action
// 根据type决定如何加工数据
switch (type) {
case 'increment': // 如果是加
return preState + data
case 'decrement': // 如果是减
return preState - data
default:
return preState
}
}
}
三、store的3个方法
1、getState()
由于组件的状态交给了redux管理,store提供了getState方法,就是用来获取store的状态。
import React, {Component} from 'react'
import store from '../../redux/store'
class Count extends Component {
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</h1>
......
</div>
);
}
}
export default Count;
2、dispatch()
dispatch方法是分发的意思,在redux原理图它是action里的dispatch方法,需要传入一个action,里面包含类型和数据。
该方法在store对象里,store调用dispatch方法之后,它就会把action传给了reducer进行数据的加工处理。那么组件里的加、减、奇数再加、异步加等功能,都可以通过dispatch完成。
import React, {Component} from 'react'
import store from '../../redux/store'
class Count extends Component {
// 加法
increment = () => {
const {value} = this.selectNumber
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>
<h1>当前求和为:{store.getState()}</h1>
......
</div>
);
}
}
export default Count;
3、subscribe()
调用dispatch方法之后,我们发现reducer已经把数据加工了,也把最新的状态返回了,但是页面上并没有任何变化。
react中只要调用了setState方法,它就一定会更新状态,重新调用一次render()。但是reducer只承诺更新状态,但它不承诺页面的更新。
此时,我们需要监听redux的状态,一旦发生改变,我们就去更新页面。store中提供了subscribe方法订阅redux的状态,只要redux里任何一个状态的改变,回调函数就会执行。
由于状态不在react里,我们在调用setState()方法进行更新页面的时候,可以传入一个空对象{}来实现。
import React, {Component} from 'react'
import store from '../../redux/store'
class Count extends Component {
componentDidMount() {
// 检测redux中状态的变化,只要变化,就调用render
store.subscribe(() => {
this.setState({})
})
}
......
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</h1>
......
</div>
);
}
}
export default Count;
四、统一订阅管理
由于redux的状态变化需要组件自己进行订阅监听,那就意味着所有组件都得订阅一次。此时可以将redux订阅绑定在根目录的index.js,一旦redux状态发生变化,刷新整个根目录组件。
// 引入ReactDOM的createRoot
import {createRoot} from 'react-dom/client'
// 引入App组件
import App from './App'
import store from './redux/store'
const containter = document.getElementById('root')
const root = createRoot(containter)
// 渲染App到页面
root.render(<App/>)
store.subscribe(() => {
root.render(<App/>)
})
这里也不会涉及太多的性能问题,虽然它更新的是整个根组件下的所有组件,但它有diffing算法,没有发生改变的组件不会进行更新。
7.1.4 求和案例—redux完整版
概要总结
1、创建action
2、定义action的type常量
一、创建action
在精简版中,没有action模块的时候,需要我们自己来组装dispatch里的action对象。按流程图而言,redux是专门有一个action来管理action对象的,在dispatch分发的时候,只需要调用action的方法即可。
redux/count_action.js:
/* * 该文件专门为Count组件生成action对象 * */
export const createIncrementAction = data => ({type: 'increment', data})
export const createDecrementAction = data => ({type: 'decrement', data})
component/Count/index.jsx:
import React, {Component} from 'react'
import store from '../../redux/store'
import {createIncrementAction, createDecrementAction} from '../../redux/count_action'
class Count extends Component {
// 加法
increment = () => {
const {value} = this.selectNumber
store.dispatch(createIncrementAction(value * 1))
}
// 减法
decrement = () => {
const {value} = this.selectNumber
store.dispatch(createDecrementAction(value * 1))
}
// 奇数再加
incrementIfOdd = () => {
const {value} = this.selectNumber
const count = store.getState()
if (count % 2 !== 0) {
store.dispatch(createIncrementAction(value * 1))
}
}
// 异步加
incrementAsync = () => {
const {value} = this.selectNumber
setTimeout(() => {
store.dispatch(createIncrementAction(value * 1))
}, 500)
}
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</h1>
......
</div>
);
}
}
export default Count;
二、定义action的type常量
因为reducer是根据type来进行对应的数据加工,但是这个type会在整个redux各个流程多次出现,一旦在某个环节拼错了type,会导致无法工作,而且极难排查。
定义一个常量文件,把所有的action里的type用常量定义在一起,统一用常量名,这样既容易管理,也杜绝了type出错。
redux/constant.js:
/* * 该模块是用于定义action对象中type类型的常量值 * */
export const INCREMENT = 'increment'
export const DECREMENT = 'increment'
redux/count_reducer.js:
import {INCREMENT, DECREMENT} from './constant';
const initState = 0 // 初始化状态
export default function countReducer(preState = initState, action) {
console.log(preState)
// 从action对象中获取:type、data
const {type, data} = action
// 根据type决定如何加工数据
switch (type) {
case INCREMENT: // 如果是加
return preState + data
case DECREMENT: // 如果是减
return preState - data
default:
return preState
}
}
redux/count_action.js:
/* * 该文件专门为Count组件生成action对象 * */
import {INCREMENT, DECREMENT} from './constant';
export const createIncrementAction = data => ({type: INCREMENT, data})
export const createDecrementAction = data => ({type: DECREMENT, data})
7.1.5 求和案例—异步action版
概要总结
1、同步action与异步action
2、创建异步action
3、redux-thunk中间键
一、同步action与异步action
从流程图中,action是一个对象,里面包含了type和data。这种一般对象的action称之为同步action,而function类型的action称之为异步action。
目前的异步是组件自身使用了setTimeout来实现异步,在回调函数里的dispatch传递的action实际上还是一个普通对象,属于同步action。
component/Count/index.jsx:
import React, {Component} from 'react'
import store from '../../redux/store'
import {createIncrementAction, createDecrementAction} from '../../redux/count_action'
class Count extends Component {
// 异步加
incrementAsync = () => {
const {value} = this.selectNumber
setTimeout(() => {
store.dispatch(createIncrementAction(value * 1))
}, 500)
}
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</h1>
......
</div>
);
}
}
export default Count;
二、创建异步action
异步action的作用是把这个异步的操作在action处理,组件不需要自己去考虑异步的相关操作,哪怕是setTimeout也不用写在组件里,交给action去处理,组件只需要告诉异步的时间等等信息即可。
component/Count/index.jsx:
import React, {Component} from 'react'
import store from '../../redux/store'
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action'
class Count extends Component {
// 异步加
incrementAsync = () => {
const {value} = this.selectNumber
store.dispatch(createIncrementAsyncAction(value * 1, 500))
}
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</h1>
......
</div>
);
}
}
export default Count;
redux/count_action.js:
/* * 该文件专门为Count组件生成action对象 * */
import {INCREMENT, DECREMENT} from './constant';
// 同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({type: INCREMENT, data})
export const createDecrementAction = data => ({type: DECREMENT, data})
// 异步action,就是指action的值为函数,异步action中一般都会调用同步action
export const createIncrementAsyncAction = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time)
}
}
注意:异步action一般都会调用同步的action,因此它的回调函数里已经帮我们传入了dispatch参数,我们可以直接用它就能分发同步action了,不用特地引入store来调用dispatch。
三、redux-thunk中间键
异步action返回一个function,它会报错action必须是一个普通的Object类型,请你使用一个中间键来处理异步action。
对于store而言,它只接收Object类型的action,如果有一个中间键,那么store就能帮我们执行一下这个异步action返回的函数,函数里面实际上包裹着一个同步的action,这样store就能识别然后交给reducer处理。
1、安装redux-thunk依赖
npm i redux-thunk
2、redux-thunk的使用
因为现在是要让store接受异步action,因此redux-thunk中间键是用于store里的。先在redux里引入applyMiddleware,然后在createStore里的第二个参数传入applyMiddleware(thunk)。
/* * 该文件专门用于暴露一个store对象,整个应用只有一个store对象 * */
// 引入createStore,专门用于创建redux中最为核心的store对象
import {createStore, applyMiddleware} from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './count_redux'
// 引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
// 暴露store
export default createStore(countReducer, applyMiddleware(thunk))
7.2 react-redux
7.2.1 对react-redux的理解
概要总结
1、react-redux的理解
一、所有的UI组件都应该包裹一个容器组件,他们是父子关系
二、容器组件是真正和redux打交道的,里面可以随意的使用redux的api
三、UI组件中不能使用任何redux的api
四、容器组件会传给UI组件
1、redux中所保存的状态
2、用于操作状态的方法
五、备注:容器给UI传递:状态、操作状态的方法,均通过props传递
7.2.2 求和案例—连接容器组件与UI组件
需求:
1、根据下拉框数字进行相应的运算操作
2、计算当前的求和值
概要总结
1、同步action与异步action
2、创建异步action
3、redux-thunk中间键
一、UI组件删除所有关于redux的代码
react-redux规定跟redux打交道的是容器组件,UI组件想操作redux只能通过容器组件,因此第一步先把所有UI组件对redux的相关操作清理干净。
component/Count/index.jsx:
import React, {Component} from 'react'
class Count extends Component {
// 加法
increment = () => {
const {value} = this.selectNumber
}
// 减法
decrement = () => {
const {value} = this.selectNumber
}
// 奇数再加
incrementIfOdd = () => {
const {value} = this.selectNumber
}
// 异步加
incrementAsync = () => {
const {value} = this.selectNumber
}
render() {
return (
<div>
<h1>当前求和为:????</h1>
......
</div>
);
}
}
export default Count;
二、创建容器组件
容器组件跟react的组件不一样,它是一个桥梁,左边是UI组件,右边是redux。因此这个容器组件功能很多,需要依赖react-redux库来创建。
1、安装react-redux依赖
npm i react-redux
2、引入UI组件与redux
因为容器组件的目的就是关联UI组件和redux,因此要把它们俩都引进来。redux最核心的模块是store,因此只需要引入store即可。
containers/Count/index.jsx:
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入store import store from '../../redux/store'
3、连接UI组件与redux
react-redux提供了一个connect方法。它通过connect()()的形式创建连接,只需要把UI组件传入第二个方法里即可。
containers/Count/index.jsx:
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入store
import store from '../../redux/store'
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
// 使用connect()()创建并暴露一个Count的容器组件
export default connect()(CountUI)
4、容器组件的store
connect()(CountUI)虽然没有关联store,但已经通过import方式引入,而它报错说的是不能找到store。
因为容器组件的store不能自己引入,需要在它的父组件通过props把store传递进去。
src/App.js:
import React, {Component} from 'react';
import Count from './containers/Count'
import store from './redux/store'
class App extends Component {
render() {
return (
<div>
<Count store={store}/>
</div>
);
}
}
export default App;
src/containers/Count/index.jsx:
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
// 使用connect()()创建并暴露一个Count的容器组件
export default connect()(CountUI)
7.2.3 求和案例—react-redux基本使用
概要总结
1、容器组件与UI组件的props传递
2、容器组件的state与dispatch
3、容器组件引入action
一、容器组件与UI组件的props传递
根据react-redux的设计,在connect()()的第一个函数里,第一个参数需要传一个函数,而它的返回值作为状态传递给UI组件。第二个参数是操作状态的方法,同样通过函数的返回指传给UI组件。
由于props传递的一定是key/value的形式,所以函数的返回值必须是一个对象。
containers/Count/index.jsx:
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
// a函数返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value——状态
function a() {
return {count: 900}
}
// b函数返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value——操作状态的方法
function b() {
return {jia: () => {console.log(1)}}
}
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(a, b)(CountUI)
此时在UI组件已经可以通过props接受到容器组件的传参了。
components/Count/index.jsx:
import React, {Component} from 'react'
class Count extends Component {
......
render() {
console.log('UI组件接收到的props是', this.props)
return (
<div>
<h1>当前求和为:{this.props.count}</h1>
......
</div>
);
}
}
export default Count;
二、容器组件的state与dispatch
容器组件的状态是读取redux的,它必须要调用store.getState(),因此react-redux事先给我们在第一个函数里帮我们传入了state状态,它就相当于store.getState()。
容器组件的操作状态方法是对redux而言的,实际上就相当于分发任务,交给reducer进行数据加工处理,它必须要调用store.dispatch(action),因此react-redux事先给我们在第二个函数里帮我们传入了dispatch,直接用它进行分发即可。
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
// a函数返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value——状态
function a(state) {
return {count: state}
}
// b函数返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value——操作状态的方法
function b(dispatch) {
return {jia: number => {
// 通知redux执行加法
dispatch({type: 'increment', data: number})
}}
}
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(a, b)(CountUI)
三、容器组件引入action
由于connect的第二个参数需要分发action,而action已经定义在redux的action里,因此只需要把action.js引入即可。
containers/Count/index.jsx:
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入action
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action';
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
function mapStateToProps(state) {
return {count: state}
}
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)
7.2.4 求和案例—优化1—简写mapDispatch
概要总结
1、简写mapDispatch
一、简写mapDispatch
容器组件的操作状态方法是一个函数,react-redux帮我们带上dispatch,方便我们去分发action。在这里还可以简写成一个对象,连dispatch分发的功能也可以省略。
containers/Count/index.jsx:
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action';
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count: state}),
// mapDispatchToProps的一般写法
// dispatch => ({
// jia: number => dispatch(createIncrementAction(number)),
// jian: number => dispatch(createDecrementAction(number)),
// jiaAsync: (number, time) => dispatch(createIncrementAsyncAction(number, time))
// })
// mapDispatchToProps的简写
{
jia: createIncrementAction,
jian: createDecrementAction,
jiaAsync: createIncrementAsyncAction
}
)(CountUI)
简写之后,本来是function类型变成了对象,对于UI组件而言,它同样是执行this.props.jia(value * 1),但是本来它可以接收参数然后通过dispatch进行分发,而现在只是执行了createIncrementAction方法,这个方法只会返回一个action对象{type: INCREMENT, data}。
src/redux/count_action.js:
/* * 该文件专门为Count组件生成action对象 * */
import {INCREMENT, DECREMENT} from './constant';
// 同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({type: INCREMENT, data})
export const createDecrementAction = data => ({type: DECREMENT, data})
// 异步action,就是指action的值为函数,异步action中一般都会调用同步action
export const createIncrementAsyncAction = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time)
}
}
注意:这里之所以能简写,是因为react-redux设计只要mapDispatchToProps返回的是一个action,它自动帮我们分发出去,不用我们手动调dispatch分发。
7.2.5 求和案例—优化2—Provider组件的使用
概要总结
1、简化对redux状态监听
2、Provider组件的使用
一、简化对redux状态监听
react对redux状态的监听,需要使用store的subscribe,一旦状态发生改变,我们就需要重新更新组件。
使用react-redux库之后,可以省略监听了,因为它已经帮我们监听了。容器组件通过connect()()关联UI组件和redux,它就默认拥有了监测redux状态改变的能力。
src/index.js:
// 引入ReactDOM的createRoot
import {createRoot} from 'react-dom/client'
// 引入App组件
import App from './App'
// import store from './redux/store'
const containter = document.getElementById('root')
const root = createRoot(containter)
// 渲染App到页面
root.render(<App/>)
// 监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
// store.subscribe(() => {
// root.render(<App/>)
// })
二、Provider组件的使用
因为容器组件不允许自己引入store,必须要靠父组件把store传进来,如果容器组件有很多,那就要给每一个组件一个一个传递store。
import React, {Component} from 'react';
import Count from './containers/Count'
import store from './redux/store'
export default class App extends Component {
render() {
return (
<div>
{/* 给容器组件传递store */}
<Count store={store}/>
<Demo1 store={store}/>
<Demo1 store={store}/>
<Demo1 store={store}/>
<Demo1 store={store}/>
<Demo1 store={store}/>
<Demo1 store={store}/>
</div>
);
}
}
react-redux提供了一个Provider组件,它的作用是帮你统一管理各个组件都需要的东西。例如这里每个容器组件都需要store,那我们可以把store传给Provider进行管理,Provider会根据组件需要自动传递过去,不需要我们手动传递。
src/App.jsx:
import React, {Component} from 'react';
import Count from './containers/Count'
// import store from './redux/store'
export default class App extends Component {
render() {
return (
<div>
{/*<Count store={store}/>*/}
<Count/>
</div>
);
}
}
我们可以在根组件外层包裹,那么所有子组件都可以统一管理。
src/index.js:
// 引入ReactDOM的createRoot
import {createRoot} from 'react-dom/client'
// 引入App组件
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'
const containter = document.getElementById('root')
const root = createRoot(containter)
// 渲染App到页面
root.render(
<Provider store={store}>
<App/>
</Provider>
)
7.2.6 求和案例—优化3—整合UI组件与容器组件
概要总结
1、整合UI组件与容器组件
一、整合UI组件与容器组件
目前UI组件与容器组件是分离的,容器组件的作用是连接UI组件和redux。但这样一来,一个组件就变两个,组件一旦多起来,就变成了两套组件。
虽然react-redux要求容器组件与UI组件分开,但并不一定要拆开两个文件。容器组件引入UI组件,这里就不用引入了,直接把UI组件写在容器组件里即可。
src/index.js:
import React, {Component} from 'react'
// 引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action';
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
class Count extends Component {
......
}
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count: state}),
{
jia: createIncrementAction,
jian: createDecrementAction,
jiaAsync: createIncrementAsyncAction
}
)(Count)
7.3 数据共享
7.3.1 求和案例—数据共享—编写Person组件
概要总结
1、优化redux目录
2、新增Person组件
一、优化redux目录
在真实项目开发中,redux下会有actions和reducers两个子目录来分别管理action和reducer。
二、新增Person组件
import React, {Component} from 'react';
export default class Person extends Component {
addPerson = () => {
const name = this.nameNode.value
const age = this.ageNode.value
console.log(name, age)
}
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>
<li>名字1--年龄1</li>
<li>名字2--年龄2</li>
<li>名字3--年龄3</li>
</ul>
</div>
);
}
}
7.3.2 求和案例—数据共享—编写Person组件的reducer
概要总结
1、编写Person组件的redux模块
一、添加Person组件的redux
1、添加Person的常量
预先把Person的action里的type在常量定义好。
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
2、添加Person的action
src/redux/actions/person.js
import {ADD_PERSON} from '../constant';
// 创建增加一个人的action动作对象
export const createAddPersonAction = personObj => ({type: ADD_PERSON, data: personObj})
3、添加Person的reducer
Person的reducer添加一个人的信息。
src/redux/actions/reducer.js
import {ADD_PERSON} from '../constant';
// 初始化人的列表
const initState = [{id: '001', name: 'tom', age: 18}]
export default function personReducer(preState = initState, action) {
const {type, data} = action
switch (type) {
case ADD_PERSON: // 若是添加一个人
return [data, ...preState]
default: return preState
}
}
7.3.3 求和案例—数据共享—完成数据共享
概要总结
1、合并reducer
2、person组件使用react-redux连接
3、count组件与person组件数据互通
一、合并reducer
如果只有一个组件使用redux,那么redux的状态想怎么存都可以。但如果存在多个组件同时使用,那么redux必须使用一个对象来存储,而且在创建store的时候,也不能单独传某一个reducer,要先把所有的reducer合并在一起然后再传过去。
redux提供了combineReducers方法对所有reducer进行合并,它需要传一个对象,这个对象很关键,它会直接影响redux的整个状态结构。对象的键对应一个reducer,在取redux状态的时候,也需要根据键来取。例如{he: countReducer, rens: personReducer},在取countReducer状态的时候,就要这样读取:state.he。
src/redux/store.js:
// 引入createStore,专门用于创建redux中最为核心的store对象
import {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'
// 汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
he: countReducer,
rens: personReducer
})
// 暴露store
export default createStore(allReducer, applyMiddleware(thunk))
src/containers/Count/index.jsx:
import React, {Component} from 'react'
// 引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/actions/count';
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
class Count extends Component {
......
}
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count: state.he}),
{
jia: createIncrementAction,
jian: createDecrementAction,
jiaAsync: createIncrementAsyncAction
}
)(Count)
二、person组件使用react-redux连接
1、创建person容器组件
src/containers/Person/index.jsx
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.jiaYiRen(personObj)
}
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 => ({yiduiren: state.rens}), // 映射状态
{jiaYiRen: createAddPersonAction}
)(Person)
三、count组件与person组件数据互通
组件之间相互读取redux的状态,其实已经很简单了,因为它们的reducer都是通过对象的形式存在了store里,只需要找到对应的键就可以读出任意一个组件的redux状态。
1、count组件读取person状态
src/containers/Count/index.jsx:
import React, {Component} from 'react'
// 引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/actions/count';
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
class Count extends Component {
......
render() {
// console.log('UI组件接收到的props是', this.props)
return (
<div>
<h2>我是Count組件,下方组件总人数为:{this.props.renshu}</h2>
<h4>当前求和为:{this.props.count}</h4>
......
</div>
);
}
}
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count: state.he, renshu: state.rens.length}),
{
jia: createIncrementAction,
jian: createDecrementAction,
jiaAsync: createIncrementAsyncAction
}
)(Count)
2、person组件读取count状态
src/containers/Person/index.jsx:
import React, {Component} from 'react';
import {nanoid} from 'nanoid';
import {connect} from 'react-redux'
import {createAddPersonAction} from '../../redux/actions/person'
class Person extends Component {
......
render() {
return (
<div>
<h2>我是Person组件,上方组件求和为{this.props.he}</h2>
......
</div>
);
}
}
export default connect(
state => ({yiduiren: state.rens, he: state.he}), // 映射状态
{jiaYiRen: createAddPersonAction}
)(Person)
7.3.4 求和案例—纯函数
一、纯函数
1、一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)
2、必须遵守以下一些约束
(1)不得改写参数数据
(2)不会产生任何副作用,例如网络请求,输入和输出设备
(3)不能调用Date.now()或者Math.random()等不纯的方法
3、redux的reducer函数必须是一个纯函数
7.4 使用redux调试工具
7.4.1 求和案例—redux开发者工具
概要总结
1、浏览器安装redux_dev_tools插件
2、安装redux-devtools-extension依赖
3、在store引入redux-devtools-extension库
4、redux开发者工具简介
一、浏览器安装redux_dev_tools插件
二、安装redux-devtools-extension依赖
npm i redux-devtools-extension
三、在store引入redux-devtools-extension库
import {composeWithDevTools} from 'redux-devtools-extension'
这个composeWithDevTools是作为createStore的第二个参数传入:
createStore(allReducer, composeWithDevTools())
但如果异步action也存在的时候,因为它也是createStore的第二个参数,那么异步action作为composeWithDevTools的参数传进去:
createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
// 引入createStore,专门用于创建redux中最为核心的store对象
import {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'
// 引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension';
// 汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
he: countReducer,
rens: personReducer
})
// 暴露store
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
四、redux开发者工具简介
1、Inspector区域
在左边主要是用来记录每一次操作action的类型,例如刚初始化的时候就是@@INIT,如果点击加号、减号操作,它都会记录下来。
Jump按钮可以让我们快速跳转到记录的action状态,也就是可以方便回退。
2、redux区域
(1)Action
它主要是用来显示当前action操作的类型和数据。
(2)State
它是用来记录redux保存的总的状态对象。
(3)Diff
它是用来显示状态的比较,前一个状态到现在的状态。
3、底部区域
(1)播放条
它的作用是可以把你的所有action从头到尾重新一次一次执行,进行一个回放。
(2)控制台悬浮按钮
底部左侧3个按钮控制redux控制台的悬浮位置,分别是左、右、下。
(3)键盘按钮
它是一个编码区域,可以在控制台通过编码临时操作action。