react redux在项目中的使用

 

一开始我接触到redux是很懵的,看官方文档也很枯燥乏味,不理解说的什么意思,也是看过就忘,所以就结合公司的项目来熟悉一下redux。也可能有写的不对的地方,欢迎指出。

一、构建reducer

首先看storeConfig下的reducers.js。

combineReducers函数的作用是把一个或多个的reducer结合成一个reducer,作为createStore函数的第一个参数。

使用方法是:

combineReducers({ key1 : reducer1, key2 : reducer2 })

最终这个函数的返回的state的结构就是 {key1,key2},这个key值是可以自定义的,如果懒得再次命名,就可以使用ES6的简洁语法 combineReducer(reducer1, reducer2);返回的state就是 {reducer1, reducer2}的形式。

由于项目很庞大,在一开始就初始化所有的store是很繁重的,这里用到了动态加载redux模块的思想,把各个模块的redux分块存储,在项目首次加载时只加载一部分reducer(如layout 登录状态、全局配置,home首页的数据)。

上图的第三个参数asyncReducer代表的就是接收异步加载的模块的reducer,注入到根reducer中,这种异步按需加载reducer的方式也称作redux的动态注入

如何加载异步模块,就是通过路由了,具体是如何实现的呢,我们来看路由的总文件routes\index.js,这个文件包含了所有模块的页面。

以申请模块为例,我们看看申请模块下的路由总文件\routes\ApproveV2\index.js,它里面包含了申请模块下所有路由的页面,是对整个模块的路由的进一步分发,我们需要再进一步看。

来看申请列表页\routes\ApproveV2\ApplyEntry.js,这个页就是如何把引入相应的reducer并传给根reducer。

  • 首先引入redux配置中的injectReducer
  • 定义页面路由
  • 通过getComponent+webpack ensure实现在路由跳转时,异步加载该模块
  • 引入该模块相应的reducer
  • 通过injectReducer函数,传入两个参数。
    • 第一个参数为store,因为异步获取的redux模块的reducer都保存在store.asyncReducer中,最后在makeRootReducer函数中通过扩展运算符给了combineReducers。
    • 第二个参数为一个对象,键有key和reducer,还记得我们之前说的向combineReducers函数传递reducer函数时可以自定义reducer的名字吗,key就是指定的申请列表的名字为applyEntry,这样state中就有了state.applyEntry,并传入引入的模块的reducer
  • 加载该组件,进行路由的跳转。

那么在初始化组件之前,数据就需要从之前的  变为这样

把异步获取的reducer和原来的reducer结合在一起,是如何实现的呢?继续看这个文件storeConfig\reducers.js

它把接收来的store.asyncReducers传入一开始的makeRootReducer函数,通过replaceReducer(nextReducer)函数来立即加载替换原来的reducer,从而改变state为新的数据结构,调用combineReducer函数,生成新的根reducer。

打印store,我们可以看到在申请的injectReducer之前只有home

 

在申请的injectReducer之后就加上了申请的reducer

 

二、创建Redux store

接下来就来看 storeConfig\createStore.js文件,

创建store的函数就是createStore,

用法如下:

createStore(reducer,[preloadedState],enhancer)

第一个参数是是reducer,传入我们上面介绍过的reducers.js文件提供的makeRootState。

第二个参数是初始的state,传入的是一个空对象,

第三个参数是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator,可能不知道增强器是什么,那我们先来看他传入的是个什么东西,

compose(...functions) 把传入的函数参数从右向左组合成一个最终函数,右边的函数执行完就作为一个参数传递给左边的函数。

我们来看一下这个函数怎么实现的,

可以看到函数内部使用的是reduce函数,它是这样说的:

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

reduce() 可以作为一个高阶函数,用于函数的 compose。

这也就验证了enhancer是一个高阶函数

举个例子:

给compose传入四个参数

compose(func1,func2,func3,func4)

就会执行核心代码

funcs.reduce((a, b) => (...args) => a(b(...args)))

也就是

[func1,func2,func3,func4].reduce((a, b) => (...args) => a(b(...args)))

reduce函数从左向右执行,a参数代表上一次执行的返回值,b参数代表数组当前遍历到的值,b参数就作为下一次执行的参数传入。

这样最终的结果就是,最右边的函数可以传入任意参数,在外部调用的时候传入,其他函数都只能接收一个参数,就是上一次函数返回的结果。

func1(func12(func3(func4(...args))))

也就验证了compose的定义:把传入的函数参数从右向左组合成一个最终函数

下面我们来具体传入的是什么函数:

相关代码如下:

 

可以看到用到了applyMiddleware,redux-thunk,redux-logger。

先来看传入applyMiddleware函数的middleware参数是redux-thunk,它是一个现成的“中间件”库,我们先不管这个库具体怎么实现的,先了解一下中间件的概念,它可以提供位于 action 被发起之后,到达 reducer 之前的扩展点,就是如果我们不想让dispatch action之后,就立马更新state,而是在执行过程中可以做一些操作,如打印日志,异步发送ajax请求等。它的作用就是改造dispatch,在dispatch发出一个action之前,和action到达reducer之间添加一些东西。ps:具体怎么实现的,我实在是理解有限,还没搞清楚,想了解的再仔细看看官方文档。

如下面这个最简单的例子:

function dispatchAndLog(store, action) {
   console.log('dispatching', action)
   store.dispatch(action)
   console.log('next state', store.getState())
}

