第7章:redux

72 阅读24分钟

7.1 使用redux编写应用

7.1.1 工作流程

概要总结

1、什么是redux

2、redux的三个核心概念

3、redux的工作流程

image.png

一、什么是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、计算当前的求和值

image.png

一、创建计数器组件

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;

image.png

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
    }
  }
}

image.png

三、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已经把数据加工了,也把最新的状态返回了,但是页面上并没有任何变化。

image.png

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。

image.png

对于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的理解

image.png

一、所有的UI组件都应该包裹一个容器组件,他们是父子关系

二、容器组件是真正和redux打交道的,里面可以随意的使用redux的api

三、UI组件中不能使用任何redux的api

四、容器组件会传给UI组件

1、redux中所保存的状态

2、用于操作状态的方法

五、备注:容器给UI传递:状态、操作状态的方法,均通过props传递

7.2.2 求和案例—连接容器组件与UI组件

需求:

1、根据下拉框数字进行相应的运算操作

2、计算当前的求和值

image.png

概要总结

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。

image.png

因为容器组件的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;

image.png

二、容器组件的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。

image.png

二、新增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>
    );
  }
}

image.png

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)

image.png

三、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)

image.png

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)

image.png

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插件

image.png

二、安装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开发者工具简介

image.png

1、Inspector区域

在左边主要是用来记录每一次操作action的类型,例如刚初始化的时候就是@@INIT,如果点击加号、减号操作,它都会记录下来。

image.png

Jump按钮可以让我们快速跳转到记录的action状态,也就是可以方便回退。

image.png

image.png

2、redux区域

(1)Action

它主要是用来显示当前action操作的类型和数据。

image.png

(2)State

它是用来记录redux保存的总的状态对象。

image.png

image.png

(3)Diff

它是用来显示状态的比较,前一个状态到现在的状态。

image.png

3、底部区域

(1)播放条

它的作用是可以把你的所有action从头到尾重新一次一次执行,进行一个回放。

image.png

(2)控制台悬浮按钮

底部左侧3个按钮控制redux控制台的悬浮位置,分别是左、右、下。

image.png

image.png

image.png

(3)键盘按钮

它是一个编码区域,可以在控制台通过编码临时操作action。

image.png

7.5 代码地址

gitee.com/huang_jing_…