俺好像看懂了公司前端代碼
他來了,他來了,
他掉著頭發走來了。
大家好,我是前端開發者卜壯,經過筆者上篇《俺咋能看懂公司前端項目?》之后,不知道大家有沒有學到其設計思想并應用到自己的項目中。我相信你們,肯定沒有。
趁著頭發茂密,讓我們步入正題!
今天的主角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文件解析。

1、引入swagger。
后臺接口服務器引入swagger。這一步就不多說了,你有我有全都有啊,誒嘿,誒嘿,參北斗啊。
2、解析swagger生成controller。
可以通過js寫一個腳本生成指定格式的js文件。swagger提供的v2/api-docs網址可以訪問接口的json。這個json是一個固定格式的字符串,包含tags數組和path對象。通過Handlebars模板編譯和fs文件解析生成以下格式的js文件,每個類對應一個文件。同時生成一個index.js入口文件,將所有的controller文件集中裝飾處理。
- export default{
- actions: {
- findById : {
- summary: '按主鍵查詢',
- method: 'get',
- url: (payload) => `/api/user/${payload.id}`,
- parameters: [{'name':'id','in':'path','description':'id','required':true,'type':'string'}],
- },
- },}
3、為每個controller文件生成對應的actions和reducers函數。
上述所說的入口文件index.js用來裝飾每一個controller,裝飾的內容就是遍歷controller文件的actions對象,生成actions函數和reducers純函數。最后將生成的reducers交給redux管理,actions則為組件提供調用。actions函數里面有三步,包括請求前,請求中和請求后對狀態的處理。這三步是為了設置接口請求的loading狀態,通過loading狀態來處理頁面的加載效果,省去在組件中自定義的邏輯判斷。下圖為每個接口在action函數的數據處理。

生成action和reducer的代碼:
- 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]
- //生成action
- actions[`${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
- }
- }
- //生成reducer
- reducers[`${name}_${key}`] = (state = defaultState(`${name}_${key}`), action) => {
- if (action.type === `${name}_${key}`) {
- return {...state, ...action}
- } else {
- return state
- }
- }
- })
- return {...actions, reducers}
- }
4、封裝高階組件,將接口請求狀態數據映射到組件的props中。
vuex里面有四個輔助函數這個react-redux要登場了。react-redux提供了一個connect,它是一個高階組件,接收 React 組件作為輸入,輸出一個新的 React 組件。高階組件讓代碼更具有復用性、邏輯性與抽象特征。可以對 render 方法作劫持,也可以控制 props 與 state。我們這里需要自己封裝一個高階組件,里面調用react-redux提供的connect函數將state和dispatch映射到組件的props,此外還需要定義兩個函數映射到props中,一個是用于調用接口的函數,另一個是獲取請求接口的loading狀態函數。最后返回一個新的組件。
高階組件封裝如下:
- 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}/>
- }
- }
- }}
5、組件引用自定義的高階組件。
如果組件涉及到網絡請求,可以引入。引入之后像這樣:
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設計是一模一樣的,大伙不妨試著設計一下。
許多事,
都是要經過不斷嘗試才會成功的。
這篇內容就到這里,我們下篇再見。