【React自学笔记05】完结撒花~React幼儿园大班毕业啦

七、redux

7.1 redux理解

7.1.1 redux是什么

  1. redux是一个专门用于做状态管理的JS库(不是react插件库)。

  2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用。

  3. 作用: 集中式管理react应用中多个组件共享的状态。

7.1.2 redux使用场景

  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。

  2. 一个组件需要改变另一个组件的状态(通信)。

  3. 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

7.1.3 redux原理图

有点类似于 客人-服务员-店长-大厨 的关系

也类似于 客户-产品-项目经理-程序员 的关系

1. action

  • 动作的对象

  • 包含2个属性

    • type:标识属性, 值为字符串, 唯一, 必要属性 例:+、-、*、÷

    • data:数据属性, 值类型任意, 可选属性 例:2、3、5、6

2. dispatch

  • 分发action

  • 保证能把action对象继续向下传,交给store

3. Store

  • 全局的调度者,指挥者,负责掌控全局

  • 将state、action、reducer联系在一起的对象

  • 功能

    • getState(): 得到state

    • dispatch(action): 分发action, 触发reducer调用, 产生新的state

    • subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

4. reducer

  • 用于初始化状态(previousState)、加工状态

  • 加工时,根据旧的state(如果没有就是undefined)和action, 产生新的state的纯函数

——————————更新于2022.5.28——————————

7.2 redux使用

version-1

安装 npm install --save redux

创建redux文件夹->新建store.js和XXX_reducer.js

在store.js文件中暴露store对象,整个应用只有一个store对象

  • 引入createStore,专门用于创建store对象

  • 引入为组件服务的reducer

  • 将用createstore()创建的store对象暴露出去

import { legacy_createStore as createStore } from "redux";
import countReducer from './count_reducer'
export default createStroe(countReducer)

在XXX_reducer.js文件中编写

  • 该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数

  • reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)

  • reducer有两个作用:初始化状态、加工状态

  • xxxReducer是一个纯函数,只负责写逻辑 ,不负责判断,接收preState、action,返回加工后的状态

  • reducer被第一次调用时,是store自动触发的

    • 传递的preState是undefined

    • 传递的action是:

export default function countReducer(preState, action) {
  // 从action对象中获取:type、data
  const { type, data } = action
  // 根据type决定如何加工数据
  switch (type) {
    case 'increment':
      return preState + data
    case 'decrement':
    default:
      return 0 //初始化
  }
}

使用redux

  • 在需要用到状态的组件中引入store

  • 获取redux中的count值 store.getState()

  • 通知redux加store.dispatch(),将type和data作为参数对象传入

  • redux里面的状态更改不会引起页面更新,redux只负责管理。需要监测一下如果redux中的状态发生改变需要自己调用组件的render

  • 在生命周期钩子componentDidMount中调用store.subscribe()函数用于监测redux中状态的变化,只要变化,就调用render【组件只要一挂载,就开始监测】

    • componentDidMount()在组件加载完成, render之后进行调用,只会执行一次
  • 如果redux中管理的状态很多,每个状态都要在componentDidMount中写一遍store.subscribe(),不如直接把store.subscribe()写到index.js入口文件中,(暂时还不知道怎么写)

import React, { Component } from 'react'
import store from '../../redux/store';
export default class Count extends Component {
  componentDidMount() {
    store.subscribe(() => {
      this.setState({})
    })
  }
  increase = () => {
    const value = this.selected.value;
    // 通知组件加
    store.dispatch({ type: 'increment', data: value * 1 })
  }
  decrease = () => {
    const value = this.selected.value;
    store.dispatch({ type: 'decrement', data: value * 1 })
  }
  increaseIfOdds = () => {
    const value = this.selected.value;
    const count = store.getState()
    if (count % 2 !== 0) {
      store.dispatch({ type: 'increment', data: value * 1 })
    }
  }
  increaseIfAsync = () => {
    const value = this.selected.value;
    setTimeout(() => {
      store.dispatch({
        type: 'increment', data: value * 1
      })
    }, 500)
  }
  render() {
    return (
      <div>
        <h1>当前求和为:{store.getState()}</h1>
        <select ref={(cNode) => { this.selected = cNode }}>
          <option value="1" >1</option>
          <option value="2" >2</option>
          <option value="3" >3</option>
        </select>
        <button onClick={this.increase}>+</button>
        <button onClick={this.decrease}>-</button>
        <button onClick={this.increaseIfOdds}>奇数再加</button>
        <button onClick={this.increaseIfAsync}>异步再加</button>
      </div>
    )
  }
}

——————————更新于2022.5.30——————————

Version-2

