編寫可維護的現代化前端項目

本文其實能夠當作是對 接手前端新項目?這裏有些注意點你可能須要留意一下 的補充,主要是我在平時寫代碼過程當中獲得的一些經驗css

使用 typescript

忘了從哪裏看到的一句話,意思是使用了 typescript的項目,將比不使用的項目體積多出 30%,固然,這多出來的 30%代碼只是你本地須要寫的代碼,項目打包完畢後,是否使用 ts,項目體積基本上是同樣大的html

ts的好處我就很少加贅述了,其最實用的就是類型檢查,同時加強編輯器的提示功能,須要注意的是,既然接入了 ts,那就儘可能把 ts的優點體現出來,不要爲了偷懶把 typescript 寫成了 anyscript,不然還不如不加前端

另外,常常聽人說,小項目不必接入 ts,只有大項目才須要,實際上,按照個人感受,不必分什麼大的小的,容易形成迷惑,你爲公司寫的項目確定不會小到哪裏去的,並且確定不是寫完了就無論了,後續確定須要屢次迭代維護的,無腦接入就好了vue

至於你本身寫的我的項目,那就看你本身心情了,由於代碼都是你本身的,你本身應該知道本身在寫啥react

不要在模板裏寫太多的邏輯

不管是 react 仍是 vue,模板或者 jsx中都不要寫太多的邏輯,模板就是用來放佈局的,儘可能不要幹其餘的事情,邏輯最好所有放到 js中,摻雜了大量邏輯的模板,不只會讓模板面目全非,很難一眼看清楚頁面佈局,同時維護起來也很困難,由於模板中只是支持部分 js的語法,並非所有,一段邏輯可能在 js中很輕易地就能實現,但在模板中,由於語法侷限,要繞一大圈才能搞定vuex

例如,當拍下一個商品後,會有一個對應的商品狀態流傳的進度頁面,這個頁面的底部可能存在按鈕,當商品拍下還沒發貨時,顯示 催發貨,當商品發貨後,顯示 確認收貨,當商品確認收貨後,顯示 去評價,這只是最基本的,複雜一點,可能有五種以上的按鈕,搞很差還要根據當前流轉的狀態來決定要同時顯示幾個按鈕,加起來十幾種的按鈕排列顯示方式chrome

對於這種情形,我看到過最多的解決方式就是直接在模板裏 if..else,大 if 套小 ifif的判斷體可能還要依賴於不止一個條件,那真是慘不忍睹,半屏幕的模板看下來,結果發現其實只是爲了顯示頁面最底部那兩個按鈕,想改哪一個按鈕,首先得層層追蹤清楚這個按鈕到底寫了哪些以及哪幾層條件,爲了兼顧 UI,還要確認一下改了以後會不會影響到顯示效果,瞎浪費時間typescript

<div class="bottom_btn">
  <div class="single" v-if="status === '1'">
    <button @click="buttonClick('sendBySelfBtn')" class="button">修改物流單號</button>
  </div>
  <div class="single" v-else-if="status === '2'">
    <button @click="buttonClick('moneyDetail')" class="button grey" >查看打款進度</button>
  </div>
  <div class="single" v-else-if="status === '3'">
    <button class="button grey" @click="buttonClick('cancelBtn')">取消寄賣</button>
  </div>
  <div class="single" v-else-if="status === '4'">
    <button class="button grey" @click="buttonClick('reSell')">從新上架</button>
  </div>
  <!-- 同時顯示兩個按鈕 -->
  <div class="double" v-else>
    <button class="button grey" @click="buttonClick('cancelBtn')">自行寄件</button>
    <button class="button grey" v-if="status === '5'" @click="buttonClick('cancelForOnlyTip')">取消寄賣</button>
    <button class="button grey" v-else @click="buttonClick('cancelBtn')">召回商品</button>
  </div>
</div>
複製代碼

而若是按照模板歸模板,邏輯歸 js的規範,那麼你模板上只須要清清楚楚地把佈局寫好就好了,而那些邏輯直接放到 js上,哪怕你仍是依舊寫 if..elsejs代碼的 if...else也比jsx這些 jshtml混合的 if..else好看點,更況且,用 js來寫,徹底能夠優化邏輯,什麼 map查詢、什麼策略模式稍微用上一用,就會好看不少json

