【React】React学习笔记4(Redux、React-Redux)


零 l  说明


本文参考教程:[尚硅谷2021版React技术全家桶]

本文上接:
[React学习笔记1(React概念)]
[React学习笔记2(React脚手架、组件通信与网络请求库)]
[React学习笔记3(React路由、Ant Design)]






一 l  Redux 基础


文档:

  1. 英文文档: https://redux.js.org/
  2. 中文文档: http://www.redux.org.cn/
  3. Github: https://github.com/reactjs/redux

Redux 是什么:

  1. redux是一个专门用于做 状态管理 的JS库(不是react插件库)。
  2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
  3. 作用: 集中式管理react应用中多个组件 共享 的状态。
  4. 其实就类似于后端的Redis

为什么用 Redux:

  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。
  2. 一个组件需要改变另一个组件的状态(通信)。
  3. 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

(一)Redux 工作流程

1.1.1

白话版流程:
【初始化是store自动调用。此时previousState是undefined;action的types是“@@redux/INTIxxx”,data为“
空”】

  1. 从 React Components[React组件] 开始,发送“action[动作对象]”给 Action Creators[动作创建器]
    (1) action动作对象是包含type属性和data属性的Object对象,如{type:'increment', data: 1};或是函数(Redux 高级会细说)
  2. Action Creators[动作创建器] 使用dispatch(action)函数,把action[动作对象]发送给 Store[仓库]
  3. Store[仓库] 将 (previousState, action)[之前的状态, 动作对象] 发送给 Reducers[归纳器] ,Reducers[归纳器] 计算完后返回newState[新状态]回 Store[仓库]
  4. React Components[React组件] 从 Store[仓库] 获取处理完的新数据

action(一个需要的组件一个)

  1. 动作的对象
  2. 包含2个属性
    (1) type:标识属性, 值为字符串, 唯一, 必要属性
    (2) data:数据属性, 值类型任意, 可选属性
  3. 例子:{ type: ‘ADD_STUDENT’,data:{name: ‘tom’,age:18} }

reducer(一个需要的组件一个)

  1. 用于初始化状态、加工状态。
  2. 加工时,根据旧的state和action, 产生新的state的纯函数。

store(唯一)

  1. 将state、action、reducer联系在一起的对象
  2. 如何得到此对象?
    (1) import {createStore} from ‘redux’
    (2) import reducer from ‘./reducers’
    (3) const store = createStore(reducer)

(二)Redux 核心API

1、store对象

  1. 作用: redux库最核心的管理对象
  2. 它内部维护着:
    (1) state
    (2) reducer
  3. 核心方法:
    (1) getState(): 获取store中state状态值
    (2) dispatch(action): 分发action, 触发reducer调用, 产生新的state
    (3) subscribe(listener): 注册监听, 当store存储了新的state时, 自动调用
  4. 具体编码:
    (1) store.getState()
    (2) store.dispatch({type:‘INCREMENT’, number})
    (3) store.subscribe(render)

2、createStore(reducer)

作用:创建包含指定reducer的store对象

3、applyMiddleware()

作用:应用上基于redux的中间件(插件库)

4、combineReducers()

作用:合并多个reducer函数


(三)demo 1.0

效果图:
1.2.1

1、文件说明及注意事项

  1. projectName/src/redux/store.js:【唯一的store定义】
    (1) 引入redux中的createStore函数,创建一个store
    (2) createStore调用时要传入一个为其服务的reducer
    (3) 记得暴露store对象
  2. projectName/src/redux/count_reducer.js:【定义count组件的reducer】
    (1) reducer的本质是一个函数,接收:preState,action,返回加工后的状态
    (2) reducer有两个作用:初始化状态,加工状态
    (3) reducer被第一次调用时,是store自动触发的。传递的preState是undefined,传递的action是:{type:’@@redux/INIT随机字符串}
  3. projectName/src/redux/count_action.js:【专门用于创建count组件的action对象】
  4. projectName/src/redux/constant.js:【放置容易写错的type值】
  5. projectName/src/index.js:【在index.js中监测store中状态的改变】
    一旦发生改变重新渲染
    备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。

2、主要代码

projectName/src/redux/constant.js:【放置容易写错的type值】

export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

projectName/src/redux/store.js:【唯一的store定义】

// 引入createStore,专门用于创建redux中最为核心的store对象
import {createStore} from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'
// 暴露store
export default createStore(countReducer)

projectName/src/redux/count_reducer.js:【定义count组件的reducer】

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

projectName/src/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})

projectName/src/components/Count/index.jsx

import React, { Component } from 'react'
// 引入store,用于获取redux中保存状态
import store from '../../redux/store'
// 引入actionCreator,专门用于创建action对象
import {createIncrementAction,createDecrementAction} from '../../redux/count_action'

export default class Count extends Component {

 state = {vars: '其他不需要共享的变量'}

