本文将涉及以下三块内容:

  • 多 Reducer
  • 中间件
  • 封装组件方便获取 Store

前言

在上一篇文章《React Redux与胖虎》 中我们详尽地介绍了 React Redux,也写了一个简单的计数器。

这篇文章叫《React Redux与胖虎他妈》,因为在哆啦A梦里面,胖虎虽然很屌老是欺负大雄和小夫,但是在他妈面前没少挨揍,胖虎他妈还是他妈,所以这篇文章主要是介绍 React Redux 的一些进阶用法。

多 Reducer

单 Reducer 不好吗

开发过程中,我们由于业务或者功能的划分,一般不同模块的数据也是不同的,如果只用一个 Reducer,那么这个 Reducer 要处理所有模块过来的事件,然后返回一个 state,所有的数据都糅合在这个 state 里面,所有接收到这个 state 的模块还得解析出其中跟自己有关的部分。

所以单个 Reducer 并不能满足当下需求,多 Reducer 的出现有利于我们模块化开发,降低耦合度。

redux 提供了 combineReducers 函数来组合 Reducer,注意不是 react-redux 库。

// reducers/index.js
import { combineReducers } from 'redux';
import firstReducer from './first-reducer';
import secondReducer from './second-reducer';const reducers = combineReducers({ firstReducer, secondReducer
});
复制代码

注意上面 combineReducers 的参数使用 ES6 的语法,相当于:

const reducers = combineReducers({ firstReducer: firstReducer, secondReducer: secondReducer
});
复制代码

注意一点:每发出一个事件,所有 Reducer 都会收到

多 Reducer 返回的 state

我们知道,在 Reducer 只有一个的情况下,容器组件的 mapStateToProps 函数接收到的 state 即为唯一 Reducer 返回的对象。

而在 Reducer 有多个的情况下,就会有多个返回值。这时候容器组件的 mapStateToProps 函数接收到的 state 其实是包含所有 Reducer 返回值的对象。可以用 key 值来区它们,这个 key 值就是我们在 combineReducers 时传入的。

const mapStateToProps = (state) => {const firstReducer = state.firstReducer;const secondReducer = state.secondReducer;return {value1: firstReducer.value,value2: secondReducer.value};
}export default connect(mapStateToProps)(Counter);
复制代码

当然,一般都是只需要用其中一个 state,那么我们可以写成:

const mapStateToProps = ({ firstReducer }) => {return {value: firstReducer.value};
}
//或者更加语义化地表示为state
const mapStateToProps = ({ firstReducer: state }) => {return {value: state.value};
}
复制代码

这样一来可以有效地隔离各个模块之间的影响,也方便多人协作开发。

(由于胖虎他妈实在没什么表情,所以还是用胖虎开涮吧)

中间件

网上对于中间件的解释基本上都是“位于应用程序和操作系统之间的程序”之类,这只是一个基本的概述。在 React Redux 里面,中间件的位置很明确,就是在 Action 到达 Reducer 之前做一些操作

React Redux 的中间件实际上是一个高阶函数:

function middleware(store) {return function wrapper(next) {return function inner(action) {...}}
}
复制代码

其中最内层的函数接收的正是 Action。

中间件可以多个叠加使用,在中间件内部使用 next 函数来将 Action 发送到下一个中间件让其处理。如果没有下一个中间件,那么会将 Action 发送到 Reducer 去。

我们看如何将中间件应用到 React Redux 应用中。

redux 提供了 applyMiddleware, compose 函数来帮助添加中间件:

import { applyMiddleware, compose, createStore } from 'redux';
import api from '../middlewares/api';
import thunk from 'redux-thunk';
import reducers from "../reducers";const withMiddleware = compose(applyMiddleware(api),
)(createStore);const store = withMiddleware(reducers);export default store;
复制代码

可以看到 applyMiddleware 函数可以将中间件引入,使用 compose 函数将多个函数整合成一个新的函数。

对于 applyMiddleware, compose, createStore 这三个函数的实现,可以自己去参考源码。

