博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从零开始写一个 redux(第三讲)
阅读量:6097 次
发布时间:2019-06-20

本文共 4001 字,大约阅读时间需要 13 分钟。

上一讲实现了react-redux,从而可以更加优雅地在react中使用redux。

但是,一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。

怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)。

中间件原理

为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加?

  • Reducer:纯函数,只承担计算State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
  • View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
  • Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

只有发送 Action 的这个步骤,即store.dispatch()方法,可以添加功能。举例来说,要添加日志功能,把 Action 和 State 打印出来,可以对store.dispatch进行如下改造。

let next = store.dispatch;store.dispatch = function dispatchAndLog(action) {  console.log('dispatching', action);  next(action);  console.log('next state', store.getState());}复制代码

store.dispatch进行了重定义,在发送 Action 前后添加了打印功能。这就是中间件的雏形。 中间件就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

中间件编写

在编写中间件之前,我们先看下在真正的redux里面是如何使用中间件的,当使用单个中间件时代码如下,其中thunk就是一个中间件:

const store = createStore(counter, applyMiddleware(thunk))复制代码

有上面的代码可以看出,我们需要做三件事情

  • 第一步 对createStore函数进行扩展,使其能够接收第二个参数——中间件

  • 第二步 定义applyMiddleware函数,使其能够将一个中间件加入到redux中

  • 第三步 实现一个中间件——redux-thunk

对createStore函数进行扩展

代码如下,检测是否有增强器,若存在则先用增强器对createStore进行扩展增强

export function createStore(reducer, enhancer) {    // 如果存在增强器,则先用增强器对createStore进行扩展增强    if (enhancer) {        return enhancer(createStore)(reducer)    }    ...}复制代码

定义applyMiddleware函数

根据上面的代码,我们可以知道,applyMiddleware(中间件)返回的是一个高阶函数,接收参数createStore后,返回一个函数,然后再接收参数reducer。 因此对应代码如下:

export function applyMiddleware(...middlewares) {    return createStore => (...args) => {        // 第一步 获得原生store以及原生dispatch        const store = createStore(...args)        let dispatch = store.dispatch                const midApi = {            getState: store.getState,            dispatch: (...args) => dispatch(...args)        }        // 第二步 将原生dipatch传入中间件进行扩展增强,生成新的dispatch        dispatch = middleware(midApi)(dispatch)        return {            ...store, // 原生store            dispatch, // 增强扩展后的dispatch        }    }}复制代码

在上述代码中,我们先是获得原生store以及原生dispatch,组成midApi,即中间件API,然后将其传入中间件,执行中间件内定义的操作,返回一个函数,再传入原生dispatch,再返回一个增强后的dispatch,最后传入action。增强后的dispatch如下:

dispatch(action) = middleware(midApi)(store.dispatch)(action)复制代码

实现中间件redux-thunk

异步操作至少要送出两个 Action:用户触发第一个 Action,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action 呢?

奥妙就在 Action Creator 之中。

// action creatorexport function addGun() {    return {
type: ADD_GUN}}export function addGunAsync() { return dispatch => { setTimeout(() => { dispatch(addGun()) }, 2000) }}复制代码

上文中有两个需求,第一个需求是store.dispatch一个action对象(即{type: ADD_GUN}),然后立即加机枪,即:

addGun = () => store.dispatch(addGun())addGun()复制代码

第二个需求是store.dispatch一个函数,这个函数内部执行异步操作,在2000ms之后再执行store.dispatch(addGun()),加机枪,但是store.dispatch参数只能是action这样的对象,而不能是函数。store.dispatch的有关源码如下:

function dispatch(action) {    // reducer根据老的state和action计算新的state    currentState = reducer(currentState, action)    // 当全局状态变化时,执行传入的监听函数    currentListeners.forEach(v => v())    return action}复制代码

为了能够让让store.dispatch能够接收函数,我们可以使用redux-thunk,改造store.dispatch,使得后者可以接受函数作为参数。

因此,异步操作的一种解决方案就是,写出一个返回函数的 Action Creator,然后使用redux-thunk中间件改造store.dispatch。

改造后的dispatch处理addGunAsync函数生成的action(一个函数):

// action creatorexport function buyHouse() {    return {
type: BUY_HOUSE}}function buyHouseAsync() { return dispatch => { setTimeout(() => { dispatch(buyHouse()) }, 2000) }}dispatch(buyHouseAsync()) = middleware(midApi)(store.dispatch)(buyHouseAsync())复制代码

因此redux-thunk对应代码如下:

const thunk = ({dispatch, getState}) => next => action => {    // next为原生的dispatch    // 如果action是函数,执行一下,参数是dispatch和getState    if (typeof action === 'function') {        return action(dispatch, getState)    }    // 默认直接用原生dispatch发出action,什么都不做    return next(action)}复制代码

即判断action如果是一个函数,则执行这个函数。否则直接用原生dispatch发出action,什么都不做

这样我们就可以通过redux-thunk中间件,实现了增强版的dispatch可以接收函数作为参数,而我们在函数里面进行异步操作,异步操作完成后用原生dispatch发出action,从而实现了redux的异步操作全局状态的功能。

另外最近正在写一个编译 Vue 代码到 React 代码的转换器,欢迎大家查阅。

转载于:https://juejin.im/post/5d05b22fe51d45108c59a537

你可能感兴趣的文章
企业级负载平衡简介(转)
查看>>
ICCV2017 论文浏览记录
查看>>
科技巨头的交通争夺战
查看>>
当中兴安卓手机遇上农行音频通用K宝 -- 卡在“正在通讯”,一直加载中
查看>>
Shell基础之-正则表达式
查看>>
JavaScript异步之Generator、async、await
查看>>
讲讲吸顶效果与react-sticky
查看>>
c++面向对象的一些问题1 0
查看>>
直播视频流技术名词
查看>>
iOS13-适配夜间模式/深色外观(Dark Mode)
查看>>
网易跟贴这么火,背后的某个力量不可忽视
查看>>
企业级java springboot b2bc商城系统开源源码二次开发-hystrix参数详解(八)
查看>>
java B2B2C 多租户电子商城系统- 整合企业架构的技术点
查看>>
IOC —— AOP
查看>>
比特币现金将出新招,推动比特币现金使用
查看>>
数据库的这些性能优化,你做了吗?
查看>>
某大型网站迁移总结(完结)
查看>>
mysql的innodb中事务日志(redo log)ib_logfile
查看>>
部署SSL证书后,网页内容造成页面错误提示的处理办法
查看>>
MS SQLSERVER通用存储过程分页
查看>>