在redux文件下新建xxx_action.js文件

  • 该文件专门为count组件生成action对象

    export const createIncrementAction=(data)=>{
      return { type:'increment',data}
    }
    export const createDecrementAction=(data)=>{
      return { type:'decrement',data}
    }
    

在index.jsx文件中

  • 引入count_action.js文件,专门用于创建action对象

  • 使用创建的action对象作为参数传入 store.dispatch中

    import { createIncrementAction, createDecrementAction } from '../../redux/count_action';
    
     increase = () => {
        const value = this.selected.value;
        store.dispatch(createIncrementAction(value * 1))
      }
    

在redux文件夹中新建constant.js文件

  • 该模块用于定义action对象中type类型的常量值

  • 便于管理,为了避免因为type值写错造成的程序无法正常运行问题

  • 凡是用到'increment','decrement'的地方都引入constant.js文件并改成INCREMENT和DECREMENT

//constant.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
//count_action.js
import { INCREMENT, DECREMENT } from "./constant"
export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: DECREMENT, data })

7.3 redux的核心API总结

7.3.1 createstore()

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

7.3.2 store对象

  1. 作用: redux库最核心的管理对象

  2. 它内部维护着state和reducer

  3. 核心方法:

    • getState()

    • dispatch(action)

    • subscribe(listener)

  4. 具体编码:

    • store.getState()

    • store.dispatch({type:'INCREMENT', number})

    • store.subscribe(render)

7.3.3 applyMiddleware()

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

7.3.4 combineReducers()

作用:合并多个reducer函数

7.4 redux异步编程

关于异步编程

  • 当action为object一般对象称同步action;action的值为function函数称异步action,因为function函数中可以开启异步任务

  • redux默认是不能进行异步处理的

  • 某些时候应用中需要在redux中执行异步任务(ajax, 定时器)

异步编程的步骤:

编程创建的action函数不再返回一般对象,而是一个函数,该函数中写异步任务

异步任务有结果后,分法一个同步的action去真正操作数据

//count_action.js
import { INCREMENT, DECREMENT } from "./constant"
import store from "./store"

export const createIncrementAsyncAction=(data,time)=>{
 return ()=>{
   setTimeout(()=>{
     // 通知store开启异步任务
     store.dispatch(createIncrementAction(data))
   },time)
 }
}
//count.js
increaseIfAsync = () => {
  const value = this.selected.value;
  store.dispatch(createIncrementAsyncAction(value*1,500))
}

使用异步中间件

  • 安装中间件npm install --save redux-thunk

  • 在store.js中引入thunk import thunk from 'redux-thunk'

//store.js
import { legacy_createStore as createStore ,applyMiddleware} from "redux";
import countReducer from './count_reducer'

import thunk from 'redux-thunk'
export default createStore(countReducer, applyMiddleware(thunk))

💌 注意:异步action中一般都会调用同步action

//count_action.js
import { INCREMENT, DECREMENT } from "./constant"
export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: DECREMENT, data })

export const createIncrementAsyncAction = (data, time) => {
  return (dispatch) => {
    setTimeout(() => {
      // 通知store开启异步任务
      dispatch(createIncrementAction(data))
    }, time)
  }
}

🍀总结:异步实现有两种方式

  • 类似于客人到餐厅吃饭时,客人自己看菜单看了五分钟,然后点菜(延迟的动作交给组件自身)

  • 还有一种客人即刻点菜,但是要求五分钟后再上菜(延迟的动作交给action)

所以:异步action不是必须要用的

7.5 react-redux

7.5.1 理解

  1. 一个react插件库

  2. 专门用来简化react应用中使用redux

  3. react-redux模型图

react-Redux将所有组件分成两大类

  1. UI组件

    • 只负责 UI 的呈现,不带有任何业务逻辑

    • 通过props接收数据(一般数据和函数)

    • 不使用任何 Redux 的 API

    • 一般保存在components文件夹下

  2. 容器组件

    • 负责管理数据和业务逻辑,不负责UI的呈现

    • 使用 Redux 的 API

    • 一般保存在containers文件夹下

7.5.2 使用

安装 npm install react-redux --save

新建containers文件夹-创建CountUI对应的文件夹,用于UI与容器建立连接

  • 引入Count的UI组件

  • 引入redux中的store

  • 使用connect()()创建并暴露容器组件,用于连接UI组件与redux。connect调用的返回值仍然是一个函数

  • 类比容器连接左手的UI,连接右手的redux

    import CountUI from '../../components/Count'
    import store from '../../redux/store'
    import {connect} from 'react-redux'
    export default connect()(CountUI)
    

