美文网首页
react 中间件 applyMiddleware, 自定义中

react 中间件 applyMiddleware, 自定义中

作者: 丹蕾_7933 | 来源:发表于2019-05-13 23:18 被阅读0次

    一、中间件的概念

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

    (1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。

    (2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。

    (3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

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

    letnext=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-logger模块。这里只介绍怎么使用中间件。

    import{applyMiddleware,createStore}from'redux';

    importcreateLoggerfrom'redux-logger';

    constlogger=createLogger();

    conststore=createStore(

    reducer,

    applyMiddleware(logger)

    );

    上面代码中,redux-logger提供一个生成器createLogger,可以生成日志中间件logger。然后,将它放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。

    这里有两点需要注意:

    (1)createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了。

    conststore=createStore(

    reducer,

    initial_state,

    applyMiddleware(logger)

    );

    (2)中间件的次序有讲究。

    conststore=createStore(

    reducer,

    applyMiddleware(thunk,promise,logger)

    );

    上面代码中,applyMiddleware方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger就一定要放在最后,否则输出结果会不正确。

    三、applyMiddlewares()

    看到这里,你可能会问,applyMiddlewares这个方法到底是干什么的?

    它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。下面是它的源码。

    exportdefaultfunction applyMiddleware(...middlewares) {

    return(createStore) =>(reducer,preloadedState,enhancer)=>{

    varstore=createStore(reducer,preloadedState,enhancer);

    vardispatch=store.dispatch;

    varchain=[];

    varmiddlewareAPI={

    getState:store.getState,

    dispatch:(action) =>dispatch(action)

    };

    chain=middlewares.map(middleware=>middleware(middlewareAPI));

    dispatch=compose(...chain)(store.dispatch);

    return{...store,dispatch}

    }

    }

    上面代码中,所有中间件被放进了一个数组chain,然后嵌套执行,最后执行store.dispatch。可以看到,中间件内部(middlewareAPI)可以拿到getState和dispatch这两个方法。

    四、异步操作的基本思路

    理解了中间件以后,就可以处理异步操作了。

    同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action。

    操作发起时的 Action

    操作成功时的 Action

    操作失败时的 Action

    以向服务器取出数据为例,三种 Action 可以有两种不同的写法。

    // 写法一:名称相同,参数不同

    {type:'FETCH_POSTS'}

    {type:'FETCH_POSTS',status:'error',error:'Oops'}

    {type:'FETCH_POSTS',status:'success',response:{...}}

    // 写法二:名称不同

    {type:'FETCH_POSTS_REQUEST'}

    {type:'FETCH_POSTS_FAILURE',error:'Oops'}

    {type:'FETCH_POSTS_SUCCESS',response:{...}}

    除了 Action 种类不同,异步操作的 State 也要进行改造,反映不同的操作状态。下面是 State 的一个例子。

    letstate={

    // ...

    isFetching:true,

    didInvalidate:true,

    lastUpdated:'xxxxxxx'

    };

    上面代码中,State 的属性isFetching表示是否在抓取数据。didInvalidate表示数据是否过时,lastUpdated表示上一次更新时间。

    现在,整个异步操作的思路就很清楚了。

    操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染

    操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染

    五、redux-thunk 中间件

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

    奥妙就在 Action Creator 之中。

    classAsyncAppextendsComponent {

    componentDidMount(){

    const{dispatch,selectedPost}=this.props

    dispatch(fetchPosts(selectedPost))

    }

    // ...

    上面代码是一个异步组件的例子。加载成功后(componentDidMount方法),它送出了(dispatch方法)一个 Action,向服务器要求数据fetchPosts(selectedSubreddit)。这里的fetchPosts就是 Action Creator。

    下面就是fetchPosts的代码,关键之处就在里面。

    constfetchPosts=postTitle=>(dispatch,getState)=>{

    dispatch(requestPosts(postTitle));

    returnfetch(`/some/API/${postTitle}.json`)

    .then(response=>response.json())

    .then(json=>dispatch(receivePosts(postTitle,json)));

    };

    };

    // 使用方法一

    store.dispatch(fetchPosts('reactjs'));

    // 使用方法二

    store.dispatch(fetchPosts('reactjs')).then(() =>

    console.log(store.getState())

    );

    上面代码中,fetchPosts是一个Action Creator(动作生成器),返回一个函数。这个函数执行后,先发出一个Action(requestPosts(postTitle)),然后进行异步操作。拿到结果后,先将结果转成 JSON 格式,然后再发出一个 Action(receivePosts(postTitle, json))。

    上面代码中,有几个地方需要注意。

    (1)fetchPosts返回了一个函数,而普通的 Action Creator 默认返回一个对象。

    (2)返回的函数的参数是dispatch和getState这两个 Redux 方法,普通的 Action Creator 的参数是 Action 的内容。

    (3)在返回的函数之中,先发出一个 Action(requestPosts(postTitle)),表示操作开始。

    (4)异步操作结束之后,再发出一个 Action(receivePosts(postTitle, json)),表示操作结束。

    这样的处理,就解决了自动发送第二个 Action 的问题。但是,又带来了一个新的问题,Action 是由store.dispatch方法发送的。而store.dispatch方法正常情况下,参数只能是对象,不能是函数。

    这时,就要使用中间件redux-thunk

    import{createStore,applyMiddleware}from'redux';

    importthunkfrom'redux-thunk';

    importreducerfrom'./reducers';

    // Note: this API requires redux@>=3.1.0

    conststore=createStore(

    reducer,

    applyMiddleware(thunk)

    );

    上面代码使用redux-thunk中间件,改造store.dispatch,使得后者可以接受函数作为参数。

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

    六、redux-promise 中间件

    既然 Action Creator 可以返回函数,当然也可以返回其他值。另一种异步操作的解决方案,就是让 Action Creator 返回一个 Promise 对象。

    这就需要使用redux-promise中间件。

    import{createStore,applyMiddleware}from'redux';

    importpromiseMiddlewarefrom'redux-promise';

    importreducerfrom'./reducers';

    conststore=createStore(

    reducer,

    applyMiddleware(promiseMiddleware)

    );

    这个中间件使得store.dispatch方法可以接受 Promise 对象作为参数。这时,Action Creator 有两种写法。写法一,返回值是一个 Promise 对象。

    constfetchPosts=

    (dispatch, postTitle) =>newPromise(function (resolve, reject) {

    dispatch(requestPosts(postTitle));

    returnfetch(`/some/API/${postTitle}.json`)

    .then(response=>{

    type:'FETCH_POSTS',

    payload:response.json()

    });

    });

    写法二,Action 对象的payload属性是一个 Promise 对象。这需要从redux-actions模块引入createAction方法,并且写法也要变成下面这样。

    import{createAction}from'redux-actions';

    classAsyncAppextendsComponent {

    componentDidMount(){

    const{dispatch,selectedPost}=this.props

    // 发出同步 Action

    dispatch(requestPosts(selectedPost));

    // 发出异步 Action

    dispatch(createAction(

    'FETCH_POSTS',

    fetch(`/some/API/${postTitle}.json`)

    .then(response=>response.json())

    ));

    }

    上面代码中,第二个dispatch方法发出的是异步 Action,只有等到操作结束,这个 Action 才会实际发出。注意,createAction的第二个参数必须是一个 Promise 对象。

    看一下redux-promise的源码,就会明白它内部是怎么操作的。

    exportdefaultfunction promiseMiddleware({ dispatch }) {

    returnnext=>action=>{

    if(!isFSA(action)){

    returnisPromise(action)

    ?action.then(dispatch)

    :next(action);

    }

    returnisPromise(action.payload)

    ?action.payload.then(

    result=>dispatch({...action,payload:result}),

    error=>{

    dispatch({...action,payload:error,error:true});

    returnPromise.reject(error);

    }

    )

    :next(action);

    };

    }

    从上面代码可以看出,如果 Action 本身是一个 Promise,它 resolve 以后的值应该是一个 Action 对象,会被dispatch方法送出(action.then(dispatch)),但 reject 以后不会有任何动作;如果 Action 对象的payload属性是一个 Promise 对象,那么无论 resolve 和 reject,dispatch方法都会发出 Action。

    中间件和异步操作,就介绍到这里。下一篇文章将是最后一部分,介绍如何使用react-redux这个库。

    相关文章

      网友评论

          本文标题:react 中间件 applyMiddleware, 自定义中

          本文链接:https://www.haomeiwen.com/subject/jqwdaqtx.html