<div class="bottom_btn">
  <button class="btn1" @click="btnClick(btnData.leftEventName)">{{btnData.leftTxt}}</button>
  <button v-if="rightBtnTxt" @click="btnClick(btnData.rightEventName)" class="btn2">{{btnData.rightTxt}}</button>
</div>
複製代碼

上述模板就清晰不少了,我一眼就知道這裏放了一個或者兩個按鈕,至於按鈕的點擊事件是什麼、按鈕的文案是什麼,何時顯式什麼按鈕,顯式一個仍是兩個按鈕等,那就是 js的事情了,模板只負責展現後端

updateBtn () {
  const statusSignleBtnMap = {
    1: {
      txt: '修改物流單號',
      eventName: 'sendBySelfBtn'
    },
    2: {
      txt: '查看打款進度',
      eventName: 'moneyDetail'
    },
    3: {
      txt: '取消寄賣',
      eventName: 'cancelBtn'
    },
    4: {
      txt: '從新上架',
      eventName: 'reSell'
    },
  }
  const statusDoubleBtnMap = {
    5: {
      txt: '取消寄賣',
      eventName: 'cancelForOnlyTip'
    }
  }
  let btnData = statusBtnMap[status]
  if (btnData) {
    this.btnData = {
      leftTxt: btnData.txt,
      leftEventName: btnData.eventName
    }
  } else {
    this.btnData = {
      leftTxt: 'cancelBtn',
      leftEventName: '自行寄件',
      rightTxt: statusDoubleBtnMap[status].txt,
      rightEentName: statusDoubleBtnMap[status].eventName
    }
  }
}
btnClick (eventName) {
  // 這裏再根據 eventName 進行相應的按鈕點擊處理
}
複製代碼

須要展現的按鈕信息所有在 btnData上,想增長或刪除或修改按鈕,只須要修改 statusSignleBtnMapstatusDoubleBtnMap 便可,不管邏輯有多少,無非都是修改對象屬性的事情,很是清晰

分支語句優化

業務代碼少不了 if...else,甚至有些功能代碼都是放在 if...else的代碼塊中,寫好分支語句,將能顯著地提高代碼的可閱讀性

不要在判斷分支裏寫太長的邏輯

一屏下來看不到分支語句的結束括號,再繼續往下看很容易忘記當前是什麼 case了,還要往上翻一翻才清楚,從代碼編寫的角度看,也不該該在一個代碼塊中寫太多的邏輯,若是發現就是須要寫很長的邏輯,那麼就把這些邏輯放到另一個函數中

提早結束分支

若是發現分支可以提早結束,那麼馬上結束,在某些時候這能大大減小 if...else的嵌套層數

function fn1(a, b) {
  let c = null
  if (a > b) {
    c = a + b
  } else {
    if (a === 0) {
      c = b + new Date()
    } else if (a === 1) {
      c = a / b
    } else {
      c = a * b
    }
  }
  return c
}
複製代碼

可優化成:

function fn2(a, b) {
  if (a > b) {
    // 提早結束
    return a + b
  }
  let c = null
  if (a === 0) {
    c = b + new Date()
  } else if (a === 1) {
    c = a / b
  } else {
    c = a * b
  }
  return c
}
複製代碼

能夠看到,fn1fn2少了一層 if...else的嵌套,這只是簡單的示例代碼,通常狀況下業務代碼裏的 if...else可不只只有兩層,時機合理的狀況下,把五六層的分支嵌套打平成一層,那舒爽……

關鍵頁面從源頭作好組件拆分

網站的一些關鍵頁面,例如首頁、詳情頁,在構建之初就要大概規劃好佈局,劃分好子組件,不要說什麼提早優化並不必定好之類的,由於這些關鍵頁面確定是須要長期存在而且持續迭代的,這是毫無疑問的事情,不存在你提早優化好告終果後面根本用不上的狀況

