俺好像看懂了公司前端代碼
他來(lái)了,他來(lái)了,
他掉著頭發(fā)走來(lái)了。
大家好,我是前端開(kāi)發(fā)者卜壯,經(jīng)過(guò)筆者上篇《俺咋能看懂公司前端項(xiàng)目?》之后,不知道大家有沒(méi)有學(xué)到其設(shè)計(jì)思想并應(yīng)用到自己的項(xiàng)目中。我相信你們,肯定沒(méi)有。
趁著頭發(fā)茂密,讓我們步入正題!
今天的主角React,它作為當(dāng)今社會(huì)的前端主流框架,在前端框架江湖中算是一哥的存在,憑借小巧高效靈活等特點(diǎn),完成了眾多企業(yè)級(jí)的大項(xiàng)目,并且衍生了很多其他的框架,比如像跨平臺(tái)移動(dòng)開(kāi)發(fā)React Native,它的一套代碼可以運(yùn)行在Android和iOS。然而這些都不是本篇文章的重點(diǎn)。
今天的重點(diǎn)是React或React Native如何高效管理調(diào)用后端接口,和上篇講到Vue管理后端接口一樣,它們有很多相似性,也有不同之處,因?yàn)槲覀冎浪鼈冮_(kāi)發(fā)模式和方法有些不同。
起初的想法,Vue有自己?jiǎn)为?dú)的狀態(tài)管理器Vuex,React也可以用Redux來(lái)管理狀態(tài);Vue提供了混入(mixins)的開(kāi)發(fā)方式,雖然React起初也有混入的功能,后來(lái)被舍棄掉了,但是React可以通過(guò)高階組件來(lái)實(shí)現(xiàn)混入的功能?;谶@些想法,該出手時(shí)就出手,風(fēng)風(fēng)火火參北斗啊。
后來(lái),我終于學(xué)會(huì)了讓自己愛(ài)自己,搞錯(cuò)了,我終于學(xué)會(huì)了Redux以及React-redux,學(xué)起來(lái)其實(shí)和Vuex一樣,只是有些概念不一樣。
Vuex里面有State定義狀態(tài)、Mutation修改狀態(tài)、Action支持異步調(diào)用Mutation修改狀態(tài)、Getter從State派生狀態(tài)。
而在Redux中主要有Reducer和Action,Reducer是一個(gè)純函數(shù),根據(jù)不同的Action返回不同的狀態(tài),Action則是用于改變狀態(tài)唯一途徑。但是僅靠Redux提供的功能只支持同步修改狀態(tài),但是Redux有很多中間件,其中Redux-thunk就是一個(gè)支持異步的中間件,因?yàn)槭褂肦edux管理請(qǐng)求需要異步支持,所以,I want you。
哆來(lái)咪發(fā)梭拉稀
又唱上了。
前面說(shuō)了那么多都是湊字?jǐn)?shù),
下面開(kāi)始我們的步驟。
首先先了解一下前端管理后臺(tái)接口的架構(gòu)設(shè)計(jì)流程,技術(shù)選型后端要使用Swagger接口管理,前端React使用Redux狀態(tài)管理,React-redux狀態(tài)映射組件Props,Redux-thunk支持異步管理狀態(tài),解析Swagger需要用到Handlebars模板編譯和fs文件解析。

1、引入swagger。
后臺(tái)接口服務(wù)器引入swagger。這一步就不多說(shuō)了,你有我有全都有啊,誒嘿,誒嘿,參北斗啊。
2、解析swagger生成controller。
可以通過(guò)js寫一個(gè)腳本生成指定格式的js文件。swagger提供的v2/api-docs網(wǎng)址可以訪問(wèn)接口的json。這個(gè)json是一個(gè)固定格式的字符串,包含tags數(shù)組和path對(duì)象。通過(guò)Handlebars模板編譯和fs文件解析生成以下格式的js文件,每個(gè)類對(duì)應(yīng)一個(gè)文件。同時(shí)生成一個(gè)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、為每個(gè)controller文件生成對(duì)應(yīng)的actions和reducers函數(shù)。
上述所說(shuō)的入口文件index.js用來(lái)裝飾每一個(gè)controller,裝飾的內(nèi)容就是遍歷controller文件的actions對(duì)象,生成actions函數(shù)和reducers純函數(shù)。最后將生成的reducers交給redux管理,actions則為組件提供調(diào)用。actions函數(shù)里面有三步,包括請(qǐng)求前,請(qǐng)求中和請(qǐng)求后對(duì)狀態(tài)的處理。這三步是為了設(shè)置接口請(qǐng)求的loading狀態(tài),通過(guò)loading狀態(tài)來(lái)處理頁(yè)面的加載效果,省去在組件中自定義的邏輯判斷。下圖為每個(gè)接口在action函數(shù)的數(shù)據(jù)處理。

