为什么要专门来处理异步呢,因为redux的设计原则要求我们的reducer必须是纯函数,所以我们在reducer去执行一些副作用的业务是不合法的。那么怎么处理异步呢?笔者认为,对于真正理解了redux的开发者来说,处理异步并不是一件难事。如果还不够深入的理解Redux以及其的中间件机制,那么以下的内容可能引起不适,会看的云里雾里,建议翻看笔者第一篇文章从0到1重新撸一个Redux
redux-thunk
先来看看源码吧
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { // 判断dispatch的参数是否是函数,是函数就把dispatch getstate 参数交出 // 让函数自己去玩 if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; }
原理很简单,dispatch不仅可以dispatch原有的action,也可以dispatch函数,中间件thunk发现action是函数则把所有的核心方法和参数交出。看看怎么实际应用
import fetch from 'fetch' // 先设计一个异步请求 export function fetchNews(id) { // Thunk middleware 知道如何处理函数。 // 这里把 dispatch 方法通过参数的形式传给函数, // 以此来让它自己也能 dispatch action。 return function (dispatch) { // 首次 dispatch:更新应用的 state 来通知 // thunk middleware 调用的函数可以有返回值, // 它会被当作 dispatch 方法的返回值传递。 // 这个案例中,我们返回一个等待处理的 promise。 // 这并不是 redux middleware 所必须的,但这对于我们而言很方便。 return fetch(`/api/getNews`, {id}) .then( response => response.json(), error => console.log('An error occurred.', error) ) .then(json => dispatch({ name: 'add_news', data: json }) ) } }
把异步的业务放进redux中去
const store = createStore( rootReducer, applyMiddleware( thunkMiddleware, // 允许我们 dispatch() 函数 ) ) store.dispatch(fetchNews(1)).then(() => console.log(store.getState()))
如此一来,我们把异步的业务封装一下,把他当做一个异步action交给reudx的dispatch便可轻松解决redux本身不支持异步的问题。那么试问是否还有更好的异步解决方法呢?回到redux和异步业务本身,最为关键和直接的一点便是异步中业务可以dispatch,如果满足这个需求无论什么方法都可以解决redux异步的问题。
Promise.then与store
直接看吧
// redux module // 直接导出store export const store = createStore( rootReducer )
// business module import * as React from 'react'; import './index.less'; import {store} from './redux' class Header extends React.Component { constructor(props: LogoProps) { super(props); } componentDidMount() { fetch(`/api/getNews`, {id: 1}) .then( response => response.json(), error => console.log('An error occurred.', error) ) .then(json => store.dispatch({ name: 'add_news', data: json }) ) } render() { return (<div> hello world </div>); } } export default Header;
如此一来,连中间件都不需要就可以轻松解决异步,但是这样不利于代码管理,我们可以按模块的把每一个异步业务给聚合起来。
async/awit和BaseAction
先创建一个封装了store的dispatch的基类
import {store} from './redux'; export default class ActionModeBase { dispatch(params) { store.dispatch(params); } }
再创建一个处理请求新闻业务的类
import BaseAction from './ActionModeBase'; import fetch from 'fetch' export default class HandleNewsMode { getNews = async (id) => { const news = await fetch('/api/getNews', id); this.dispatch({ name: 'add_news', data: news }) } }
业务方引入异步业务的类
import * as React from 'react'; import './index.less'; import HandleNewsMode from './HandleNewsMode' class Header extends React.Component { constructor(props: LogoProps) { super(props); // 初始化 this.businessMode = new HandleNewsMode(); } componentDidMount() { this.businessMode.getNews(1); } render() { return (<div> hello world </div>); }
总结
处理redux的异步并不是一件难事,redux-thunk也不是万金油,对于更为复杂的异步场景笔者认为通过内聚异步业务+async更能胜任。