若是沒有規劃好,後面再想優化那就有點考驗人性了,由於人都是有惰性的,再加上你們都忙得很,需求都寫不完,誰願意去優化代碼,再加上這份代碼早不知被多少人改過了,改別人的代碼都跟吃屎似的,優化別人的代碼簡直要昇天,誰願意幹?當聚沙成塔終於不堪忍受想要優化的時候,可能你須要付出數倍的精力,再加上能夠接受可能弄出 bug來的勇氣,才能作好優化,何苦來哉?

固然,子組件的劃分也不是說越細越好,要保持一個平衡,這個平衡怎麼把握呢?按照個人經驗來看,這個組件總體模板加 js方法最起碼要有100行以上,若是低於這個行數,考慮一下是否是劃分的太細了,也不要不超過 400行,超過了就要考慮一下是否是能再拆一下,這是大部分狀況下的作法,也不是絕對的,仍是要根據實際狀況來權衡

例如,對於詳情頁來講,通常均可以分爲 頭部banner + 商品標題描述區sku選擇區評價區詳情描述+圖片區底部按鈕區,這些區域基本上都是能夠相互獨立存在的,或者說耦合性很低,能夠當作獨立的組件進行維護管理

下降組件耦合性

組件劃分的一大原則是,儘可能下降耦合性

首頁、詳情頁這種關鍵頁面,須要的數據和方法絕對不會少的,這麼多的數據和方法混雜在同一個地方,維護起來絕對不輕鬆,每修改或調用某個數據和方法,都要先看下到底有哪些數據和方法依賴當前的數據和方法,只有在確保當前修改不會影響到其餘數據和方法的時候,才能真正開始修改的動做,這是一件無心義且耗時耗力的重複性事情

而且能夠預見的是,誰都不敢輕易刪除某個數據或方法,儘管這個數據和方法可能永遠用不到了,由於數據和方法太多了,想要理清這些數據之間的關係耗時耗力,不如就扔在那裏,不改不會出錯,改了可能就弄出 bug了,久而久之,頁面上的無效代碼也就愈來愈多,數據和方法也越來也多,邏輯愈來愈複雜,到了最後,巴不得將其推翻重來,只惋惜,你可能仍是不敢,由於你連刪除一個數據或方法都不敢,怎麼可能敢重構?

而若是能在一開始就劃分好組件,將數據和方法打散到獨立的組件中,那麼每一個子組件內部的數據和方法確定就沒那麼多了,增刪改查前都只須要掃一眼便可清楚全部數據和方法間的聯繫,更新起來也就更駕輕就熟

固然,組件的劃分也不是越細越好,要有一個權衡

若是你發現 A組件的不少數據和方法都與B組件重合,對 A組件的修改很大機率會致使 B組件的更新,那麼考慮將 A組件和 B組件合併成一個組件,組件的一大目標是提高複用性,兩個耦合嚴重的組件顯然不容易複用,還多出一些交互的邏輯

組件的絕大部分數據和方法都應該能在內部維護,這樣,之後進行迭代的時候,須要更新哪一個區域的內容就到對應的組件裏去修改,不影響其餘組件的邏輯,少部分公共的數據和方法應該放在父組件中進行維護,子組件直接經過 props取父組件中的數據便可

若是不是特別須要的話,建議不要使用 vuex,這個東西用起來確實挺爽,但追蹤起來確定沒有 props那麼直觀,vuex的使用建議也是如此,只有當你感受須要用的時候才用,不然就不必用,徒增繁瑣,而據個人經驗來看,絕大多數的項目都並不須要這個東西

用好 mixin

當你須要在一些關鍵頁面上例如首頁、詳情頁加入一個存活時間很短極可能只用一次不再用的運營活動時,你會怎麼作?直接開擼,該加數據加數據該加方法加方法,通常都是這麼作的

但問題在於活動結束後,你怎麼辦?