这里说一下,这三个函数虽然代码量不大,但是其实用了挺多函数式编程的思想和做法,一开始看会很抽象,特别是几个箭头符号连着用更是懵逼。但是看源码总是好的,一旦你渐入佳境,定会发现新的天地。不过,这里就只讲用法了,说实话我也还没认真去看(逃

简单中间件

我们可以实现一个炒鸡简单的中间件来看看效果,比如说,在事件到达 Reducer 之前,把事件打印出来。

export default store => next => action => {console.log(action);next(action);
}
复制代码

emmmm,是挺简单的....

复杂中间件

在谈复杂中间件时,我们需要先说说同步事件、异步事件。

在 React Redux 应用中,我们平时发出去的事件都是直接到达中间件(如果有中间件的话)然后到达 Reducer,干净利落毫不拖拉,这种事件我们称为同步事件。

而异步事件,按照我个人理解,指的是,你发出去的事件,经过中间件时有了可观的时间停留,并不会立即传到 Reducer 里面处理。也就是说,这个异步事件导致事件流经过中间件时发生了耗时操作,比如访问网络数据、读写文件等,在操作完成之后,事件才继续往下流到 Reducer 那儿。

嗯...同步事件我们都知道怎么写:

{type: 'SYNC_ACTION',...
}
复制代码

异步事件的话,一般是定义成一个函数:

function asyncAction({dispatch, getState}) {const action = {type: 'ASYNC_ACTION',api: {url: 'www.xxx.com/api',method: 'GET'          }};dispatch(action);
}
复制代码

但是,现在我们的异步事件是一个函数,你如果不作任何处理的话直接执行 dispatch(asyncAction) ,那么会报错,告诉你只能发送 plain object,即类似于同步事件那样的对象。

redux-thunk

我们要在中间件搞些事情,让函数类型的 Action 可以用,简单地可以使用 redux-thunk 。

P.S. 虽然我不是专门搞前端的,虽然我是男的,但是作者 gaearon 真的好帅......

redux-thunk 的代码量十分地少... 贴出来看看:

function createThunkMiddleware(extraArgument) {return ({ dispatch, getState }) => next => action => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument);}return next(action);};
}const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;export default thunk;复制代码

emmmm,其实它做的事情就是判断传进来的 Action 是不是 function 类型的,如果是,就执行这个 action 并且把 store.dispatchstore.getState 传给它;如果不是,那么调用 next 将 Action 继续往下发送就行了。

带有网络请求的中间件

行吧... 那我们仿照 redux-thunk 写一个中间件,整合进网络请求的功能。

  1. 首先当然是允许 function 类型的 Action

    export default store => next => action => {if (typeof action === 'function') {return action(store);}
    }
    复制代码
  2. 然后当 Action 是 plain object 而且没有 api 字段时,当成同步事件处理

    export default store => next => action => {if (typeof action === 'function') {return action(store);}const { type, api, isFetching, ...rest } = action;if (!api) {return next(action);}
    }
    复制代码
  3. 如果有 api 字段,那么先发送一个事件,告诉下游的 Reducer 我先要开始来拿网络数据了嘿嘿,即 isFetching 字段值为 true

    export default store => next => action => {if (typeof action === 'function') {return action(store);}const { type, api, isFetching, ...rest } = action;if (!api) {return next(action);}next({ type, api, isFetching: true, ...rest });
    }
    复制代码
  4. 然后就开始进行异步操作,即网络请求。并且请求成功、请求失败和请求异常三种情况都会发送不同的事件给下游的 Reducer

    import fetch from 'isomorphic-fetch';
    import React from "react";export default store => next => action => {if (typeof action === 'function') {return action(store);}const { type, api, isFetching, ...rest } = action;if (!api) {return next(action);}next({ type, api, isFetching: true, ...rest });fetch(api.url, {method: api.method,}).then(response => {if (response.status !== 200) {next({type,api,status: 'error',code: response.status,response: {},isFetching: false,...rest});} else {response.json().then(json => {next({type,api,status: 'success',code: 200,response: json.response,isFetching: false,...rest});})}}).catch(err => {next({ type, api, status, code: 0, response: {}, isFetching: false, msg: err, ...rest });});
    }
    复制代码

