【React学习笔记】React—redux

中文文档:https://cn.redux.js.org/
Github: https://github.com/reduxjs/redux

学习的视频是尚硅谷react教程

1、redux理解

redux是专门做状态管理的JS库,可以在react、angular、vue等项目中使用,集中管理 react应用中多个组件共享的状态。Redux 提供的模式和工具使你更容易理解应用程序中的状态何时、何地、为什么、state 如何被更新,以及当这些更改发生时应用程序逻辑将如何表现

1.1使用情况

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

1.2 redux工作流程和核心概念

React Components: react组件
Action Creators: 行为创建器。

action就是动作对象,包括本次动作类型
type和本次操作的数据data。比如在计数器中,x2这个动作,“X”就是动作类型。“2”就是操作的数据。 dispatch(action):分发action对象

Store: 状态仓库(指挥者)

dispatch函数把action交给Store,再将(previousState, action)传给Reducers
将state、action、reducer联系在一起的对象
如何得到此对象??
import {createStore} from ‘redux’
import reducer from ‘./reducers’
const store = createStore(reducer)
此对象的功能??
getState(): 得到state
dispacth(action): 分发action,触发reducer,调用新的state
subcribe(listener):注册监听,当产生了新的state时自动调用

Reducers:处理状态(执行者)

执行完返回 newState给store,Store使用getState()获取最新状态交给React Components组件
用于初始化状态、加工状态,加工时,根据旧的state和action,产生新的state的纯函数

原理图

2、使用redux编写应用

2.1 准备

首先创建新的一个基于脚手架的React应用,可以删除public和src文件夹下的文件,自己按需创建,整个代码目录就会比较简单。在public文件夹下新建index.html文件,写好基本的结构;在src文件夹下新建app.jsx文件和index.js文件,在app.jsx中编写组件,在index.js渲染。

// app.js文件
import React, {Component} from "react";

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

// index.js文件
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./app";


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

最后在项目所在的目录下执行npm start命令,确定正常运行就可以往里面编写需求代码了。

2.2 纯React编写求和案例

在src下面新建文件夹components,在此文件下新建组件并编写需求代码。
下面是components下面Count文件夹下index.jsx的代码,还没有使用redux,只是普通的react程序

import React, {Component} from "react";

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

2.3 mini版redux程序

下载安装redux,在终端下载npm add redux
首先,新建一个文件夹redux,存放所有与redux相关的代码。在redux文件夹新建store.js和count_reducer.js文件。在上面redux的工作流程中提到了这些关键概念。

// store.js文件
/* 
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
// 创建redux的核心store对象
import {legacy_createStore as createStore} from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'

// 暴露store
export default createStore(countReducer)

在count_reducer.js文件:

/* 
该文件是用于创建一个为Count组件服务的reducer,本质就是一个函数
reducer函数的两个参数: 之前的状态(preState)和动作对象action
*/
const initState = 0
// 形参默认值
export default function countReducer(preState=initState, action) {
  // 从action动作中获取type和data
  //if (preState === undefined) preState = 0
  const { type, data } = action
  switch (type) {
    case 'increment':
      return preState + data;
    case 'decrement':
      return preState - data;
    default:
      //初始化
      return preState
  }
}

对应的,组件Count的程序更改为:

import React, {Component} from "react";
//引入store  用于获取redux中的状态
import store  from "../../redux/store";

export default class Count extends Component {
  //检测redux中状态的变化,只要变化就调用render
  componentDidMount(){
    store.subscribe(()=>{
      // 重要redux任何状态就会调用该回调函数
      this.setState({})
    })
  }
  // 加法
  increment = () =>{
    const {value} = this.selectNumber
    store.dispatch({type:'increment', data:value*1})
  }
  //减法
  decrement= () =>{
    const {value} = this.selectNumber
    // 通知redux
    store.dispatch({type:'decrement', data:value*1})
  }
  incrementIfOdd = () =>{
    const {value} = this.selectNumber
    if(store.getState() % 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>
       {/* 获取store的状态 */}
          <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>
      </div>
    )
  }
}

