react知识点总结 -- redux(四)

本文深入讲解Redux的基本概念与核心原理,包括store、action及reducer的使用,并探讨如何通过中间件处理异步操作,同时介绍了Redux与React的整合方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Redux 使用

前言:

  • javascript 开发的应用程序,已经变得越来越复杂了
    • javascript需要管理的状态越来越多,越来越复杂
    • 这些状态包括服务器返回的数据,缓存数据,用户操作产生的数据等等,也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页
  • 管理不断变化的state是非常困难的
    • 状态之间会相互存在依赖,一个状态的变化会引起另一个状态的变化, view页面也会引起状态的变化
    • 当应用程序复杂的时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变的非常难以控制和追踪
  • react是在视图层帮助我们呢解决了DOM的渲染过程,但是state依然是留给我们自己管理的
    • 无论是组件定义自己的state,还是组件之间的通信通过props进行传递,也包括通过context进行数据之间的共享
    • react主要负责帮助我们管理视图,state如何维护最终还是我们自己来决定的
  • Redux就是一个帮助我们管理State的容器“Redux是 Javascript的状态容器,提供了可预测的状态管理
  • Readux除了和React一起使用之外,它也可以和其他界面库一起使用(Vue), 它非常小,包括依赖也只有2kb

Redux核心理念 -store

  • redux的核心理念非常简单
  • 比如我们呢有一个朋友列表需要管理
    • 如果我们呢没有定义同意的规范来操作这段数据,那么整个数据变化就是无法跟踪的
    • 比如页面的某处通过products.push的方式增加了一条数据
    • 比如另一个页面通过 products[0].age = 25 修改了一条数据
  • 整个应用程序错综复杂,当出现bug时,很难跟踪到底哪里发生的变化

Redux核心理念 -action

  • redux要求我们通过action来更新数据
    • 所有数据的变化,必须通过派发(dispatch)action来更新
    • action是一个普通的javascript对象,用来描述这次更新的type和content
  • 比如下面就是几个更新 friends 和 action
    • 强制使用action的好处是可以清晰的知道数据到底发生了什么样的变化,所有的数据变化都是可以追踪的,可预测的
    • 当然,目前我们的action是固定的对象,真实的应用中,我们会通过函数来定义,返回一个action
const action1 = {type:"ADD_FRIEND",info:{name:"lucy",age:20}}

Redux核心理念 -reducer

  • 但是如何将state 和action 联系在一起呢?答案是 reducer
    • reducer是一个纯函数
    • reducer做的事情就是传入的state和action结合起来生成一个新的state

Redux 的 三大原则

  • 单一数据源
    • 整个程序的state被存储在一个object tree中,并且这个object tree只存储在一个store中
    • Redux并没有强制让我们不能创建多个Store,但是那样做不利于数据的维护
    • 单一的数据源可以让整个应用程序的state变得方便维护,追踪,修改
  • state是只读的
    • 唯一修改State的方法一定 是出发action ,不要试图在其他地方通过action来描述自己想要如何修改state
    • 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心 race condition(竞态)的问题
  • 使用纯函数来执行
    • 通过reducer 将旧state和action联系在一起,并且返回一个新的State
    • 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducer,分别操作不同的state tree的一部分
    • 但是所有的reducer都应该是纯函数,不能产生任何的副作用

Redux基础使用

const redux = require('redux')

const initialState = {
  counter: 0
}

// 1.store(创建的时候传入一个 reducer)
const store = redux.createStore(reducer)


// 2.action 
const action1 = { type: "INCREMENT" }
const action2 = { type: "DECREMENT" }

const action3 = { type: "ADD_NUMBER", number: 5 }
const action4 = { type: "SUB_NUMBER", number: 12 }

// 3.订阅store的修改
store.subscribe(() => {
  console.log("state 发生了改变", store.getState().counter);
})

// 4.派发action
store.dispatch(action1)
store.dispatch(action2)
store.dispatch(action2)
store.dispatch(action3)
store.dispatch(action4)

// 5.定义reducer
function reducer (state = initialState, action) {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, counter: state.counter + 1 }
    case "DECREMENT":
      return { ...state, counter: state.counter - 1 }
    case "ADD_NUMBER":
      return { ...state, counter: state.counter + action.number }
    case "SUB_NUMBER":
      return { ...state, counter: state.counter - action.number }
    default:
      return state
  }
}

redux融入react代码

// Home页面
import React, { PureComponent } from 'react';

import store from '../store';
import {
  addAction
} from '../store/actionCreators';