重新在App.jsx中引入container下的UI组件

  • 此时还有一个问题:虽然上述逻辑是正确的,但是容器连接redux不能直接import引入,需要在用到该标签处引入

  • 代码更新后如下:

    //App.js
    import React, { Component } from 'react'
    import Count from './container/Count'
    import store from './redux/store'
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count store={store}/>
          </div>
        )
      }
    }
    
    //Count.jsx
    import CountUI from '../../components/Count'
    import { connect } from 'react-redux'
    const CountContainer = connect()(CountUI)
    export default CountContainer
    

相关文件代码叙述

🍎Container/Count.js

  • 容器组件:负责和redux通信,将结果交给UI组件

  • 该文件作文容器左边连接UI组件,右边连接redux,但是右边连接redux时,不直接引用store文件,而是在App.js文件中通过props进行传递,在Count容器中来接收state和dispatch

  • 如何创建一个容器组件---靠react-redux的connect函数

    connect(mapstateToProps,mapDispatchToProps)(UI组件)

    • mapstateToProps:映射状态,返回值是一个对象
    • mapDispatchToProps :l映射操作状态的方法,返回值是一个对象
    import CountUI from '../../components/Count'
    import { connect } from 'react-redux'
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction
    } from '../../redux/count_action'
    // 函数返回的对象中的key作为 传递给UI组件props的key value就作为传递给UI组件props的value——状态
    function mapStateToProps(state) {
      return { count: state }
    }
    // 函数返回的对象中的key作为传递给UI组件props的key value就作为传递给UI组件props的value——操作状态的方法
    function mapDispatchToProps(dispatch) {
      return {
        add: (data) => {
          // 通知redux执行加法
          dispatch(createIncrementAction(data))
        },
        minus: (data) => {
          dispatch(createDecrementAction(data))
        },
        addAsync(data, time) {
          dispatch(createIncrementAsyncAction(data, time))
        }
      }
    }
    export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
    

🍊App.js

  • 该文件用于接收从redux中传递给Container容器的状态和方法

  • 使用标签属性的方式传给容器

    import React, { Component } from 'react'
    import Count from './container/Count'
    import store from './redux/store'
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count store={store} />
          </div>
        )
      }
    }
    

🍋Components/Count

  • UI组件,不能使用任何的redux的api,只负责页面的呈现、交互等

  • 使用this.props接收状态和方法

    import React, { Component } from 'react'
    export default class Count extends Component {
      increase = () => {
        const value = this.selected.value;
        this.props.add(value * 1)
      }
      decrease = () => {
        const value = this.selected.value;
        this.props.minus(value * 1)
      }
      increaseIfOdds = () => {
        const value = this.selected.value;
        if (this.props.count % 2 !== 0) {
          this.props.add(value * 1)
        }
      }
      increaseIfAsync = () => {
        const value = this.selected.value;
        this.props.addAsync(value * 1, 500)
      }
      render(){
          return {...}  
      }
    }
    

————————————记录于2022.5.31————————————

代码优化:

🍐mapDispatchToProps 的简写

import CountUI from '../../components/Count'
import { connect } from 'react-redux'
import {
  createIncrementAction,
  createDecrementAction,
  createIncrementAsyncAction
} from '../../redux/count_action'
// 函数返回的对象中的key作为 传递给UI组件props的key value就作为传递给UI组件props的value——状态

export default connect(
  state => ({ count: state }),
  {
    add: createIncrementAction,
    minus: createDecrementAction,
    addAsync: createIncrementAsyncAction
  }
)(CountUI)

🍇Provider的使用

  • 批量给所有容器组件传递store对象

  • 不需要用容器组件传store,减少代码冗余

  • 在入口文件index.js中引入< Provider>标签

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import store from './redux/store';
    import App from './App';
    import { Provider } from 'react-redux';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <Provider store={store}>
        <App />
      </Provider>
    );
    
    //app.js
    import React, { Component } from 'react'
    import Count from './container/Count'
    
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count />
          </div>
        )
      }
    }
    

🍓文件优化

  • 删除component文件

  • 将UI组件和容器组件写到一起

    //Count.jsx
    import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction
    } from '../../redux/count_action'
    
    class Count extends Component {
      increase = () => {
        const value = this.selected.value;
        this.props.add(value * 1)
      }
      decrease = () => {
        const value = this.selected.value;
        this.props.minus(value * 1)
      }
      increaseIfOdds = () => {
        const value = this.selected.value;
        if (this.props.count % 2 !== 0) {
          this.props.add(value * 1)
        }
      }
      increaseIfAsync = () => {
        const value = this.selected.value;
        this.props.addAsync(value * 1, 500)
      }
      render() {
        return (
          <div>
            <h1>当前求和为:{this.props.count}</h1>
            <select ref={(cNode) => { this.selected = cNode }}>
              <option value="1" >1</option>
              <option value="2" >2</option>
              <option value="3" >3</option>
            </select>
            <button onClick={this.increase}>+</button>
            <button onClick={this.decrease}>-</button>
            <button onClick={this.increaseIfOdds}>奇数再加</button>
            <button onClick={this.increaseIfAsync}>异步再加</button>
          </div>
        )
      }
    }
    export default connect(
      state => ({ count: state }),
      {
        add: createIncrementAction,
        minus: createDecrementAction,
        addAsync: createIncrementAsyncAction
      }
    )(Count)
    

