本文将涉及如下三块内容:html
在上一篇文章《React Redux与胖虎》 中咱们详尽地介绍了 React Redux,也写了一个简单的计数器。前端
这篇文章叫《React Redux与胖虎他妈》,由于在哆啦A梦里面,胖虎虽然很屌总是欺负大雄和小夫,可是在他妈面前没少挨揍,胖虎他妈仍是他妈,因此这篇文章主要是介绍 React Redux 的一些进阶用法。react
开发过程当中,咱们因为业务或者功能的划分,通常不一样模块的数据也是不一样的,若是只用一个 Reducer,那么这个 Reducer 要处理全部模块过来的事件,而后返回一个 state,全部的数据都糅合在这个 state 里面,全部接收到这个 state 的模块还得解析出其中跟本身有关的部分。android
因此单个 Reducer 并不能知足当下需求,多 Reducer 的出现有利于咱们模块化开发,下降耦合度。git
redux
提供了 combineReducers
函数来组合 Reducer,注意不是 react-redux
库。github
// 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 都会收到。json
咱们知道,在 Reducer 只有一个的状况下,容器组件的 mapStateToProps
函数接收到的 state 即为惟一 Reducer 返回的对象。redux
而在 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,即相似于同步事件那样的对象。
咱们要在中间件搞些事情,让函数类型的 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.dispatch
和 store.getState
传给它;若是不是,那么调用 next
将 Action 继续往下发送就好了。
行吧... 那咱们仿照 redux-thunk
写一个中间件,整合进网络请求的功能。
首先固然是容许 function 类型的 Action
export default store => next => action => {
if (typeof action === 'function') {
return action(store);
}
}
复制代码
而后当 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);
}
}
复制代码
若是有 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 });
}
复制代码
而后就开始进行异步操做,即网络请求。而且请求成功、请求失败和请求异常三种状况都会发送不一样的事件给下游的 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 });
});
}
复制代码
到此为止,一个比较复杂的带有网络请求的中间件就完成了。
还记得上一篇文章咱们说到“一个深度为 100 的组件要去改变一个浅层次组件的文案”的例子吗?咱们当时说,只要从深层次的组件里面发送一个事件出来就能够了,也就是使用 dispatch 函数来发送。
emmmm,咱们到如今好像还没遇到过直接在组件里面 dispatch 事件的状况,咱们以前都是在容器组件的 mapDispatchToProps
里面 dispatch 的。
因此在 UI 组件里面不能拿到 dispatch 函数?
这里先说明一点,咱们亲爱的 dispatch 函数,是存在于 Store 中的,能够用 Store.dispatch 调用。有些机灵的同窗已经想到,那咱们全局的 Store 引入 UI 组件不就好咯。
哦我亲爱的上帝,瞧瞧这个优秀的答案,来,我亲爱的汤姆斯·陈独秀先生,这是你的奖杯...
是的没错,这是一种方式,可是我以为这很不 React Redux。
在上一篇文章中,咱们说到引入了 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'});
}
}
复制代码
这里咱们声明 contextTypes
的 store
字段,而后就能够经过 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 上维护的项目:
欢迎 star/fork/follow 提 issue 和 PR。