总结一下: 先写好store.js(暴露store对象)和count_reducer.js文件(对action传递的动作类型和数据进行操作,初始化或者修改状态),再在组件中通过store.getState()获取状态,store.dispatch()执行动作, store.subscribe()订阅redux状态更改。
redux的Mini版
(1)去除count组件自身的状态
(2).src下建立
——redux
————store.js
————count_reducer.js
(3) .store.js
引入redux中的legacy_createStore,创建store
.legacy_createStore调用时要传入一个为其服务的reducer
记得要暴露对象
(4).count_reducer.js
reducer本质是一个函数:接收preState,action,返回加工后的状态
reducer第一次被调用时,是store自动触发的,传递的preState是Undefined
(5) 在index.jx中检测store中状态的改变,一旦发生改变重新渲染
redux只负责管理状态,至于状态的改变驱动页面的展示,要自己写。
完整版只需要再新增count_action.js文件,专门创建action对象。另外可以将常用的变量统一写在constant.js中,便于后期管理和维护。

3、 异步action

一般对象类型的action称为同步action,值为函数的action称为异步action。
需要安装一个中间件:npm add redux-thunk

store.js文件
/* 
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
// 创建redux的核心store对象
import {legacy_createStore as createStore, applyMiddleware} from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'
import  thunk from 'redux-thunk'

// 暴露store
export default createStore(countReducer,applyMiddleware(thunk))

4、react-redux

!!! 这部分会很绕,是在redux版本上进行更改对比介绍的,最后面会给大家整理一个最终版本。个人认为中间优化的思路还是可以学习一下的。

由react团队出品的插件库,与react更适配

  • 所有的UI组件都应该包裹在一个容器组件,是父子关系
  • 容器组件是真正和UI组件打交道的,可以随意的使用redux的API
  • UI组件不能使用任何redux的API
  • 容器组件会传给UI组件:(1)redux中所保存的状态 (2)用于操作状态的方法
  • 容器传给UI状态和操作状态的方法均通过props传递
    在这里插入图片描述

4.1 UI组件和容器组件

UI组件:一般放在components文件夹,不能使用任何的redux的api,只负责页面的呈现、交互
容器组件:一般放在container文件夹,负责和redux通信,将结果交给UI组件

(1)容器组件—connect

看上面那种图可知,容器组件起的作用就是连接UI组件和redux的,因此在容器组件在需要引入UI组件和redux,但是redux不需要自己直接引入,而是在App.jsx文件中通过props向容器组件中传入。

connect(mapStateToProps, mapDispatchToProps)(CountUI):靠rect-redux的connect函数,返回值也是一个函数 使用connect()()创建并暴露count容器组件。

mapStateToProps

  1. mapStateToProps函数返回的是一个对象
  2. 返回的对象中key作为传递给UI组件props的Key,value就作为传递给UI组件的value
  3. mapStateToProps用于传递状态

mapDispatchToProps

  1. mapDispatchToProps函数返回的一个对象
  2. 返回的对象中key作为传递给UI组件props的Key,Value就作为传递给UI组件的props的方法
  3. mapStateToProps用于传递操作状态的方法

注意:容器组件的store是通过props传进去的,而不是再容器组件中直接引入,如下面代码所示
mapDispatchToProps也可以是一个对象,下面精简版的代码中可以看到对象的写法

export default class App extends Component {
  render() {
    return (
      <div>
        {/* 给容器组件传递store */}
        <Count store={store}/>
      </div>
    )
  }

下面是src/containers/Count/index.jsx文件的内容:

/**
 * Count的容器组件
 */
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入action
import {createIncrementAction, createDecrementAction, createIncrementActionAsyn} from '../../redux/count_action'
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

/* 映射状态
1.mapStateToProps函数返回的是一个对象
2.返回的对象中key作为传递给UI组件props的Key,value就作为传递给UI组件的value
3.mapStateToProps用于传递状态
*/
const mapStateToProps = (state)=>{
  return {count: state}
}
/*映射操作状态的方法
1.mapDispatchToProps函数返回的一个对象
2.返回的对象中key作为传递给UI组件props的Key,Value就作为传递给UI组件的props的方法
3.mapStateToProps用于传递操作状态的方法
*/
// mapDispatchToProps函数传递操作方法key作为传递给UI组件props的key,Value就作为传递给UI组件的props的方法
const mapDispatchToProps = (dispatch)=>{
  return {
     //通知redux执行加法
    increment:number=> dispatch(createIncrementAction(number)),
    //通知redux执行减法
    decrement:number=> dispatch(createDecrementAction(number)),
    //通知redux执行异步
    asynIncrement:(number,time)=> dispatch(createIncrementActionAsyn(number,time)),
  }
}
// connect的返回值也是一个函数 使用connect()()创建并暴露count容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)