生成action和reducer的代碼:
- export default (name, controller) => {
- const defaultState = (type) => ({//設(shè)置請(qǐng)求前的數(shù)據(jù)狀態(tài),生成reducer時(shí)使用
- type: type,
- loading: false,
- error: null,
- request: null,
- data: {},
- })
- const start_request = (type) => ({//設(shè)置開(kāi)始請(qǐng)求的數(shù)據(jù)狀態(tài)
- 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) => {
- //設(shè)置開(kāi)始請(qǐng)求的數(shù)據(jù)狀態(tài)
- dispatch(start_request(`${name}_${key}`))
- //開(kāi)始網(wǎng)絡(luò)請(qǐng)求
- let resp = {}
- try {
- resp = await ajaxUtil.myRequest(action, payload)
- } catch (e) {
- resp = e }
- let handleResult = null
- //數(shù)據(jù)處理,這里對(duì)resp.status狀態(tài)碼為400-500的錯(cuò)誤處理,和200-300的成功數(shù)據(jù)處理
- if (!resp) {
- handleResult = {
- type: `${name}_${key}`,
- loading: false,
- error: '系統(tǒng)錯(cuò)誤',
- 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,
- }
- }
- //請(qǐng)求結(jié)束數(shù)據(jù)狀態(tài)處理
- 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、封裝高階組件,將接口請(qǐng)求狀態(tài)數(shù)據(jù)映射到組件的props中。
vuex里面有四個(gè)輔助函數(shù)這個(gè)react-redux要登場(chǎng)了。react-redux提供了一個(gè)connect,它是一個(gè)高階組件,接收 React 組件作為輸入,輸出一個(gè)新的 React 組件。高階組件讓代碼更具有復(fù)用性、邏輯性與抽象特征??梢詫?duì) render 方法作劫持,也可以控制 props 與 state。我們這里需要自己封裝一個(gè)高階組件,里面調(diào)用react-redux提供的connect函數(shù)將state和dispatch映射到組件的props,此外還需要定義兩個(gè)函數(shù)映射到props中,一個(gè)是用于調(diào)用接口的函數(shù),另一個(gè)是獲取請(qǐng)求接口的loading狀態(tài)函數(shù)。最后返回一個(gè)新的組件。
高階組件封裝如下:
- export default function connect(states = null, dispatches = null) {
- return (WrappedComponent) => {
- const mapStateToProps = state => {
- //該函數(shù)用于獲取網(wǎng)絡(luò)請(qǐng)求的loading,用于組件顯示加載中的指示
- const loading = (scope) => {
- return state[`${scope.controller}_${scope.method}`].loading
- }
- return states ? {...state, ...states(state), loading} : {...state, loading}
- }
- //集中處理請(qǐng)求發(fā)送的異常
- const error = (cbData) => {
- //判斷cbData.error,用來(lái)alert()提示用戶錯(cuò)誤信息
- }
- const mapDispatchToProps = dispatchAsync => {
- //該函數(shù)用于組件中發(fā)起網(wǎng)絡(luò)請(qǐng)求
- 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,我這里起了個(gè)別名,為了避免和我封裝的高階組件名沖突
- //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、組件引用自定義的高階組件。
如果組件涉及到網(wǎng)絡(luò)請(qǐng)求,可以引入。引入之后像這樣:
export default connect(mapStateToProps, mapDispatchToProps)(Home),其中Home是組件,mapStateToProps和mapDispatchToProps是想要指定映射哪些數(shù)據(jù)到props中,可以不傳。
然后就可以為所欲為了,發(fā)起網(wǎng)絡(luò)請(qǐng)求this.props.dispatch(IUserController.findById,{id}),返回值是請(qǐng)求后的數(shù)據(jù)。獲取請(qǐng)求狀態(tài)this.props.loading(IUserController.findById),返回值是true或false。
上文我著重說(shuō)的是react如何管理調(diào)用接口,其實(shí)react native設(shè)計(jì)是一模一樣的,大伙不妨試著設(shè)計(jì)一下。
許多事,
都是要經(jīng)過(guò)不斷嘗試才會(huì)成功的。
這篇內(nèi)容就到這里,我們下篇再見(jiàn)。