🍑总结

  • 容器组件和UI组件整合一个文件

  • 无需自己给容器组件传递store,给< App/>包裹一个< Provider store={store}>即可

  • 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作

  • mapDispatchToProps也可以简单的写成一个对象

  • 一个组件要和redux“打交道”要经过那几步

    • 定义好UI组件---不暴露

    • 引入connect生成一个容器组件,并暴露,写法如下:

      connect(
      state =>({key :value}){key : xxxxxAction}
      )(UI组件)
      
    • 在UI组件中通过this.props.xxxxxxx读取和操作状态

7.5.3 数据共享

redux如果为多个组件服务的时候,必须用对象的结构保存总状态,根据key保存value

combineReducers传入的对象就是redux帮我们保存的总对象

汇总所有的reducer变为一个总的reducer

//store.js
import { legacy_createStore as createStore, applyMiddleware, combineReducers } from "redux";
import thunk from 'redux-thunk'
import countReducer from './reducer/count'
import personReducer from './reducer/person'
const allReducer = combineReducers({
  countReducer, personReducer
})
export default createStore(allReducer, applyMiddleware(thunk))

添加一个Person组件

🍌reducer/person.js

import { ADD_PERSON } from "../constant";

// 初始化person列表
const initState = [{ id: '001', name: 'tom', age: 18 }, { id: '002', name: 'jack', age: 20 }]
export default function personReducer(preState = initState, action) {
  const { type, data } = action
  switch (type) {
    case ADD_PERSON:
      return [data, ...preState]
    default:
      return initState
  }
}

🍉constant.js和action/person.js

export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
import { ADD_PERSON } from "../constant";
// 准备一个动作对象
export const createAddPersonAction = (personObj) => ({ type: ADD_PERSON, data: personObj })

🍒container/person.jsx

import React, { Component } from 'react'
import { nanoid } from 'nanoid'
import { connect } from 'react-redux';
import { createAddPersonAction } from '../../redux/action/person'
class Person extends Component {
  addPerson = () => {
    let name = this.nameNode.value;
    let age = this.ageNode.value;
    const personObj = { id: nanoid(), name, age }
    this.props.addPerson(personObj)
    this.nameNode.value = ''
    this.ageNode.value = ''
  }
  render() {
    return (
      <div>
        <h1>我是Person组件</h1>
        <input ref={(cNode) => this.nameNode = cNode} type="text" placeholder='请输入名字' />
        <input ref={(cNode) => this.ageNode = cNode} type="text" placeholder='请输入年龄' />
        <button onClick={this.addPerson}>添加</button>
        <ul>
          {
            this.props.person.map((p) => {
              return <li key={p.id}>姓名:{p.name}-----年龄:{p.age}</li>
            })
          }
        </ul>
      </div>
    )
  }
}
export default connect(
  state => ({
    person: state.person,
    count: state.count
  }),
  {
    addPerson: createAddPersonAction
  }
)(Person)

7.6 redux开发者工具

下载工具依赖包

  • npm install --save-dev redux-devtools-extension

在store.js文件中

import { composeWithDevTools } from 'redux-devtools-extension'
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))

7.7 纯函数和高阶函数

7.7.1 纯函数

  1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)

  2. 必须遵守以下一些约束

    • 不得改写参数数据
    • 不会产生任何副作用,例如网络请求,输入和输出设备
    • 不能调用Date.now()或者Math.random()等不纯的方法
  3. redux的reducer函数必须是一个纯函数

7.7.2 高阶函数

  1. 理解: 一类特别的函数

    • 情况1: 参数是函数
    • 情况2: 返回是函数
  2. 常见的高阶函数:

    • 定时器设置函数
    • 数组的forEach()/map()/filter()/reduce()/find()/bind()
    • promise
    • react-redux中的connect函数
  3. 作用: 能实现更加动态, 更加可扩展的功能

————————————本章笔记完成于2022.6.2————————————

posted @ 2022-05-28 13:50  Lu西西  阅读(71)  评论(0)    收藏  举报