精简版的代码:

import CountUI from '../../components/Count'
import {createIncrementAction, createDecrementAction, createIncrementActionAsyn} from '../../redux/count_action'
import {connect} from 'react-redux'

export default connect(
 state=> ({count: state}),
 {
  increment: createIncrementAction
  decrement: createDecrementAction
  asynIncrement: createIncrementActionAsyn
 },
))(CountUI)

(2)react-redux版的代码优化

src下的index.js

react-redux版本中,可以删去。容器组件已经具有检测状态变化的能力。

store.subscribe(()=>{
  ReactDOM.render(<App/>, document.getElementById('root'))
})

index.js和App.jsx文件

上面说到,容器组件的store是由App.jsx传入的。当存在多个容器组件时,则需要对每一个容器组件进行传值。根据这点也可以进行优化。
需要在入口文件index.js中引入Provider组件

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import store from "./redux/store";
//引入Provider组件
import {Provider} from 'react-redux'

ReactDOM.render(
//App里所有组件都能收到store,只需要写入一次
<Provider store={store}>
  <App/>
</Provider>, 
document.getElementById('root')
)

整合UI组件和容器组件

当组件数目增多时,使用redux的组件数量会成倍增长。根据这点,将UI组件和容器组件整合来优化。
直接讲UI组件的代码复制,通过connect函数连接即可。

/**
 * Count的容器组件
 */
// 整合UI和容器组件,不引入UI组件,直接在这个文件中写UI组件
import {createIncrementAction, createDecrementAction, createIncrementActionAsyn} from '../../redux/count_action'
import {connect} from 'react-redux'
import React, {Component} from "react"

//定义UI组件
class Count extends Component {
  increment = () =>{
    const {value} = this.selectNumber
    // store.dispatch(createIncrementAction(value*1))
    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.asynIncrement(value*1, 500)
      // store.dispatch(createIncrementActionAsyn(value*1, 500))
  }
  render() {
    return (
      <div>
       {/* 获取store的状态 */}
          <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>
      </div>
    )
  }
}

const mapStateToProps = state=> ({count: state})

const mapDispatchToProps = (dispatch)=>{
  return {
     //通知redux执行加法
    increment:number=> dispatch(createIncrementAction(number)),
    //通知redux执行减法
    decrement:number=> dispatch(createDecrementAction(number)),
    //通知redux执行异步
    asynIncrement:(number,time)=> dispatch(createIncrementActionAsyn(number,time)),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Count)

(3) 总结:react-redux优化

(1)容器组件和UI组件整合一个文件
(2)无需自己给容器组件传递store,给包裹
(3)使用react-redux后也不用自己检测redux中状态的改变,容器组件可以自动完成这个工作
(4)mapDispatchToProps可以写成对象
(4)组件与redux交互的步骤
首先定义好一个UI组件,引入connet生成一个容器组件(并暴露),写法如下:

export default connect(
 state=> ({count: state}),
 {
  increment: createIncrementAction
  decrement: createDecrementAction
  asynIncrement: createIncrementActionAsyn
 },
))(CountUI)

接着在UI组件中,通过this.props.XX读取和操作状态

  decrement= () =>{
    const {value} = this.selectNumber
    this.props.decrement( value *1 )
  }

4.2 数据共享

redux的使用场景就是多个组件需要数据共享,接下来介绍的就是多组件使用的情况。

  • 首先定义一个Person组件,和Count组件通过redux共享数据
  • 为Person组件编写:reducer、action,配置constant常量
  • 重点:Person的reducer和Count的reducer要使用combineReducers合并,合并后的总状态是一个对象