到此为止,一个比较复杂的带有网络请求的中间件就完成了。

封装组件方便获取 Store

遗留的问题

还记得上一篇文章我们说到“一个深度为 100 的组件要去改变一个浅层次组件的文案”的例子吗?我们当时说,只要从深层次的组件里面发送一个事件出来就可以了,也就是使用 dispatch 函数来发送。

emmmm,我们到现在好像还没遇到过直接在组件里面 dispatch 事件的情况,我们之前都是在容器组件的 mapDispatchToProps 里面 dispatch 的。

所以在 UI 组件里面不能拿到 dispatch 函数?

这里先说明一点,我们亲爱的 dispatch 函数,是存在于 Store 中的,可以用 Store.dispatch 调用。有些机灵的同学已经想到,那我们全局的 Store 引入 UI 组件不就好咯。

哦我亲爱的上帝,瞧瞧这个优秀的答案,来,我亲爱的汤姆斯·陈独秀先生,这是你的奖杯...

是的没错,这是一种方式,但是我觉得这很不 React Redux。

利用 this.context

在上一篇文章中,我们说到引入了 Provider 组件来讲 Store 作用于整个组件树,那么是否在每一个组件中都能获取到 Store 呢?

当然可以,Store 是穿透到整个组件树里面的,这个特性依赖于 context 这个玩意,context 具体的介绍可以参看 官方文档 。

只需要在顶层的组件声明一些方法就可以实现穿透,这部分工作 Provider 组件内部已经帮我们做好了。

不过在想使用 Store 的组件内部,也要声明一些东西才能拿到:

import PropTypes from 'prop-types';export default class DeepLayerComponent extends React.Component {static contextTypes = {store: PropTypes.object}componentDidMount() {this.context.store.dispatch({type: 'DO_SOMETHING'});}}
复制代码

这里我们声明 contextTypesstore 字段,然后就可以通过 this.context.store 来使用了。

注意,由于 react 库自带的 PropTypes 在 15.5 版本之后抽离到 prop-types 库中,需要自行引入才能使用

封装

但是如果每个要使用 Store 的组件都这么搞,不得累死,所以我们考虑做一下封装,创建一个能通过 this.store 就能拿到全局 Store 的组件。

import React from "react";
import PropTypes from 'prop-types';export default class StoreAwareComponent extends React.Component {static contextTypes = {store: PropTypes.object};componentWillMount() {this.store = this.context.store;}}
复制代码

嘿嘿,然后你只要继承这个组件就可以轻松拿到全局 Store 了。

import React from "react";
import PropTypes from 'prop-types';export default class DeepLayerComponent extends StoreAwareComponent {componentDidMount() {this.store.dispatch({type: 'DO_SOMETHING'});}}
复制代码

