
●●●
他来了,他来了,
他掉着头发走来了。
大家好,我是前端开发者卜壮,经过笔者上篇《俺咋能看懂公司前端项目?》之后,不知道大家有没有学到其设计思想并应用到自己的项目中。我相信你们,肯定没有。

趁着头发茂密,让我们步入正题!
今天的主角React,它作为当今社会的前端主流框架,在前端框架江湖中算是一哥的存在,凭借小巧高效灵活等特点,完成了众多企业级的大项目,并且衍生了很多其他的框架,比如像跨平台移动开发React Native,它的一套代码可以运行在Android和iOS。然而这些都不是本篇文章的重点。

今天的重点是React或React Native如何高效管理调用后端接口,和上篇讲到Vue管理后端接口一样,它们有很多相似性,也有不同之处,因为我们知道它们开发模式和方法有些不同。
起初的想法,Vue有自己单独的状态管理器Vuex,React也可以用Redux来管理状态;Vue提供了混入(mixins)的开发方式,虽然React起初也有混入的功能,后来被舍弃掉了,但是React可以通过高阶组件来实现混入的功能。基于这些想法,该出手时就出手,风风火火参北斗啊。
后来,我终于学会了让自己爱自己,搞错了,我终于学会了Redux以及React-redux,学起来其实和Vuex一样,只是有些概念不一样。
Vuex里面有State定义状态、Mutation修改状态、Action支持异步调用Mutation修改状态、Getter从State派生状态。
而在Redux中主要有Reducer和Action,Reducer是一个纯函数,根据不同的Action返回不同的状态,Action则是用于改变状态唯一途径。但是仅靠Redux提供的功能只支持同步修改状态,但是Redux有很多中间件,其中Redux-thunk就是一个支持异步的中间件,因为使用Redux管理请求需要异步支持,所以,I want you。
首先先了解一下前端管理后台接口的架构设计流程,技术选型后端要使用Swagger接口管理,前端React使用Redux状态管理,React-redux状态映射组件Props,Redux-thunk支持异步管理状态,解析Swagger需要用到Handlebars模板编译和fs文件解析。

export default{actions: {findById : {summary: '按主键查询',method: 'get',url: (payload) => `/api/user/${payload.id}`,parameters: [{'name':'id','in':'path','description':'id','required':true,'type':'string'}],},},}
上述所说的入口文件index.js用来装饰每一个controller,装饰的内容就是遍历controller文件的actions对象,生成actions函数和reducers纯函数。最后将生成的reducers交给redux管理,actions则为组件提供调用。actions函数里面有三步,包括请求前,请求中和请求后对状态的处理。这三步是为了设置接口请求的loading状态,通过loading状态来处理页面的加载效果,省去在组件中自定义的逻辑判断。下图为每个接口在action函数的数据处理。

export default (name, controller) => {const defaultState = (type) => ({//设置请求前的数据状态,生成reducer时使用type: type,loading: false,error: null,request: null,data: {},})const start_request = (type) => ({//设置开始请求的数据状态type: type,loading: true,error: null,request: null,data: {},})let actions = {}let reducers = {}//遍历生成的controller文件的actions_.forEach(_.keys(controller.actions), key => {const action = controller.actions[key]//生成actionactions[`${name}_${key}`] = (payload) => {return async (dispatch) => {//设置开始请求的数据状态dispatch(start_request(`${name}_${key}`))//开始网络请求let resp = {}try {resp = await ajaxUtil.myRequest(action, payload)} catch (e) {resp = e}let handleResult = null//数据处理,这里对resp.status状态码为400-500的错误处理,和200-300的成功数据处理if (!resp) {handleResult = {type: `${name}_${key}`,loading: false,error: '系统错误',data: null,}} else if (resp.status >= 400 && resp.status < 500) {handleResult = {...}} else if (resp.status >= 500) {handleResult = {...}} else if (resp.status >= 200 && resp.status < 300) {handleResult = {type: `${name}_${key}`,loading: false,error: null,request: payload,data: resp.data,}}//请求结束数据状态处理dispatch(handleResult)return handleResult}}//生成reducerreducers[`${name}_${key}`] = (state = defaultState(`${name}_${key}`), action) => {if (action.type === `${name}_${key}`) {return {...state, ...action}} else {return state}}})return {...actions, reducers}}
export default function connect(states = null, dispatches = null) {return (WrappedComponent) => {const mapStateToProps = state => {//该函数用于获取网络请求的loading,用于组件显示加载中的指示const loading = (scope) => {return state[`${scope.controller}_${scope.method}`].loading}return states ? {...state, ...states(state), loading} : {...state, loading}}//集中处理请求发送的异常const error = (cbData) => {//判断cbData.error,用来alert()提示用户错误信息}const mapDispatchToProps = dispatchAsync => {//该函数用于组件中发起网络请求const dispatch = async (scope, payload) => {const controller = Controller[scope.controller]let cbData = await dispatchAsync(controller[`${scope.controller}_${scope.method}`](payload))error(cbData)return cbData}return dispatches ? {...dispatches(dispatchAsync), dispatch} : {dispatch}}//reduxConnect是react-redux提供的,原名称是connect,我这里起了个别名,为了避免和我封装的高阶组件名冲突//import {connect as reduxConnect} from 'react-redux'const Root = reduxConnect(mapStateToProps, mapDispatchToProps)(WrappedComponent)return class HOC extends React.Component {render() {return <Root {...this.props}/>}}}}
如果组件涉及到网络请求,可以引入。引入之后像这样:
export default connect(mapStateToProps, mapDispatchToProps)(Home),其中Home是组件,mapStateToProps和mapDispatchToProps是想要指定映射哪些数据到props中,可以不传。
然后就可以为所欲为了,发起网络请求this.props.dispatch(IUserController.findById,{id}),返回值是请求后的数据。获取请求状态this.props.loading(IUserController.findById),返回值是true或false。
上文我着重说的是react如何管理调用接口,其实react native设计是一模一样的,大伙不妨试着设计一下。
许多事,
都是要经过不断尝试才会成功的。
这篇内容就到这里,我们下篇再见。
关于作者:卜壮,普元前端开发工程师,负责Mobile 8.0项目管理平台前端部分。熟悉ReactNative,目前正在学习Vue,大前端技术探求者。