 // 加法
 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>
       <select ref={c => this.selectNumber = c}>
         <option value="1">1</option>
         <option value="2">2</option>
         <option value="3">3</option>
       </select>&nbsp;
       <button onClick={this.increment}>+</button>&nbsp;
       <button onClick={this.decrement}>-</button>&nbsp;
       <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
       <button onClick={this.incrementAsync}>异步加</button>&nbsp;
     </div>
   )
 }
}

projectName/src/index.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'

ReactDOM.render(<App/>,document.getElementById('root'))

// 检测状态的改变,实时渲染页面
store.subscribe(()=>{
 ReactDOM.render(<App/>,document.getElementById('root'))
})

projectName/src/App.jsx

import React, { Component } from 'react'
import Count from './components/Count'

export default class App extends Component {
 render() {
   return (
     <div>
       <Count/>
     </div>
   )
 }
}






二 l  Redux 高级


(一)【不常用】异步(延迟)发送action

action[动作对象] 有两种格式:

  1. Object 对象(同步action):Action Creators返回一个对象,如{type:'xxx', data: x}
  2. function 函数(异步action):Action Creators返回一个函数,函数中自定义操作

1、为什么需要异步(延迟)发送action

  1. 延迟的动作不想交给组件自身,想交给action
  2. 想要对状态进行操作,但是具体的数据靠action异步任务返回
  3. 组件自身延迟不够通用

2、实现

上面的demo 1.0中,在组件Count中实现了异步(延迟)发送action,这不够通用,下面流程说明在Action Creators中实现异步(延迟)发送action

  1. 安装异步处理插件(命令行中):yarn add redux-thunk
  2. 在store中引入redux-thunk,用于支持异步action

projectName/src/redux/store.js:【唯一的store定义】

// 引入createStore,专门用于创建redux中最为核心的store对象
import {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))
  1. 修改Action Creators的代码:

projectName/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,异步action不是必须要用的。
export const createIncrementAsyncAction = (data, time) => {
 return (dispatch)=>{
   setTimeout(()=>{
     dispatch(createIncrementAction(data))
   }, time)
 }
}

(二)纯函数和高阶函数(redux不起作用bug的原因)

1、纯函数

  1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
  2. 必须遵守以下一些约束:【函数中不能有随机因素,且形参不能改】
    (1) 不得改写参数数据
    (2) 不会产生任何副作用,例如不能有网络请求,输入和输出设备等不靠谱的行为
    (3) 不能调用Date.now()、Math.random()、unshift()、等不纯的方法(输出不确定)
  3. redux的reducer函数必须是一个纯函数

2、高阶函数

  1. 理解: 一类特别的函数
    (1) 情况1: 参数是函数
    (2) 情况2: 返回是函数
  2. 常见的高阶函数:
    (1) 定时器设置函数
    (2) 数组的forEach()/map()/filter()/reduce()/find()/bind()
    (3) promise
    (4) react-redux中的connect函数
  3. 作用: 能实现更加动态, 更加可扩展的功能






三 l  React-Redux(React 官方发布的 Redux 整合包)


(一)介绍

1、模型图

2.2.1

  1. UI组件
    (1) 不能使用任何redux的api,只负责 UI 的呈现
    (2) 通过props接收数据(一般数据和函数)
    (3) 不使用任何 Redux 的 API
    (4) 一般保存在components文件夹下
    (5) UI组件可以放在容器组件的相同文件下,不写这么多文件,容易乱
  2. 容器组件
    (1) 负责和redux通信,将结果交给UI组件,不负责UI的呈现
    (2) 使用 Redux 的 API
    (3) 一般保存在containers文件夹下

优点:

  1. 不用在src/index.js监听state的变化进行实时渲染,提高效率(每次改变都会渲染,就算不渲染到真实DOM,diff算法也是开销)
  2. 可以自动将store传给每个需要的容器

2、相关API

  1. Provider:让所有组件都可以得到state数据
<Provider store={store}>
  <App />
</Provider>
  1. connect:用于包装 UI 组件生成容器组件 (创建容器组件的方法)
import { connect } from 'react-redux'
  connect(
    mapStateToprops,
    mapDispatchToProps
  )(Counter)
  1. mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性
const mapStateToprops = function (state) {
  return {
    value: state
  }
}
  1. mapDispatchToProps:将分发action的函数转换为UI组件的标签属性 (可以是函数也可以是对象)

3、一般代码流程

  1. 定义好UI组件—不暴露
  2. 引入connect生成一个容器组件,并暴露,写法如下:
connect(
  state => ({key1:value}),  // 映射状态
  {key2:xxxxxAction}  // 映射操作状态的方法
)(UI组件)
  1. 在UI组件中通过this.props.key1读取和操作状态

(二)demo 2.0(React-Redux 的基本使用)

不变的文件夹(相对demo 1.0):

  1. projectName/src/redux/store.js:【唯一的store定义】
  2. projectName/src/redux/count_reducer.js:【定义count组件的reducer】
  3. projectName/src/redux/count_action.js:【专门用于创建count组件的action对象】
  4. projectName/src/redux/constant.js:【放置容易写错的type值】
  5. projectName/src/App.jsx