export default class Home extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      counter: store.getState().counter
    }
  }

  componentDidMount() {
    store.subscribe(() => {
      this.setState({
        counter: store.getState().counter
      })
    })
  }

  render() {
    return (
      <div>
        <h1>Home</h1>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <button onClick={e => this.addCounter()}>+5</button>
      </div>
    )
  }

  increment() {
    store.dispatch(addAction(1));
  }

  addCounter() {
    store.dispatch(addAction(5));
  }
}


// Profile页面

import React, { PureComponent } from 'react';

import store from '../store';
import {
  subAction
} from '../store/actionCreators';

export default class Profile extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      counter: store.getState().counter
    }
  }

  componentDidMount() {
    store.subscribe(() => {
      this.setState({
        counter: store.getState().counter
      })
    })
  }

  render() {
    return (
      <div>
        <hr/>
        <h1>Profile</h1>
        <div>
          <h2>当前计数: {this.state.counter}</h2>
          <button onClick={e => this.decrement()}>-1</button>
          <button onClick={e => this.subCounter()}>-5</button>
        </div>
      </div>
    )
  }

  decrement() {
    store.dispatch(subAction(1));
  }

  subCounter() {
    store.dispatch(subAction(5));
  }
}

上面的代码其实非常简单,核心代码主要是两个:

  • componentDidMount 中定义数据的变化,当数据发生变化时重新设置 counter;
  • 在发生点击事件时,调用store的dispatch来派发对应的action

自定义connect函数

上面的代码是否可以实现react组件redux结合起来呢?

  • 当然是可以的,但是我们会发现每个使用的地方其实会有一些重复的代码:
  • 比如监听store数据改变的代码,都需要在 componentDidMount中完成;
  • 比如派发事件,我们都需要去先拿到 store, 在调用其 dispatch 等;

能否将这些公共的内容提取出来呢?

我们来定义一个connect函数:

  • 这个connect函数本身接受两个参数:

    • 参数一:里面存放 component 希望使用到的 State 属性;
    • 参数二:里面存放 component 希望使用到的 dispatch动作;
  • 这个connect函数有一个返回值,是一个高阶组件:

    • constructor中的state中保存一下我们需要获取的状态;
    • componentDidMount中订阅store中数据的变化,并且执行 setState操作;
    • componentWillUnmount中需要取消订阅;
    • render函数中返回传入的WrappedComponent,并且将所有的状态映射到其props中;
    • 这个高阶组件接受一个组件作为参数,返回一个class组件;
    • 在这个class组件中,我们进行如下操作
  • import React, { PureComponent } from "react";
    
    import store from '../store';
    
    export default function connect(mapStateToProps, mapDispatchToProps) {
      return function handleMapCpn(WrappedComponent) {
        return class extends PureComponent {
          constructor(props) {
            super(props);
    
            this.state = {
              storeState: mapStateToProps(store.getState())
            }
          }
    
          componentDidMount() {
            this.unsubscribe = store.subscribe(() => {
              this.setState({
                storeState: mapStateToProps(store.getState())
              })
            })
          }
    
          componentWillUnmount() {
            this.unsubscribe();
          }
    
          render() {
            return <WrappedComponent {...this.props} 
                                     {...mapStateToProps(store.getState())}
                             		 {...mapDispatchToProps(store.dispatch)}/>
          }
        }
      }
    }
    
  • 在home和props文件中,我们按照自己需要的state、dispatch来进行映射:

  • 比如home.js中进行如下修改:

    • mapStateToProps:用于将state映射到一个对象中,对象中包含我们需要的属性;

    • mapDispatchToProps:用于将dispatch映射到对象中,对象中包含在组件中可能操作的函数;

      • 当调用该函数时,本质上其实是调用dispatch(对应的Action);
const mapStateToProps = state => {
  return {
    counter: state.counter
  }
}