最好的作法是直接移除全部活動相關邏輯,還原到這個頁面原本的面目,可是一般來講很難還原了,由於你當初已經將運營活動相關邏輯與頁面原本的主體邏輯代碼混雜在了一塊兒,就像是把一把沙子混進一堆屎山裏,確實能從新將全部的沙子所有挑出,但很顯然耗時耗力,更況且,大多數人根本不會移除失效的代碼,而是直接在代碼中加入一個邏輯上的開關,關掉這個開關,相關代碼就再也不運行或者即時是運行了也不會對主體邏輯產生任何影響

因而,隨着運營活動的屢次迭代,開關越加越多,失效的代碼也愈來愈多

屎山中混入了愈來愈多的沙子,不只難吃還硌牙,How Dare You!

個人作法是,若是活動相關邏輯與主體代碼的耦合性較低,那麼考慮獨立成一個組件,但大多數狀況下,運營活動的邏輯都會與主體代碼息息相關,耦合性很高,這個時候最好將活動相關邏輯儘可能所有獨立到 一個 mixin 中,儘可能減小對主文件數據的修改,最好經過儘可能少的接口來進行 mixin與主文件的交互,保證即便 mixin中的方法報錯了,或者直接移除這個 mixin,也不會對主文件形成實質性的影響

頁面上添加的運營活動數量越多,越能體現這種劃分的好處,主體文件只關心主體邏輯,各個活動邏輯都有各自對應的 mixin文件,你們各司其職,將因添加了愈來愈多運營活動頁面的複雜度從指數級將至常數級

還有一個技巧是,若是須要在主文件中修改 mixin的數據或調用 mixin的方法,那麼建議這個數據或方法具有清晰的 mixin歸屬,好比給這個 mixin中的數據命名爲 mixinSomeData 或方法命名爲 mixinSomeMethod,若是不止有一個 mixin,還能夠單獨標識每一個 mixin,好比 mixinASomeDatamixinBSomeData,這樣在主文件中使用這些數據或方法時,一眼就知道這是mixin而且是哪一個 mixin的數據或方法

作好了這些工做後,那麼後續要下掉這個運營活動就很簡單了,直接刪掉主文件對這個運營活動對應的 mixin文件的引用,刪掉主文件中全部引用 mixin中數據和方法的地方便可

關於這個 mixin,我還想多說一句,既然用了 mixin,那麼就要儘可能減小 mixin與主體代碼之間的交互,不然耦合得太嚴重,理解一段邏輯須要在兩個文件中來回對比着看,還不如直接寫在一塊兒,我看過將一個頁面拆分出兩個 mixin的操做,這兩個 mixin以及主體邏輯之間的數據交互那叫一個千絲萬縷,一段正常的邏輯非要在三個文件中穿梭遊走,看得我涕泗橫流

儘可能使用純函數

純函數是爲了保證相同的輸入能獲得相同的輸出,在調用這個函數的時候,不須要考慮更高做用域的變量會不會對函數的運行形成什麼影響,主要目的也是爲了解耦

固然,在不少狀況下,除非是通用的工具方法,不然方法的調用或多或少都會與全局 data相關,那麼這種狀況下咱們能作的就是進來減小不相關的高做用域變量參與運算

充分利用變量

定義了一個變量後,那麼就要充分發揮這個變量的做用,無關緊要的變量爲何要定義出來?

能用一個數據或一個方法解決的問題就不要用兩個

我常常在項目中看到,明明用一個變量或方法就能解決的問題,卻用了好幾個變量或方法才解決,不只增長了代碼量,維護起來也頗費心力

好比,若是當前的訂單流轉狀態是 status: 4,那麼就顯示一個彈窗,不然就不顯示,而這個 status是從接口中獲取的,那麼彈窗組件的顯示邏輯徹底能夠用 v-if="status === 4"來完成,不必再從新申請一個專門用來控制彈窗顯示的變量 this.modalShow(若是這個變量還有其餘的做用那另說),由於在其餘人接手維護的時候,這我的想要弄清彈窗到底何時顯示,就必須先找到 modalShow,再從 modalShow找到 status,平白增添了一個環節

賦予變量特定的能力

先後端分離的項目,頁面上的主要數據基本上都來自於一個主要的接口,根據接口返回的數據進行頁面的數據顯示和渲染,好比後端接口返回的數據格式:

{
  "code": "0",
  "data": {
    "title": "IPhone XS Max 全網通 95xin",
    "price": "6200",
    "userName": "小王",
    "status": 4
  }
}
複製代碼

常常看到的一種作法是,接口返回的數據中有多少數據,就在本地申請多少個變量來承載:

this.title = res.title
this.price = res.price
this.userName = res.userName
this.status = res.status
// ...
複製代碼

通常來講,一個經常使用的頁面,頁面上所須要的數據可能有十個以上,那麼按照這種邏輯就須要申請十個以上的本地變量,數據回來以後啥也沒幹,直接先給十個本地變量賦個值,因而浩浩蕩蕩一長串的賦值語句就先佔了大半屏

我是不太推崇這種作法的

第一是這種寫法太繁瑣,要首先定義這十來個變量,而後再對它們一一賦值,若是後續維護的時候,接口數據結構改變,那麼變量的定義和賦值也都要跟着變,而這徹底都是不必的事情

第二從代碼維護的角度看,這也不是一個好習慣,我看到 status這個變量,我首先要弄明白這個變量從何而來,而後還要弄清楚是否在什麼位置修改過這個變量,status的功能並不明確

個人習慣是直接用一個變量,好比 baseData來承接這些數據: this.baseData = res.data,而後要使用其中某項數據的時候,好比 status,那麼直接 baseData.status便可

變量只是用於指向數據的一個指針,對於計算機來講,只要數據明確,那麼指向數據的變量是什麼都可有可無,可是對於維護代碼的開發者來講,變量不只要承載數據,最好還要有其特定的含義,好比給變量起一個自解釋的變量名,除此以外,其實還能夠再進一步,額外賦予變量名特定的含義

我賦予了 baseData這個變量一個清晰的能力,每一個頁面都會有一個 baseData,只要看到這個變量,那麼毫無疑問,這個變量中存儲的都是接口返回的頁面主體數據,而不是計算出來或者自定義的什麼數據,這個數據在被第一次賦值以後就不再會改變,我只須要 baseData這一個變量就能夠承接住全部的接口數據,哪怕後續接口數據結構改變了我也不須要跟着從新定義和從新賦值本地數據

固然,這只是針對那些獲取到了就再也不改變的接口數據,若是某個接口數據後續還須要屢次再加工,或者有特定的做用和含義等,那最好仍是單獨定義一個變量進行承載,這也是一個權衡

非正常邏輯和主要變量、方法要寫好註釋

不少人都不喜歡寫註釋,我剛畢業的時候也不喜歡寫,以爲是在浪費時間,可是我早已經改變了想法,必要的註釋是必不可少的

固然,我不是鼓勵濫寫註釋,每一行代碼都對應一行註釋,那就太多了,又不是寫小說,另外,註釋應當是解釋型的而不是描述型的,好比,下述註釋就是描述型的,並且是不必的註釋,簡單代碼的註釋只會擾亂視線:

// 將 b 與 c 的和賦值給 a
const a = b + c
複製代碼

這行註釋就是脫褲子放屁,畫蛇添足,由於是個會寫代碼的人一眼都能看出來這行代碼是幹啥的

好的解釋型註釋示例:

// -1 是爲了抹平可能存在的 1px 的距離誤差
this.isfilterFixed = scroll2Top() >= (filterDomTop - 1)
複製代碼

這個註釋就很好了,由於這個 -1出現得很突兀,沒有經驗的人一眼看上去會以爲莫名其妙,爲何最後要 -1呢,不 -1邏輯上也沒問題啊,因而代碼上面的註釋就恰到其時地解決了這個困惑

這裏的 -1 就是所謂的非正常邏輯

一個長期迭代的項目中,確定存在爲了實現某個功能而進行的某種妥協或者 hack,結果必然致使出現非正常邏輯的代碼,並且數量確定不會少,給這些非正常邏輯加上註釋,避免了後續接手人的困惑之旅,更避免了無心間可能觸碰到的 bug

