摘要:前言如何寫出可維護和可讀性高的代碼,這一直是一個困擾很多人的問題。總結在開始寫業務之前,理應先想清楚需求和業務邏輯,設計出合理的數據結構,對代碼進行好的分層,這樣在一定程度上可以寫出可維護性更高的代碼。
前言
如何寫出可維護和可讀性高的代碼,這一直是一個困擾很多人的問題。關于變量如何起名、如何優化if else之類的小技巧,這里就不做介紹了,推薦去看《代碼大全2》,千書萬書,都不如一本《代碼大全2》。
工作以來,我一直在寫一些重復且交互復雜的頁面,也沒有整理過自己的思路,這篇文章是我工作一年半來在項目中總結出來的一些經驗。
分層對于業務代碼來說,大部分的前端應用都還是以展示數據為主,無非是從接口拿到數據,進行一系列數據格式化后,顯示在頁面當中。
首先,應當盡可能的進行分層,傳統的mvc分層很適用于前端開發,但對于復雜頁面來說,隨著業務邏輯增加,往往會造成controller臃腫的問題。因此,在此之上,可以將controller其分成formatter、service等等。
下面這是一些分層后簡單的目錄結構。
+ pages + hotelList + components + Header.jsx + formatter + index.js + share + constants.js + utils.js + view.js + controller.js + model.jsService
統一管理所有請求路徑,并且將頁面中涉及到的網絡請求封裝為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,后續某個請求方法名修改了后,在所有調用的地方也會提示錯誤,非常方便。
formatterformatter層儲存一些格式化數據的方法,這些方法接收數據,返回新的數據,不應該再涉及到其他的邏輯,這樣有利于單元測試。單個format函數也不應該格式化過多數據,函數應該根據功能進行適當拆分,合理復用。
mvc顧名思義,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)。
對于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 ( <>> ) } 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}的鍵值對格式),這樣從時間復雜度上說,比直接遍歷兩個數組更高效。
總結在開始寫業務之前,理應先想清楚需求和業務邏輯,設計出合理的數據結構,對代碼進行好的分層,這樣在一定程度上可以寫出可維護性更高的代碼。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102792.html
摘要:開工,我是如何定義現代切圖的既然所有后臺都有計劃重做,那么統一風格那就是必須的了。我們前端部門采用的是自己搭建的。讓我使用,我是不樂意的。我采用提供一個服務,提供靜態頁面預覽。沒錯我就是這么直接。 歡迎一起交流 歡迎關注我的個人公眾號,不定期更新自己的工作心得。 showImg(https://segmentfault.com/img/bVEk23?w=258&h=258); 什么是靜...
摘要:開工,我是如何定義現代切圖的既然所有后臺都有計劃重做,那么統一風格那就是必須的了。我們前端部門采用的是自己搭建的。讓我使用,我是不樂意的。我采用提供一個服務,提供靜態頁面預覽。沒錯我就是這么直接。 歡迎一起交流 歡迎關注我的個人公眾號,不定期更新自己的工作心得。 showImg(https://segmentfault.com/img/bVEk23?w=258&h=258); 什么是靜...
摘要:后端開發的疑惑后端開發最常面對的一個問題性能高并發等等。而到了時代,在方面有了前后端分離概念移動后端更是無力渲染天然前后端分離。 先來上一張前端頁面的效果圖(Vue + Vux + Vuex + Vue-Router)。showImg(https://segmentfault.com/img/remote/1460000010207850); 第一次做gif 沒什么經驗,太大了。加載...
閱讀 1265·2019-08-30 12:49
閱讀 3106·2019-08-28 18:14
閱讀 818·2019-08-26 11:38
閱讀 1675·2019-08-23 18:23
閱讀 2821·2019-08-23 17:04
閱讀 499·2019-08-23 16:52
閱讀 4015·2019-08-23 16:43
閱讀 2768·2019-08-23 16:12