删除的文件夹:

  1. projectName/src/components/Count/index.jsx【Count组件】

代码:

projectName/src/containers/Count/index.jsx

import React, { Component } from 'react'
// 引入action
import {
 createIncrementAction,
 createDecrementAction,
 createIncrementAsyncAction
} from '../../redux/count_action'
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

// 定义UI组件
class Count extends Component {

 state = {carName:'奔驰c63'}

 // 加法
 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>
       <h1>当前求和为:{this.props.count}</h1>
       <select ref={c => this.selectNumber = c}>
         <option value="1">1</option>
         <option value="2">2</option>
         <option value="3">3</option>
       </select>&nbsp;
       <button onClick={this.increment}>+</button>&nbsp;
       <button onClick={this.decrement}>-</button>&nbsp;
       <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
       <button onClick={this.incrementAsync}>异步加</button>&nbsp;
     </div>
   )
 }
}

/** 
* 使用connect()()创建并暴露一个Count的容器组件
* 
* 第一个参数
* 1.mapStateToProps函数返回的是一个对象;
* 2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
* 3.mapStateToProps用于传递状态
* 
* 第二个参数
* 1.mapDispatchToProps函数返回的是一个对象;
* 2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
* 3.mapDispatchToProps用于传递操作状态的方法
*/
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的简写(React-Redux对Redux中API的优化)
 {
   increment:createIncrementAction,
   decrement:createDecrementAction,
   incrementAsync:createIncrementAsyncAction,
 }
)(Count)

projectName/src/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自动的将redux中的store给到需要的组件 */
	<Provider store={store}>
		<App/>
	</Provider>,
	document.getElementById('root')
)

(三)demo 3.0 完整版项目(React-Redux、组件通信、Redux 开发者工具)

1、组件通信注意事项

  1. 定义一个Pserson组件,和Count组件通过redux共享数据。
  2. 为Person组件编写:reducer、action,配置constant常量。
  3. 重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象!!!
  4. 交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位(取到变量)”。
  5. reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer

2、React-Redux 开发者工具的使用

  1. 安装浏览器插件(请自行安装):Redux DevTools
  2. 安装(命令行中):npm install redux-devtools-extension --save
  3. store中进行配置
import {composeWithDevTools} from 'redux-devtools-extension'
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

3、【重要】完整代码

效果图:
3.3.3.1
文件结构:
3.3.3.2

(1)基本代码

projectName/public/index.html

<!DOCTYPE html>
<html>
 <head>
   <meta charset="UTF-8" />
   <title>redux</title>
 </head>
 <body>
   <div id="root"></div>
 </body>
</html>

projectName/src/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')
)

projectName/src/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>
   )
 }
}
(2)Redux - store代码:

projectName/src/redux/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)))

projectName/src/redux/constant.js

/**
* 该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
(3)Redux - actions代码:

projectName/src/redux/actions/count.js

/** 
* 该文件专门为Count组件生成action对象
*/
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不是必须要用的。
export const incrementAsync = (data,time) => {
 return (dispatch)=>{
   setTimeout(()=>{
     dispatch(increment(data))
   }, time)
 }
}

projectName/src/redux/actions/person.js

import {ADD_PERSON} from '../constant'

// 创建增加一个人的action动作对象
export const addPerson = personObj => ({type:ADD_PERSON,data:personObj})
(4)Redux - reducers代码:

projectName/src/redux/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
 }
}

projectName/src/redux/reducers/person.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:  // 若是添加一个人
     // preState.unshift(data) //此处不可以这样写,这样会导致preState被改写了,personReducer就不是纯函数了。
     return [data,...preState]
   default:
     return preState
 }
}

projectName/src/redux/reducers/index.js

/**
* 该文件用于汇总所有的reducer为一个总的reducer
*/
// 引入combineReducers,用于汇总多个reducer
import {combineReducers} from 'redux'
// 引入为Count组件服务的reducer
import count from './count'
// 引入为Person组件服务的reducer
import persons from './person'

// 汇总所有的reducer变为一个总的reducer(这里规定了store中的key)
export default combineReducers({
 count,
 persons
})

容器代码:

projectName/src/contains/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组件
class Count extends Component {

 state = {carName:'奔驰c63'}

 // 加法
 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.persons}</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>&nbsp;
       <button onClick={this.increment}>+</button>&nbsp;
       <button onClick={this.decrement}>-</button>&nbsp;
       <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
       <button onClick={this.incrementAsync}>异步加</button>&nbsp;
     </div>
   )
 }
}

// 使用connect()()创建并暴露一个Count的容器组件
export default connect(
 state => ({
   count:state.count,
   personCount:state.persons.length
 }),
 {increment,decrement,incrementAsync}
)(Count)

projectName/src/contains/Person/index.jsx

import React, { Component } from 'react'
import {nanoid} from 'nanoid'
import {connect} from 'react-redux'
import {addPerson} from '../../redux/actions/person'

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)






本文完,React入门系列完结撒花

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值