引入redux-thunk库的作用就是用来解决异步问题的,单纯的redux,在dispatch action发出之后,就立即计算更新state,这是同步,而在我们的项目中,往往有用户的操作需要请求接口来更新state,这时候就需要异步来执行action,原本的store.dispatch只能接收action对象,但是在经过redux-thunk的处理之后,store.dispatch可以接收一个函数作为参数,并且这个函数带有dispatch和getState参数

如项目中的例子,可以看到能接收一个函数fetchIndexInfo,而这个函数就是action creater,用来请求接口、获取数据的

在store.dispatch执行完传入的函数中的axios请求之后,还会返回一个dispatch,用来继续执行redux事件,

// 即把获取的数据存入store中

dispatch(updateIndexInfo(result.data));

export const updateIndexInfo = (value) => ({
   type: HOME_UPDATE_INDEX_INFO,
   payload: value
});

以上就实现了异步更新state,这是我们系统最核心的地方。

而redux-logger就是一个可以打印日志的中间件,在我们执行dispatch时,他会打印出dispatch具体的执行过程,并且注意要放在所有中间件的最后,防止先执行了,无法打印出日志的情况。

可以看到在控制台打印如下:

在构造好所有的中间件之后,需要通过applyMiddlerware函数来把他们组合成一个数组,从右向左顺序执行。实现的原理也是我们上面介绍过的compose函数,会把原来的dispatch函数替换为会遍历执行所有的middleware的新的dispatch函数,这样每次执行dispatch时都会经过一次次的middlewares的调用,最终会生成新的dispatch,并return出去。

来看源码,我看了好几遍也看不懂,感兴趣的可以深入研究一下。

以上介绍了createStore中的第三个参数enhancer的中的第一个参数applyMiddleware,我们来看第二个参数enhancers,devToolsExtension是一个观察redux中状态变化的一个谷歌插件。

if (__DEV__) {
    const devToolsExtension = window.devToolsExtension
    if (typeof devToolsExtension === 'function') {
       enhancers.push(devToolsExtension())
   }
}

通过compose函数把多个增强器:中间件和调试工具组合,嵌套执行。

以上就介绍完了createStore中传入的第三个参数,那这个参数是干什么用的呢,上面介绍了对middlerware的强化,它是对createStore方法进行强化的,返回功能加强后的新的store。

三、使用store

  • 在main.js文件中,引入上面创建好的createStore函数,进行实例化,创建好的store会暴露出一些API。
  • getState()用来获取应用的state树,
  • subscribe()是是一个变化监听器,在dispatch action之后,state会发生变化,在它的回调函数中,通过getState()重新获取state,赋值给全局变量。
  • 同时还要引入创建好的路由,
  • 把store和routes传入项目的根组件AppContainer,挂载到创建好的dom节点上。

下面来具体看一下AppContainer组件

引入的库如下:

import PropTypes from 'prop-types';

import React, { Component } from 'react';
import {browserHistory, Router} from 'react-router'
import { Provider } from 'react-redux'

1.  PropTypes是react内置的类型检查工具,用来对组件上的props进行类型检查。

使用方法如下:

项目中要求传入AppContainer的对象是必填的。

AppContainer.propTypes = {
    routes: PropTypes.object.isRequired
};

2. react router是基于history的,history监听浏览器地址栏的变化,然后router根据它来匹配路由,从而渲染正确的组件。

它有三种形式,browserHistory、hashHistory、createMemoryHistory,项目中使用的是browserHistory,它创建的URL是类似于example.com/some/path。

3.接下来看最关键的Provider,通过它包裹整个项目的根组件,并且传入Store对象,在相应的容器组件内才能通过connect来结合React和Redux。

关键之处就在于connect()函数,用法如下:

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

  • 第一个参数:mapStateToProps(state, [ownProps]): stateProps (Function)

根据名字来看,是对state到props做一个映射,这个函数接收两个参数,第一个参数是应用的state,第二个参数顾名思义指的是本组件的props,函数必须返回一个纯对象,这个对象就和组件的props组合,形成新的props,每当state变化,或者组件中的props变化的时候,mapStateToProps都会被调用。

例如组件ReduxForm中:

对本组件的props进行处理,获取到state中相应的数据,然后赋值给formReducerVal,那么组件的props就会多出这个属性。

  • 第二个参数:mapDispatchToProps(dispatch, [ownProps]): dispatchProps  (Object or Function)

根据名字意思就是把dispatch action也映射到组件的props上,如同把state映射到props上一样。

通常我们调用action时,需要由store上的dispatch来触发,下面的例子就把aciton和dispatch合成了一个值,作为当前组件的属性,使得可以在组件中通过this.props的方式访问action

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        increase: (...args) => dispatch(actions.increase(...args)),
        decrease: (...args) => dispatch(actions.decrease(...args))
    }
}
//在组件中访问action的方式

this.props.increase();

this.props.decease();

redux官方给提供了一个现成的方法,可以实现同样的效果。

const mapDispatchToProps = (dispatch, ownProps) => {
     return {
           increase: (...args) => dispatch(actions.increase(...args)),
           decrease: (...args) => dispatch(actions.decrease(...args))
     }
}

在我们的项目中是这样使用的,所以在组件中直接通过 this.props.dispatch的方式调用action,并没有进行过多的处理。

const mapDispatchToProps = (dispatch) => {
   return {dispatch}
};

 

const mapDispatchToProps = (dispatch) => {

    return {

         ...bindActionCreators(actions,dispatch), dispatch

    }

}

  • 第三个参数 mergeProps(stateProps, dispatchProps, ownProps): props  (Function)

它是接收前面两个参数的执行结果的回调函数,并会把返回值和当前的props合并

  • 第四个参数options (Object)   用来定制 connector 的行为

 

以上就完成了react使用redux的基本步骤。