如何寫出可維護和可讀性高的代碼,這一直是一個困擾不少人的問題。關於變量如何起名、如何優化if else之類的小技巧,這裏就不作介紹了,推薦去看《代碼大全2》,千書萬書,都不如一本《代碼大全2》。javascript
工做以來,我一直在寫一些重複且交互複雜的頁面,也沒有整理過本身的思路,這篇文章是我工做一年半來在項目中總結出來的一些經驗。前端
對於業務代碼來講,大部分的前端應用都仍是以展現數據爲主,無非是從接口拿到數據,進行一系列數據格式化後,顯示在頁面當中。java
首先,應當儘量的進行分層,傳統的mvc分層很適用於前端開發,但對於複雜頁面來講,隨着業務邏輯增長,每每會形成controller臃腫的問題。所以,在此之上,能夠將controller其分紅formatter、service等等。react
+ pages
+ hotelList
+ components
+ Header.jsx
+ formatter
+ index.js
+ share
+ constants.js
+ utils.js
+ view.js
+ controller.js
+ model.js
複製代碼
統一管理全部請求路徑,而且將頁面中涉及到的網絡請求封裝爲class。api
// 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中,展現到頁面上。數據結構
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)。mvc
對於react來講,最外層的通常稱做容器組件,咱們會在容器組件裏面進行網絡請求等反作用的操做。
在這裏,容器組件裏面的一些邏輯也能夠剝離出來放到controller中(react-imvc就是這種作法),這樣就能夠給controller賦予生命週期,容器組件只用於純展現。
咱們將容器組件的生命週期放到wrapper這個高階組件中,並在裏面調用controller裏面封裝的生命週期,這樣咱們能夠就編寫更加純粹的view,例如:
wrapper.js
// wrapper.js(僞代碼)
const Wrapper = (view) => {
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}的鍵值對格式),這樣從時間複雜度上說,比直接遍歷兩個數組更高效。
在開始寫業務以前,理應先想清楚需求和業務邏輯,設計出合理的數據結構,對代碼進行好的分層,這樣在必定程度上能夠寫出可維護性更高的代碼。
PS:歡迎你們關注個人公衆號【前端小館】,你們一塊兒來討論技術。