const mapDispatchToProps = dispatch => {
  return {
    addNumber: function(number) {
      dispatch(addAction(number));
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);

context 处理store

  • 但是上面的connect 函数有一个很大的缺陷:依赖导入的store
    • 如果我们将其封装成一个独立的库,需要依赖创建的store,我们应该如何去获取
    • 肯定不能让用户更改我们的源码
  • 正确的做法是我们提供一个Provider Provider 来自我们创建的Context,让用户将store传入value中

react-redux使用

  • redux和 react没有直接的关系,你可以将redux用在其他的框架里面
  • 话虽如此,但是redux 确实是和 react , Deku 结合非常好的库,因为他们是通过state函数来描述界面的状态,redux可以发射状态的更新,让他们做出相应的变化
    • 虽然我们之前已经实现了 connect Provider这些帮助我们完成链接redux react 的辅助工具,但是实际上redux官方帮助我们提供了react-redux的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效
  • 安装react-redux
    • yarn install react-redux

组件中的异步操作(一)

  • 在之前的简单案例中,redux中保存的counter是一个本地定义的数据
    • 我们可以直接通过同步操作来 dispatch actoin ,state就会立即更新
    • 但是真实开发中,redux中保存的很多数据可能来自服务器,我们需要进行异步的请求,再将数据保存到redux中
  • 在之前学习的网络请求的时候我们讲过,网络请求可以在class类组件的componentDidMount中发送
  • image-20200926221508757

组件中的异步操作(二)

  • 上面的代码有一个缺陷

    • 我们必须将网络请求的异步代码放到组件的生命周期中来完成
    • 事实上,网络请求到的数据也属于我们状态管理的一部分,更好的一种方式应该是将其也交给redux来管理;
    image-20200926221649771
  • 但是在 redux中如何进行异步操作呢

    • 答案就是使用 中间件
    • 学习过 express 或者 koa框架的话 对中间件一定印象
    • 在这类框架中 middleware 可以帮助我们在请求和响应之间嵌入一些操作的代码,比如cookie 解析,日志记录,文件压缩等操作

理解中间件

  • redux也引入了中间件(Middleware)的概念
    • 这个中间件的目的是在dispatch的action和最终达到的reducer之间,扩展一些自己的代码;
    • 比如日志记录、调用异步接口、添加代码调试功能等等;
  • 我们现在要做的事情就是发送异步的网络请求,所以我们可以添加对应的中间件:
    • 这里官网推荐的、包括演示的网络请求的中间件是使用 redux-thunk
  • redux-thunk是如何做到让我们可以发送异步的请求呢?
    • 我们知道,默认情况下的dispatch(action),action需要是一个JavaScript的对象;
    • redux-thunk可以让dispatch(action函数),action可以是一个函数
    • 该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数
      • dispatch函数用于我们之后再次派发action;
      • getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态;

如何使用redux-thunk

  • 安装 yarn add redux-thunk
  • 在创建store时传入应用了middleware的enhance函数
    • 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
    • 将enhancer作为第二个参数传入到createStore中;
  • 定义返回一个函数的action:
    • 注意:这里不是返回一个对象了,而是一个函数;
    • 该函数在dispatch之后会被执行;

redux-devtools

  • 我们之前讲过,redux可以方便的让我们对状态进行跟踪和调试,那么如何做到呢
    • redux官网为我们提供了redux-devtools的工具
    • 利用这个工具,我们呢可以知道每次状态是如何被修改的
  • 安装该工具需要两个步骤
    • 第一步:在对应的浏览器中安装相关的插件
    • 第二步:在redux中集成devtools的中间件

generator的使用

  • saga中间件使用了es6的generator语法
  • 我们安装如下步骤演示一下生成器的使用过程
    • javascript 中编写一个普通的函数,进行调用会立即拿到这个函数的结果
    • 如果我们将这个函数编写成一个生成器函数
    • 调用iterator的next函数,会销毁一次迭代器,并且返回一个yield的结果
    • 研究一下foo生成器函数代码的执行顺序
    • gennerator和promise一起使用

redux-saga的使用

  • redux-saga是另一个常用的redux发送异步请求的中间件,它的使用更加灵活
  • redux-saga的使用步骤如下
  • 1.安装 redux-saga
    • yarn add redux-saga
  • 2.集成redux-saga中间件
    • 导入创建中间件的函数
    • 通过创建中间件的函数,创建中间件,并且放到applyMiddleware函数中
    • 启动中间件的监听过程,并且传入要监听的saga
  • 3.saga.js文件的编写
    • takeEvery:可以传入多个监听的actio Type,每一个都可以被执行(对应有一个takeLatest,会取消前面的)
    • put:在saga中派发action不再是通过dispatch,而是通过put
    • all:keyi zai yield的使用put多个action

打印日志需求

  • 前面我们已经提过,中间件的目的是在redux中插入一些自己的操作:
    • 比如我们现在有一个需求,在dispatch之前,打印一下本次的action对象,dispatch完成之后可以打印一下最新的store ,state
    • 也就是我们需要将对应的代码插入到redux的某部分,让之后所有的dispatch都可以包含这样的操作;
  • 如果没有中间 件,我们是否可以实现类似的代码呢? 可以在派发的前后进行相关的打印
  • 但是这种方式缺陷非常明显
    • 首先,每一次的dispatch操作,我们都需要在前面加上这样的逻辑代码
    • 其次,存在大量重复的代码,会非常麻烦和臃肿
  • 是否有一种更优雅的方式来处理这样的相同逻辑呢?
    • 我们可以将代码封装到一个独立的函数中
  • 但是这样的代码有一个非常大的缺陷:
    • 调用者(使用者)在使用我的dispatch时,必须使用我另外封装的一个函数dispatchAndLog
    • 显然,对于调用者来说,很难记住这样的API,更加习惯的方式是直接调用dispatch;

修改dispatch

  • 事实上,我们可以利用一个hack一点的技术:Monkey Patching,利用它可以修改原有的程序逻辑

  • 我们对代码进行如下的修改

    • 这样就意味着我们已经直接修改了dispatch的调用过程
    • 在调用dispatch的过程中,真正调用的函数其实是dispatchAndLog
  • 当然,我们可以将它封装到一个模块中,只要调用这个模块中的函数,就可以对store进行这样的处理

  • function patchLogging(store) {
        let next = store.dispatch;
        function dispatchAndLog(action) { 
            console.log("dispatching:", action);
            next(addAction(5));
            console. log("新的state: ", store.getState());
        }
            store.dispatch = dispatchAndLog;
    }
    

thunk需求

  • redux-thunk的作用:

    • 我们知道redux中利用一个中间件redux-thunk可以让我们的dispatch不再只是处理对象,并且可以处理函数
    • 那么redux-thunk中的基本实现过程是怎么样的呢?事实上非常的简单
  • 我们来看下面的代码

    • 我们又对dispatch进行转换,这个dispatch会判断传入的

      function patchThunk(store) {
          let next = store.dispatch;
          
          function dispatchAndThunk(action){
              if (typeof action =zs "function") { 
                  action(store.dispatch, store.getState);
              } else { 
                  next(action);
              }
           }
          store.dispatch = dispatchAndThunk;
      }
      

合并中间件

  • 单个调用某个函数来合并中间件并不是特别的方便,我们可以封装一个函数来实现所有的中间件合并:
function applyMiddleware(store,middlewares) {
    middlewares = middlewares.slice();
    
    middlewares.forEach(middleware => {
        store.dispatch = middleware(store)
    })
}

applyMiddleware(store,[patchLogging,patchThunk])

我们来理解一下上面操作之后,代码的流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KaypnGUz-1606021308680)(C:\Users\kim\AppData\Roaming\Typora\typora-user-images\image-20201005204652597.png)]