另外,一些頁面上經常使用的數據和方法最好也要寫好註釋,好比對於一個 vue組件來講,我如今的習慣是,會給每個 data、每個 methods寫好註釋:

怎麼樣?看着是否是很繁瑣?

這種寫法實際上是當初帶個人一個組長要求這麼作的,我一開始也很反感,老子寫的代碼自解釋,爲何要寫這麼多註釋?可是當我忍住不適寫了一段時間後,我卻愈來愈欣賞這種寫法了

首先,一個組件的 datamethods確定不會太多的(只要你知道怎麼正確的劃分組件),因此其實就算是爲每個 data、每個 methods寫註釋,也沒多少,但帶來的收益卻很可觀,固然,這是潛在的後續收益,不是能馬上體現出來的收益,相似於前人栽樹後人乘涼吧

我在儘可能確保變量名自解釋的前提下,又給每一個變量寫好了註釋,進一步明確變量的做用,不只是後續接手人,哪怕是我本身寫代碼,當看到一個變量的時候,根本你不用多想,直接根據編輯器的提示功能就知道這個變量是幹啥的了,若是有什麼須要注意的地方也都清清楚楚,根本不存在須要停下來弄清楚變量做用的事情,寫起代碼來行雲流水,本身寫得爽,接手的人看起來也爽

固然,你要非要說本身代碼自解釋我也沒辦法,只能讓你去看看 Vue或者 React的源代碼,爲何 VueReact中存在那麼多註釋呢,並且有的還一大段一大段的註釋,難道你比 VueReact的做者還厲害?他們都不懂代碼自解釋?不如你去教教他們?

React源碼一角

選擇器名,最好帶特殊標記

最起碼從目前來看,前端正處於並將長期處於要寫頁面的階段,當你某天發現頁面上某個數據或者樣式顯示的有點問題,因而你在 chrome上打開這個頁面,選取元素的選擇器名,好比 title,而後項目代碼中全局搜索,赫然發現出現了幾十個 title,因而你不得不進一步定位到對應的頁面組件,在這個頁面組件上搜,結果你發現這個頁面組件又劃分爲好幾個子組件,而後仍是有十幾個 title,因而你不得不繼續定位到準確的子組件,繼續搜,結果發現就算是在這一個子組件仍是有幾個 title,沒辦法了,只能肉眼篩選了

本應該一步到位的操做,硬生生被截斷成了好幾步,不只耗時耗力,內心還會很不爽

因此,最好避免 titleboxbottom 這種太過通泛的選擇名,考慮 prod-titleuser-boxbtn-bottom這種更具體的進行替代,固然,也不是讓你把類名寫得越長越精確越好,那也是不必的,只要你多寫一個限定字母,其實就能大幅度減小整個項目中的同名類名,這主要是爲了方便搜索,而不是爲了加長類名而加長,因此你類名寫得再長,但若是你用這種形式也是不可取的:

.page {
  &-user {
    &-box {
      &-name {
      }
    }
  }
}
複製代碼

那麼最後一層 name的類名實際上是 page-user-box-name,看起來是很精確也很長,可是這沒什麼卵用啊,我在項目代碼全局根本搜不到 page-user-box-name 這個東西,只能搜到 pageuserboxname這些分割開的單詞,而毫無疑問的,這些通泛的字母,全局會有不少

對於這種子類名與父類名高度耦合的寫法,反正我是深惡痛絕的,不只搜起來難搜,就是肉眼去找也很難找,比直接將通泛單詞當成類名的作法還可惡,固然,寫的人是很爽了

總結

對於三年及如下的技術人員來講,最重要的事情其實仍是多寫代碼,而且在寫的同時多思考,一樣的一個需求我上次是怎樣實現的,出現了什麼問題,那麼此次有沒有更好的解決方法避免這個問題,先思然後行方能有所進步,如果一樣一個需求,你如今的實現方案和一年前同樣,那麼這期間你寫再多的代碼也都是堆砌罷了

我見過太多工做三五年而工做經驗只有一年的人,前期不趁着剛畢業精力充沛的狀況下夯實基礎,到了職場下半場你拿什麼和別人拼?

相關文章
相關標籤/搜索