ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Redux学习(三)——redux-saga的使用、编写中间件函数、Reducer文件拆分

2022-02-05 20:32:22  阅读:244  来源: 互联网

标签:函数 saga reducer 中间件 dispatch redux


一、redux-devtools

我们之前讲过,redux可以方便的让我们对状态进行跟踪和调试,那么如何做到呢?

  • redux官网为我们提供了redux-devtools的工具;
  • 利用这个工具,我们可以知道每次状态是如何被修改的,修改前后的状态变化等等;

在这里插入图片描述

安装该工具需要两步:

  1. 第一步:在对应的浏览器中安装相关的插件(比如Chrome浏览器扩展商店中搜索Redux DevTools即可,其他方法可以参考GitHub);
  2. 第二步:在redux中继承devtools的中间件;

在这里插入图片描述
index.js:

import {createStore, applyMiddleware, compose} from 'redux'
import reducer from "./reducer.js";
import thunkMiddleware from 'redux-thunk'

// composeEnhancers函数
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose;

// 通过applyMiddleware来结合多个Middleware,返回一个storeEnhancer
const storeEnhancer = applyMiddleware(thunkMiddleware)
const store = createStore(reducer, composeEnhancers(storeEnhancer))

export default store;

二、generator

saga中间件使用了ES6的generator语法,所以我们有必须简单讲解一下:
注意:我这里并没有列出generator的所有用法,事实上它的用法非常的灵活,大家可以自行去学习一下。

我们按照如下步骤演示一下生成器的使用过程:
在JavaScript中编写一个普通的函数,进行调用会立即拿到这个函数的返回结果。

如果我们将这个函数编写成一个生成器函数。调用iterator的next函数,会销毁一次迭代器,并且返回一个yield的结果。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

研究一下foo生成器函数代码的执行顺序
generator和promise一起使用:
在这里插入图片描述

   // generator 和 Promise一起使用
    function* bar() {
        console.log(111)
        const result = yield new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('Hello Generator')
            }, 3000)
        })
        console.log(result)
    }

    const it = bar()
    // console.log(it.next().value); // Promise对象
    it.next().value.then((res) => {
        it.next(res)
    })

三、redux-saga的使用

redux-saga是另一个比较常用在redux发送异步请求的中间件,它的使用更加的灵活。

Redux-saga的使用步骤如下

  1. 安装redux-saga
    yarn add redux-saga
  2. 集成redux-saga中间件
    导入创建中间件的函数;
    通过创建中间件的函数,创建中间件,并且放到applyMiddleware函数中;
    启动中间件的监听过程,并且传入要监听的saga;

store/index.js:

import {createStore, applyMiddleware, compose} from 'redux'
import reducer from "./reducer.js";
import thunkMiddleware from 'redux-thunk'
import createSagaMiddleware from 'redux-saga'
import saga from './saga'

// composeEnhancers函数
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose;

// 通过applyMiddleware来结合多个Middleware,返回一个storeEnhancer
// 1.引入thunkMiddleware中间件(代码在上面)
// 2.创建sagaMiddleware中间件
const sagaMiddleware = createSagaMiddleware()

const storeEnhancer = applyMiddleware(thunkMiddleware, sagaMiddleware)
const store = createStore(reducer, composeEnhancers(storeEnhancer))

sagaMiddleware.run(saga) // saga为生成器函数

export default store;

在这里插入图片描述
3. saga.js文件的编写

takeEvery:可以传入多个监听的actionType,每一个都可以被执行(对应有一个takeLatest,会取消前面的)

put:在saga中派发action不再是通过dispatch,而是通过put;

all:可以在yield的时候put多个action;

在这里插入图片描述
store/saga.js:

import {takeEvery, put, all, takeLatest} from 'redux-saga/effects'
import axios from "axios";
import {FETCH_HOME_MULTIDATA} from "./constants";
import {changeBannersAction,changeRecommendsAction} from "./actionCreator";
function* fetchHomeMultidata(action) {
    const res = yield axios.get('http://123.207.32.32:8000/home/multidata')
    const banners = res.data.data.banner.list
    const recommends = res.data.data.recommend.list
    // yield put(changeBannersAction(banners))
    // yield put(changeRecommendsAction(recommends))
    yield all([
        yield put(changeBannersAction(banners)),
        yield put(changeRecommendsAction(recommends))
    ])
}
function* mySaga() {
    // takeEvery: 每个action都会被执行
    // yield takeEvery(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
    // takeLatest:一次只能监听一个对应的action
    // yield takeLatest(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
    yield all([
        yield takeEvery(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
        // yield takeLatest(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
    ])
}
export default mySaga

在这里插入图片描述
home.js:

import React, {PureComponent} from 'react';
import {connect} from "react-redux";
import {
    addAction,
    fetchHomeMultidataAction
}from "../store/actionCreator";

class Home extends PureComponent {
    componentDidMount() {
        this.props.getHomeMultidata()
    }

    render() {
        return (
            <div>
                <h1>Home</h1>
                <h3>当前计数:{this.props.counter}</h3>
                <button onClick={e => this.props.increment()}>+1</button>
                <button onClick={e => this.props.addNumber(5)}>+5</button>
            </div>
        );
    }
}
const mapStateToProps = state => ({
    counter: state.counter
})
const mapDispatchToProps = dispatch => ({
    increment() {
        dispatch(addAction(1));
    },
    addNumber(num) {
        dispatch(addAction(num));
    },
    getHomeMultidata() {
        dispatch(fetchHomeMultidataAction)
    }
})
export default connect(mapStateToProps, mapDispatchToProps)(Home);

在这里插入图片描述
在这里插入图片描述

四、打印日志需求

前面我们已经提过,中间件的目的是在redux中插入一些自己的操作:

  • 比如我们现在有一个需求,在dispatch之前,打印一下本次的action对象,dispatch完成之后可以打印一下最新的store
    state;
  • 也就是我们需要将对应的代码插入到redux的某部分,让之后所有的dispatch都可以包含这样的操作;

如果没有中间件,我们是否可以实现类似的代码呢? 可以在派发的前后进行相关的打印。
但是这种方式缺陷非常明显:

  • 首先,每一次的dispatch操作,我们都需要在前面加上这样的逻辑代码;
  • 其次,存在大量重复的代码,会非常麻烦和臃肿;

是否有一种更优雅的方式来处理这样的相同逻辑呢?

  • 我们可以将代码封装到一个独立的函数中

但是这样的代码有一个非常大的缺陷:

  • 调用者(使用者)在使用我的dispatch时,必须使用我另外封装的一个函数dispatchAndLog;
  • 显然,对于调用者来说,很难记住这样的API,更加习惯的方式是直接调用dispatch;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

修改dispatch

事实上,我们可以利用一个hack一点的技术:Monkey Patching,利用它可以修改原有的程序逻辑;
我们对代码进行如下的修改:

  • 这样就意味着我们已经直接修改了dispatch的调用过程;
  • 在调用dispatch的过程中,真正调用的函数其实是dispatchAndLog;

当然,我们可以将它封装到一个模块中,只要调用这个模块中的函数,就可以对store进行这样的处理:
在这里插入图片描述

thunk需求

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

我们来看下面的代码:
我们又对dispatch进行转换,这个dispatch会判断传入的
在这里插入图片描述

合并中间件

单个调用某个函数来合并中间件并不是特别的方便,我们可以封装一个函数来实现所有的中间件合并:
在这里插入图片描述
我们来理解一下上面操作之后,代码的流程:
在这里插入图片描述
当然,真实的中间件实现起来会更加的灵活,这里我们仅仅做一个抛砖引玉,有兴趣可以参考redux合并中间件的源码流程。

五、Reducer代码拆分

我们先来理解一下,为什么这个函数叫reducer?
我们来看一下目前我们的reducer:

  • 当前这个reducer既有处理counter的代码,又有处理home页面的数据;
  • 后续counter相关的状态或home相关的状态会进一步变得更加复杂;
  • 我们也会继续添加其他的相关状态,比如购物车、分类、歌单等等;
  • 如果将所有的状态都放到一个reducer中进行管理,随着项目的日趋庞大,必然会造成代码臃肿、难以维护。

因此,我们可以对reducer进行拆分:

  • 我们先抽取一个对counter处理的reducer;
  • 再抽取一个对home处理的reducer;
  • 将它们合并起来;

六、Reducer文件拆分

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

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

在这里插入图片描述

七、combineReducers函数

目前我们合并的方式是通过每次调用reducer函数自己来返回一个新的对象。
事实上,redux给我们提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并:
在这里插入图片描述
那么combineReducers是如何实现的呢?
事实上,它也是讲我们传入的reducers合并到一个对象中,最终返回一个combination的函数(相当于我们之前的reducer函数了);

在执行combination函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state还是新的state; 新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新;

标签:函数,saga,reducer,中间件,dispatch,redux
来源: https://blog.csdn.net/weixin_44827418/article/details/122785009

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有