如何寫出可維護和可讀性高的代碼,這一直是一個困擾不少人的問題。關於變量如何起名、如何優化if else之類的小技巧,這裏就不作介紹了,推薦去看《代碼大全2》,千書萬書,都不如一本《代碼大全2》。javascript
工做以來,我一直在寫一些重複且交互複雜的頁面,也沒有整理過本身的思路,這篇文章是我工做一年半來在項目中總結出來的一些經驗。前端
對於業務代碼來講,大部分的前端應用都仍是以展現數據爲主,無非是從接口拿到數據,進行一系列數據格式化後,顯示在頁面當中。java
首先,應當儘量的進行分層,傳統的mvc分層很適用於前端開發,但對於複雜頁面來講,隨着業務邏輯增長,每每會形成controller臃腫的問題。所以,在此之上,能夠將controller其分紅formatter、service等等。react
下面這是一些分層後簡單的目錄結構。api
+ pages + hotelList + components + Header.jsx + formatter + index.js + share + constants.js + utils.js + view.js + controller.js + model.js
統一管理全部請求路徑,而且將頁面中涉及到的網絡請求封裝爲class。數組
// api.js export default { HOTELLIST: '/hotelList', HOTELDETAIL: '/hotelDetail' } // Service.js class Service { fetchHotelList = (params) => { return fetch(HOTELLIST, params); } fetchHotelDetail = (params) => { return fetch(HOTELLIST, params); } } export default new Service
這樣帶來的好處就是,很清楚的知道頁面中涉及了哪些請求,若是使用了TypeScript,後續某個請求方法名修改了後,在全部調用的地方也會提示錯誤,很是方便。緩存
formatter層儲存一些格式化數據的方法,這些方法接收數據,返回新的數據,不該該再涉及到其餘的邏輯,這樣有利於單元測試。單個format函數也不該該格式化過多數據,函數應該根據功能進行適當拆分,合理複用。網絡
顧名思義,controller就是mvc中的c,controller應該是處理各類反作用操做(網絡請求、緩存、事件響應等等)的地方。數據結構
當處理一個請求的時候,controller會調用service裏面對應的方法,拿到數據後再調用formatter的方法,將格式化後的數據存入store中,展現到頁面上。mvc
class Controller { fetchHotelList = () => async (dispatch) => { const params = {} this.showLoading(); try { const res = await Service.fetchHotelList(params) const hotelList = formatHotelList(res.Data && res.Data.HotelList) dispatch({ type: 'UPDATE_HOTELLIST', hotelList }) } catch (err) { this.showError(err); } finally { this.hideLoading(); } } }
view則是指react組件,建議儘可能用純函數組件,有了hooks以後,react也會變得更加純粹(實際上有狀態組件也能夠看作一個mvc的結構,state是model,render是view,各類handler方法是controller)。
對於react來講,最外層的通常稱做容器組件,咱們會在容器組件裏面進行網絡請求等反作用的操做。
在這裏,容器組件裏面的一些邏輯也能夠剝離出來放到controller中(react-imvc就是這種作法),這樣就能夠給controller賦予生命週期,容器組件只用於純展現。
咱們將容器組件的生命週期放到wrapper這個高階組件中,並在裏面調用controller裏面封裝的生命週期,這樣咱們能夠就編寫更加純粹的view,例如:
wrapper.js
// wrapper.js(僞代碼) const Wrapper = (components) => { return class extends Component { constructor(props) { super(props) } componentWillMount() { this.props.pageWillMount && this.props.pageWillMount() } componentDidMount() { this.props.pageDidMount && this.props.pageDidMount() } } componentWillUnmount() { this.props.pageWillLeave && this.props.pageWillLeave() } render() { const { store: state, actions } = this.props return view({state, actions}) } } }
view.js
// view.js function view({ state, actions }) { return ( <> <Header title={state.title} handleBack={actions.goBackPage} /> <Body /> <Footer /> </> ) } export default Wrapper(view)
controller.js
// controller.js class Controller { pageDidMount() { this.bindScrollEvent('on') console.log('page did mount') } pageWillLeave() { this.bindScrollEvent('off') console.log('page will leave') } bindScrollEvent(status) { if (status === 'on) { this.bindScrollEvent('off'); window.addEventListener('scroll', this.handleScroll); } else if (status === 'off') { window.removeEventListener('scroll', this.handleScroll); } } // 滾動事件 handleScroll() { } }
對於埋點來講,本來也應該放到controller中,但也是能夠獨立出來一個tracelog層,至於tracelog層如何實現和調用,仍是看我的愛好,我比較喜歡用發佈訂閱的形式。
若是還涉及到緩存,那咱們也能夠再分出來一個storage層,這裏存放對緩存進行增刪查改的各類操做。
對於一些經常使用的固定不變的值,也能夠放到constants.js,經過引入constants來獲取值,這樣便於後續維護。
// constants.js export const cityMapping = { '1': '北京', '2': '上海' } export const traceKey = { 'loading': 'PAGE_LOADING' } // tracelog.js class TraceLog { traceLoading = (params) => { tracelog(traceKey.loading, params); } } export default new TraceLog // storage.js export default class Storage { static get instance() { // } setName(name) { // } getName() { // } }
不過也不表明着這樣寫就夠了,分層只可以保證代碼結構上的清晰,真正想寫出好的業務代碼,最重要的仍是你對業務邏輯足夠清晰,頁面上的數據流動是怎樣的?數據結構怎麼設計更加合理?頁面上有哪些交互?這些交互會帶來哪些影響?
以以下酒店列表頁爲例,這個頁面看似簡單,實際上包含了不少複雜的交互。
上方的是四個篩選項菜單,點開后里麪包含了不少子類篩選項,好比篩選裏面包括了雙牀、大牀、三牀,價格/星級裏面包含了高檔/豪華、¥150-¥300等等。
下方是快捷篩選項,對應了部分篩選項菜單裏面的子類篩選項。
當咱們選中篩選裏面的雙牀後,下方的雙牀也會被默認選中,反之當咱們選中下方的雙牀後,篩選類別裏面的雙牀也會被選中,名稱還會回顯到原來的篩選上。
除此以外,咱們點擊搜索框後,輸入'雙牀',聯想詞會出現雙牀,並表示這是個篩選項,若是用戶選中了這個雙牀,咱們依然須要篩選項和快捷篩選項默認選中。
這三個地方都涉及到了篩選項,而且修改一個,其餘兩個地方就要跟着改變,更況且三者的數據來自於三個不一樣的接口數據,這是多麼蛋疼的一件事情!
我藉助這個例子來講明,在開始寫頁面以前,必定要對頁面中的隱藏交互和數據流動很熟悉,也須要去設計更加合理的數據結構。
對於深層次的列表結構,鍵值對會比數組查詢速度更快,經過key也會更容易和其餘數據進行聯動,可是卻不能保證順序,有時候可能就須要犧牲空間來換時間。
// 假設篩選項牀型type爲1,大牀id爲1,雙牀id爲2. const bed = { '1-1': { name: '大牀', id: 1, type: 1 }, '1-2': { name: '雙牀', id: 2, type: 1 } } const bedSort = ['1-1', '1-2'] // 保證展現順序
當咱們選中大牀的時候,只須要保存'1-1'這個key,再和store中快捷篩選項列表裏面的key進行mapping(快捷篩選項裏面的項也應該格式化爲{'type-id': filterItem}的鍵值對格式),這樣從時間複雜度上說,比直接遍歷兩個數組更高效。
在開始寫業務以前,理應先想清楚需求和業務邏輯,設計出合理的數據結構,對代碼進行好的分層,這樣在必定程度上能夠寫出可維護性更高的代碼。