这篇我就不作总结了。(逃

———

技术上的问题,欢迎讨论。

个人博客:mindjet.github.io

最近在 Github 上维护的项目:

  • LiteWeather [一款用 Kotlin 编写,基于 MD 风格的轻量天气 App],对使用 Kotlin 进行实际开发感兴趣的同学可以看看,项目中会使用到 Kotlin 的委托机制、扩展机制和各种新奇的玩意。
  • Reask [用 React&Flask 开发的全栈项目,前端采用 react-redux]
  • LiteReader [一款基于 MD 的极轻阅读 App,提供知乎日报、豆瓣电影等资源],项目主要使用了 MVVM 设计模式,界面遵循 Material Design 规范,提供轻量的阅读体验。
  • LiveMVVM [Kotlin 编写的 Android MVVM 框架,基于 android-architecture],轻量 MVVM+Databinding 开发框架。

欢迎 star/fork/follow 提 issue 和 PR。

React Redux 与胖虎他妈相关推荐

  1. React Redux 与胖虎

    这是一篇详尽的 React Redux 扫盲文. 对 React Redux 已经比较熟悉的同学可以直接看 <React Redux 与胖虎他妈>. 是什么 React Redux 是 R ...

  2. React+Redux开发实录(一)搭建工程脚手架

    React+Redux开发实录(一)搭建工程脚手架 React+Redux开发实录(二)React技术栈一览 搭建工程脚手架 准备工作 安装node 安装git 安装一款前端IDE 推荐VSCode, ...

  3. 基于 react, redux 最佳实践构建的 2048

    前段时间 React license 的问题闹的沸沸扬扬,搞得 React 社区人心惶惶,好在最终 React 团队听取了社区意见把 license 换成了 MIT.不管 React license ...

  4. React Redux 的一些基本知识点

    一.React.createClass 跟 React.Component 的区别在于后者使用了ES6的语法,用constructor构造器来构造默认的属性和状态. 1. React.createCl ...

  5. 【视频】React redux toolkit创建状态切片

    React redux toolkit创建状态切片

  6. react实战项目_React实战之React+Redux实现一个天气预报小项目

    引言 经过一段时间的React学习,React和Vue的开发确实有很大的不同,但是都是MVVM框架,因此上手没有很大的难度,这次用React+Redux开发一个天气预报小项目.源码地址:https:/ ...

  7. React+Redux仿Web追书神器

    引言 由于 10 月份做的 React Native 项目没有使用到 Redux 等库,写了一段时间想深入学习 React,有个想法想做个 demo 练手下,那时候其实还没想好要做哪一个类型的,也看了 ...

  8. react+redux+generation-modation脚手架搭建一个todolist

    TodoList 1. 编写actions.js 2. 分析state 试着拆分成多个reducer 3. 了解store 4. 了解redux数据流生命周期 5. 分析容器组件和展示组件 搞清楚,数 ...

  9. webpack+react+redux+es6开发模式---续

    一.前言 之前介绍了webpack+react+redux+es6开发模式 ,这个项目对于一个独立的功能节点来说是没有问题的.假如伴随着源源不断的需求,前段项目会涌现出更多的功能节点,需要独立部署运行 ...

最新文章

  1. R语言可视化斜率图、扩充图像纵横比为数据标签显示更整齐、ggrepel包来帮忙
  2. python输出乘法口诀-python以不同方式打印输出九九乘法表
  3. python join字符连接函数的使用方法
  4. 渗透测试专题之decms的攻防篇(一)
  5. C++中 static 关键字的作用
  6. 策略模式Strategy Pattern应用场景
  7. 【渝粤教育】广东开放大学 行政管理 形成性考核 (35)
  8. HDU 5600(瞎搞)
  9. 手册中数组最后一个元素的 $value 引用在 foreach 循环之后仍会保留的理解
  10. 【英语学习】【WOTD】farouche 释义/词源/示例
  11. [Web Chart系列之三] 图形布局-Layout
  12. stm32 FATFS文件系统如何减少Flash和RAM占用,FATFS移除中文文件名,FATFS移除动态内存
  13. 虚拟机一直安装程序正在启动服务器失败,安装使用Vmware出现的问题及解决方法...
  14. stvd使用c语言编程,STVD使用教程.pdf
  15. 南京航空大学c语言课程设计,南京航空航天大学C语言课程设计报告.doc
  16. 撩小姐姐的小程序(二)----旋转3D八音盒
  17. whm 设置共享IP
  18. ==06-07第一网络大事件---熊猫烧香==
  19. ArduPilot之开源代码Sensor Drivers设计
  20. [Python3] 超级码力在线编程大赛初赛 第2场 题解

热门文章

  1. U盘安装原版Win7或Win8教程
  2. 【网络基础】qps | tps | pv | uv
  3. 柚子壁纸为什么自动安装_Wallpaper Engine,Windows最好用的动态壁纸软件,附教程!...
  4. Dxoygen语法规范
  5. 计算机毕业设计 SSM+Vue停车位管理系统 临时停车位管理系统 车位停车管理系统 停车位租用管理系统Java Vue MySQL数据库 远程调试 代码讲解
  6. 大数据基础与应用课程总结
  7. iOS7 Xcode5 键盘的处理
  8. linux 共享存储 iostat,Linux环境下存储监控工具nfsiostat介绍
  9. python3 from aip import错误,包文件下载
  10. EndNote设置参考文献对齐与10.5号字体