/* 
redux文件下的store.js
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
// 创建redux的核心store对象
import {legacy_createStore as createStore, applyMiddleware, combineReducers} from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './reducers/count_reducer'
// 引入为Person组件服务的reducer
import personReducer from './reducers/person'
import  {thunk} from 'redux-thunk'

//合并reducers  传入的就是redux保存的总状态对象
const allReducer = combineReducers({
  he: countReducer,
  rens: personReducer
})

// 暴露store
export default createStore(allReducer,applyMiddleware(thunk))
  • 交给store的总是reducer,最后注意在组件中取出状态的
    在这里插入图片描述
    redux的reducer函数必须是一个纯函数。纯函数就是只要是同样的输入(实参),必定得到同样的输出(返回),不得改写参数数据,不会产生任何副作用(比如网络请求),不能调用Date.Now()或者Math.random()等不纯的方法。

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 store from "./redux/store";
import Person from "./containers/Person";//引入Person的容器组件

export default class App extends Component {
  render() {
    return (
      <div>
        {/* 给容器组件传递store */}
        <Count store={store}/>
        <hr/>
        <Person/>
      </div>
    )
  }
}

redux文件夹下的store.js代码

/* 
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
// 创建redux的核心store对象
import {legacy_createStore as createStore, applyMiddleware, combineReducers} from 'redux'
// 引入汇总后的reducer
import allReducer from './reducers'
import  {thunk} from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
// 暴露store
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

redux文件夹下reducers文件夹下的index.js代码

这个文件由 redux文件夹下的store.js代码所引入,当组件数量多时,store.js文件也不会太大

/**
 * 该文件用于汇总所有的reducer
 */
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'
// 引入为Person组件服务的reducer
import personReducer from './person'
import {combineReducers} from 'redux'

//合并reducers  传入的就是redux保存的总状态对象
const allReducer = combineReducers({
  count: countReducer,
  persons: personReducer
})

export default allReducer

redux文件夹下actions文件夹下的person.js和count.js代码

import {ADDPERSON} from '../constant'

// 创建增加一个人的动作action对象
export const createAddPerson = personObj => ({type: ADDPERSON, data:personObj})
/*
该文件专门为count组件生成action对象
*/
// 同步action  返回值为object 
import {INCREMENT, DECREMENT} from '../constant'



export const createIncrementAction = data =>({type: INCREMENT, data})
export const createDecrementAction = data =>({type: DECREMENT, data})
// 异步action  就是值为函数 异步action中一般会调用同步action
export const createIncrementActionAsyn = (data, time) =>{
  return (dispatch)=>{
    setTimeout(()=>{
      dispatch(createIncrementAction)
    },time)
  }
}

containers文件夹下Person的index.jsx代码

import React, { Component } from "react";
import {nanoid} from 'nanoid'
import { connect } from "react-redux";
import { createAddPerson } 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.increPerson(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.personArr.map((p)=>{
          return <li>{p.name}--{p.age}</li>
        })}
      </ul>
    </div>
  )
}
}
export default connect(
  state => ({
    personArr: state.persons,
    count: state.count
  }),
  {
    increPerson: createAddPerson
  }
)(Person)

containers文件夹下Count的index.jsx代码

import React, {Component} from "react";
import { connect } from "react-redux";
import {createDecrementAction,createIncrementAction} from '../../redux/actions/count_action'

class count extends React.Component {
  add =()=>{
    this.props.jiafa(1)
  }
  render() {
    return (
      <div>
        <h1> 求和为:{this.props.count},下面总人数和为:{this.props.personCount}</h1>
        <button onClick={this.add}>+1</button>
      </div>
    )
  }
}

export default connect(
  state => ({count: state.count, personCount: state.persons.length}),
  {
    jiafa: createIncrementAction,
    jianfa: createDecrementAction
  }
)(count)

5 redux开发工具的使用

使用redux的扩展的开发工具步骤。在浏览器的扩展中搜索redux下载Redux DevTools。接着在项目终端安装下面这个包

npm add redux-devtools-extension

在store.js中导入

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

重新启动项目即可,下图是我点击了几次按钮和添加新的一位人员信息的记录。可以很快的查看redux的Action、State等等
在这里插入图片描述

  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值