来源:Redux入门教程-阮一峰
文档:Redux中文文档
中间件
同步操作:在Action发出以后,Reducer立即返回新的State;
异步操作:在Action发出以后,过一段时间才执行Reducer;
中间件是使Reducer在异步操作结束后自动执行的工具
中间件的概念
中间件的功能是在异步操作结束之后能够自动执行Reducer更新state,这个功能应该被添加在发送Action的功能中,即store.dispatch()。
例如,在下面的例子中,将会对store.dispatch()进行改造,使其能够将Action和State打印。
let next=store.dispatch;
store.dispatch=function dispatchAndLog(action){
console.log('dispatching',action);
next(action);
console.log('next state',store.getState());
}
以上的dispatch()将会在发送Action的前后进行打印。
中间件的用法
一般常见的中间件都已经被编写好了,只要通过引用即可获得,引用的方法为:
import {applyMiddleware,createStore} from 'redux';
import createLogger from 'redux-logger';
const logger=createLogger();
const store=createStore(
reducer,
applyMiddleware(logger)
)
在上面的例子中,添加了一个日志中间件,将其放入applyMiddleware()
方法中作为参数传入createStore()。
注意:
- createStore()方法可以接收整个应用的初始状态作为参数,在接受初始状态时,applyMiddleware是第三个参数;
const store = createStore(
reducer,
initial_state,
applyMiddleware(logger)
);
- 中间件的次序需要注意
中间件的次序在有些时候有些要求,使用前要检查一下文档,比如logger一定要放在最后。
异步操作的基本思路
同步操作只要发出一种Action,但是异步操作要发出三种Action:
- 操作发起时的Action
- 操作成功时的Action
- 操作失败时的Action
在向服务器获得数据时,三种Action可以有两种不同的写法:
//写法1:名称相同,参数不同
{type:'FETCH_POSTS'}
{type:'FETCH_POSTS',status:'error',error:'0ops'}
{type:'FETCH_POSTS',status:"success",response:{...}}
//写法2:名称不同
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
Action种类不同,且异步操作的State也要进行改造,反映不同的操作状态。
let state = {
// ...
isFetching: true,
didInvalidate: true,
lastUpdated: 'xxxxxxx'
};
在上面的例子中,定义了State的属性isFetching是否在获得数据,didInvalidate表示数据是否过时,lastUpdated表示上一次更新时间。
在异步操作过程中,需要:
- 在操作开始时,送出一个Action,触发State更新为"正在操作"状态,View重新渲染;
- 操作结束后,再送出一个Action,触发State更新为"操作结束"状态,View再一次渲染。
redux-thunk中间件
异步操作需要送出两个Action:用户在操作开始时发送的第一个Action,在结束时送出第二个Action;
这需要在Action Creator中进行操作:
class AsyncApp extends Component {
componentDidMount() {
const { dispatch, selectedPost } = this.props
dispatch(fetchPosts(selectedPost))
}
// ...
在加载结束后生命周期函数内送出Action,向服务器请求数据(fetchPosts(selectedSubreddit)),这里的fetchPosts是Action Creator。
fetchPosts
fetchPosts的代码为:
const fetchPosts = postTitle => (dispatch, getState) => {
//第一个Action,表示异步操作开始
dispatch(requestPosts(postTitle));
return fetch(`/some/API/${postTitle}.json`)//获取数据
.then(response => response.json())//将数据转化为JSON
.then(json => dispatch(receivePosts(postTitle, json)));//发送第二个Action,表示异步操作结束
};
};
fetchPosts是一个Action Creator,返回一个函数,函数执行之后将会发出一个Action,进行异步操作得到结果后将会在对结果进行处理之后再发一个Action。
在上面的例子中,
fetchPosts
返回一个函数,普通的Action Creator默认返回一个对象;- 返回函数的参数是
dispatch
和getState
这两个Redux方法,普通的Action Creator的参数是Action的内容。
存在问题:在通常情况下,store.dispatch()的参数为Action对象,不能是函数。
为了解决整个问题,使用中间件redux-thunk;
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
// Note: this API requires redux@>=3.1.0
const store = createStore(
reducer,
applyMiddleware(thunk)
);
使用中间件改造后的store.dispatch()将可以接受函数作为参数。
这是异步操作的第一个解决方案。
redux-promise
另一个异步操作的解决方案时让Action Creator返回一个Promise对象,这需要redux-promise中间件的支持。
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import reducer from './reducers';
const store = createStore(
reducer,
applyMiddleware(promiseMiddleware)
);
这个中间件使得store.dispatch()可以接收Promise对象作为参数,这时有两种写法,一种是将Action Creator的返回值设置为一个Promise对象;
另一种是将Action的payload属性设置为Promise对象,这需要从redux-actions引入createAction方法。
两种写法如下:
//第一种
const fetchPosts =
(dispatch, postTitle) => new Promise(function (resolve, reject) {
dispatch(requestPosts(postTitle));
return fetch(`/some/API/${postTitle}.json`)
.then(response => {
type: 'FETCH_POSTS',
payload: response.json()
});
});
//第二种
import { createAction } from 'redux-actions';
class AsyncApp extends Component {
componentDidMount() {
const { dispatch, selectedPost } = this.props
// 发出同步 Action
dispatch(requestPosts(selectedPost));
// 发出异步 Action
dispatch(createAction(
'FETCH_POSTS',
fetch(`/some/API/${postTitle}.json`)
.then(response => response.json())
));
}
createAction的第二个参数必须是一个Promise对象。
对于redux-promise的源码进行分析:
export default function promiseMiddleware({ dispatch }) {
return next => action => {
if (!isFSA(action)) {
return isPromise(action)//Action是一个Promise
? action.then(dispatch)//resolve的值将会被送出,reject无动作
: next(action);
}
return isPromise(action.payload)//Action的payload属性是一个Promise
? action.payload.then(
result => dispatch({ ...action, payload: result }),//resolve发出Action
error => {//reject发出Action
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
}
)
: next(action);
};
}