当然,真实的中间件实现起来会更加的灵活,这里我们仅仅做一个抛砖引玉,有兴趣可以参考redux合并中间件的源码流程

Reducer代码拆分

  • 我们先来理解一下,为什么这个函数叫reducer?
  • 我们来看一下目前我们的reducer
    • 当前这个reducer既有处理counter的代码,又有处理home页面的数据
    • 后续counter相关的状态或home相关的状态会进一步变得更加复杂
    • 我们也会继续添加其他的相关状态,比如购物车、分类、歌单等等;
    • 如果将所有的状态都放到一个reducer中进行管理,随着项目的日趋庞大,必然会造成代码臃肿、难以维护。
  • 因此,我们可以对reducer进行拆分:
    • 我们先抽取一个对counter处理的reducer;
    • 再抽取一个对home处理的reducer;
    • 将它们合并起来;

Reducer文件拆分

  • 目前我们已经将不同的状态处理拆分到不同的reducer中,我们来思考

    • 虽然已经放到不同的函数了,但是这些函数的处理依然是在同一个文件中,代码非常的混乱;
    • 另外关于reducer中用到的constant、action等我们也依然是在同一个文件中;
    image-20201005204850454

combineReducers函数

  • 目前我们合并的方式是通过每次调用reducer函数自己来返回一个新的对象。
  • 事实上,redux给我们提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并:
const reducer = combineReducers({
    counterInfo:counterReducer,
    homeInfo:homeReducer
})
export default reducer
  • 那么combineReducers是如何实现的呢
    • 事实上,它也是讲我们传入的reducers合并到一个对象中,最终返回一个combination的函数(相当于我们之前的reducer函 数了);
    • 在执行combination函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state还是新的state;
    • 新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值