一個優秀的Vue團隊代碼規范是什么樣子的?
規范與每個團隊和個人都是息息相關的,因為其影響的不只是只是代碼的維護和理解成本,嚴重的時候是會影響成員開發的心情
一個團隊的編碼規范、git規范等,并沒有絕對的最優解,心里要清楚明白沒有銀彈,規范是為了讓團隊統一,提高代碼閱讀性、降低代碼維護成本等,本文是記錄一些在項目code review中常見的規范,僅供參考。
一、JS部分
1. 和渲染無關的數據
vue中data的數據默認便會進行雙向數據綁定,若是將大量的和渲染無關的數據直接放置在data中,將會浪費雙向數據綁定時所消耗的性能,將這些和渲染無關的數據進行抽離并配合Object.freeze進行處理。
table中columns數據可以單獨提取一個外部js文件作為配置文件,也可以在當前.vue文件中定義一個常量定義columns數據,因為無論如何都是固定且不會修改的數據,應該使用Object.freeze進行包裹,既可以提高性能還可以將固定的數據抽離,一些下拉框前端固定的數據也建議此操作。
- const columnList = Object.freeze([
- { title: '姓名', key: 'name', align: 'center' },
- { title: '性別', key: 'gender', align: 'center' }
- ])
需要注意的是 Object.freeze() 凍結的是值,這時仍然可以將變量的引用替換掉,還有確保數據不會變才可以使用這個語法,如果要對數據進行修改和交互,就不適合使用凍結了。
2. Modal框的控制
一個頁面種通常會存在很多個不同功能的彈框,若是每一個彈框都設置一個對應的變量來控制其顯示,則會導致變量數量比較冗余和命名困難,可以使用一個變量來控制同一頁面中的所有Modal彈框的展示。
比如某個頁面中存在三個Modal彈框:
- // bad
- // 每一個數據控制對應的Modal展示與隱藏
- new Vue({
- data() {
- return {
- modal1: false,
- modal2: false,
- modal3: false,
- }
- }
- })
- // good
- // 當modalType為對應的值時 展示其對應的彈框
- new Vue({
- data() {
- return {
- modalType: '' // modalType值為 modal1,modal2,modal3
- }
- }
- })
3. debounce使用
例如遠程搜索時需要通過接口動態的獲取數據,若是每次用戶輸入都接口請求,是浪費帶寬和性能的。
當一個按鈕多次點擊時會導致多次觸發事件,可以結合場景是否立即執行immediate:
- <Select :remote-method="remoteMethod">
- <Option v-for="item in temoteList" :value="item.value" :key="item.id">{{item.label}}</Option>
- </Select>
圖片功能的開發過程中,圖片的處理往往是比較容易被忽略的環節,也會在一定程度影響開發的效率和頁面的性能:
(1) 圖片壓縮問題,除非特別要求圖片必須高質量的顯示,否則都應該進行對應的壓縮處理
(2) 不同業務場景進行圖片格式的選型:
- JPG 適用于呈現色彩豐富的圖片,JPG 圖片經常作為大的背景圖、輪播圖或 Banner 圖出現等
- Logo、顏色簡單且對比強烈的圖片或背景、需要透明度等
- 將常用且變動頻率很低的小圖片進行合并成雪碧圖,對于變動比較頻繁和小于6KB的圖片進行base64處理
- 根據項目圖片數量和項目的用戶機型分布等,考慮采取webp進行圖片的處理
4. 路由組件傳參
在組件中使用 $route 會使之與其對應路由形成高度耦合,從而使組件只能在某些特定的 URL 上使用,限制了其靈活性。
使用 props 將組件和路由解耦:
(1) 取代與 $route 的耦合
- const User = {
- template: '<div>User {{ $route.params.id }}</div>'
- }
- const router = new VueRouter({
- routes: [
- { path: '/user/:id', component: User }
- ]
- })
(2) 通過 props 解耦
這樣你便可以在任何地方使用該組件,使得該組件更易于重用和測試。
- const User = {
- props: ['id'],
- template: '<div>User {{ id }}</div>'
- }
- const router = new VueRouter({
- routes: [
- { path: '/user/:id', component: User, props: true },
- // 對于包含命名視圖的路由,你必須分別為每個命名視圖添加 `props` 選項:
- {
- path: '/user/:id',
- components: { default: User, sidebar: Sidebar },
- props: { default: true, sidebar: false }
- }
- ]
- })
參考:路由組件傳參
5. Vue生命周期
在父子組件中,掌握父子組件對應的生命周期鉤子加載順序可以讓開發者在更合適的時候做適合的事情父組件
- <template>
- <div>
- <h3>home</h3>
- <list @hook:mounted="listMounted" />
- </div>
- </template>
- <script>
- import List from './list'
- export default {
- name: "home",
- components: {
- List
- },
- methods: {
- listMounted(){
- console.log('------------ listMounted');
- }
- },
- beforeCreate() {
- console.log("home beforeCreate");
- },
- created() {
- console.log("home created");
- },
- beforeMount() {
- console.log("home beforeMount");
- },
- mounted() {
- console.log("home mounted");
- },
- beforeDestroy() {
- console.log("home beforeDestroy");
- },
- destroyed() {
- console.log("home destroyed");
- }
- }
- </script>
子組件:
- <template>
- <div>
- list
- </div>
- </template>
- <script>
- export default {
- naem: "list",
- beforeCreate() {
- console.log("list beforeCreate");
- },
- created() {
- console.log("list created");
- },
- beforeMount() {
- console.log("list beforeMount");
- },
- mounted() {
- console.log("list mounted");
- },
- beforeDestroy() {
- console.log("list beforeDestroy");
- },
- destroyed() {
- console.log("list destroyed");
- }
- }
- </script>
加載時父子組件的加載順序:
- home beforeCreate --> home created --> home beforeMount --> list created --> list beforeMount --> list mounted
銷毀時父子組件的銷毀順序:
- home beforeDestroy --> list beforeDestroy --> list destroyed --> home destroyed
實際開發過程中會遇到當子組件某個生命周期完成之后通知父組件,然后在父組件做對應的處理。
emit up:
- // 子組件在對應的鉤子中發布事件
- created(){
- this.$emit('done')
- }
- // 父組件訂閱其方發
- <list @done="childDone">
hook:
通過@hook監聽子組件的生命周期
- <list @hook:mounted="listMounted" />
6. Select優化
下拉框遍歷時,需要注意options標簽保持同一行,若是存在換行,會導致選中時的值存在多余的空白
- <!-- bad -->
- <Select :remote-method="remoteMethod">
- <Option v-for="item in temoteList" :value="item.value" :key="item.id">
- {{item.label}}
- </Option>
- </Select>
需要將Options和下拉框的值保持在同一行
- <!-- good -->
- <Select :remote-method="remoteMethod">
- <Option v-for="item in temoteList" :value="item.value" :key="item.id">{{item.label}}</Option>
- </Select>
7. data數據層級
data數據具有數據層級結構,切勿過度扁平化或者嵌套層級過深,若是過度扁平化會導致數據命名空間沖突,參數傳遞和處理,若是層級嵌套過深也會導致vue數據劫持的時候遞歸層級過深,若是嵌套層級喪心病狂那種的,小心遞歸爆棧的問題。而且層級過深會導致數據操作和處理不便,獲取數據做容錯處理也比較繁瑣。一般層級保持2-3層最好。
若是只有一層數據,過于扁平
- {
- name: '',
- age: '',
- gender: ''
- }
導致處理不方便
- // 作為接口參數傳遞
- ajax({
- this.name, this.age, this.gender
- })
- // 接口獲取數據,批量處理
- ajax().then(res => {
- const {name, age, gender} = res.data
- this.name = name
- this.age = age
- this.gender = gender
- })
適當的層級結構不僅增加代碼的維護和閱讀性,還可以增加操作和處理的便捷性
- {
- person: { // 個人信息
- name: '',
- age: '',
- gender: ''
- }
- }
可以針對person進行操作
- // 作為接口參數傳遞
- ajax(this.person)
- // 接口獲取數據,批量處理
- ajax().then(res => {
- const {name, age, gender} = res.data
- this.$set(this, 'person', {name, age, gender})
- })
8. 策略模式
策略模式的使用,避免過多的if else判斷,也可以替代簡單邏輯的switch
- const formatDemandItemType = (value) => {
- switch (value) {
- case 1:
- return '基礎'
- case 2:
- return '高級'
- case 3:
- return 'VIP'
- }
- }
- // 策略模式
- const formatDemandItemType2 = (value) => {
- const obj = {
- 1: '基礎',
- 2: '高級',
- 3: 'VIP',
- }
- return obj[value]
- }
解構解構賦值以及默認值,當解構的數量小于多少時適合直接解構并賦值默認值,數據是否進行相關的聚合處理
- const {
- naem = '',
- age = 10,
- gender = 'man'
- } = res.data
- // bad
- this.name = name
- this.age = age
- this.gender = gender
- // good
- this.person = {
- naem,
- age,
- gender
- }
9. 職責單一
任何時候盡量是的一個函數就做一件事情,而不是將各種邏輯全部耦合在一起,提高單個函數的復用性和可讀性
每個頁面都會在加載完成時進行數據的請求并展示到頁面
- created() {
- this.init();
- },
- methods: {
- // 將全部的請求行為聚合在init函數中
- // 將每個請求單獨拆分
- init() {
- this.getList1()
- this.getList2()
- },
- getList1() {
- // to do ...
- },
- getList2() {
- // to do ...
- }
- }
10. v-bind
二、HTML部分
1. html書寫
編寫template模板時,屬性過多時,是否換行
- <template>
- <!-- 不換行 -->
- <VueButton class="icon-button go-up" icon-left="keyboard_arrow_up" v-tooltip="$t('org.vue.components.folder-explorer.toolbar.tooltips.parent-folder')" @click="openParentFolder" />
- <!-- 換行 -->
- <VueButton
- class="icon-button go-up"
- icon-left="keyboard_arrow_up"
- v-tooltip="$t('org.vue.components.folder-explorer.toolbar.tooltips.parent-folder')"
- @click="openParentFolder"
- />
- </template>
2. 實體使用
html中展示一些如<,>,&等字符時,使用字符實體代替
- <!-- bad -->
- <div>
- > 1 & < 12
- </div>
- <!-- bad -->
- <div>
- > 1 & < 12
- </div>
三、CSS部分
1. 樣式穿透
在開發中修改第三方組件樣式是很常見,但由于 scoped 屬性的樣式隔離,可能需要去除 scoped 或是另起一個 style 。這些做法都會帶來副作用(組件樣式污染、不夠優雅),樣式穿透在css預處理器中使用才生效。
- less使用 /deep/
- <style scoped lang="less">
- .content /deep/ .el-button {
- height: 60px;
- }
- </style>
- <style scoped lang="scss">
- .content ::v-deep .el-button {
- height: 60px;
- }
- </style>
- <style scoped ang="stylus">
- 外層 >>> .custon-components{
- height: 60px;
- }
- </style>
2. 空格
適當的空格可以提升代碼的閱讀體驗,顯得更為優雅和美觀
選擇器后、屬性值:
- .custom-style { // 選擇器和{ 之間空格
- margin: 0; // 屬性值前
- transform: scale(1.5, 2.2); // 逗號之后增加空格
- }
3. 換行
和html類型,當某行的屬性很多,適當的換行可以提高閱讀和美觀
- .custom-style{
- // 可以在一次聲明中定義一個或多個屬性
- background: background-clip
- background-color
- background-image
- background-origin
- background-position
- background-repeat
- background-size;
- }
當一個規則包含多個選擇器時,每個選擇器聲明必須獨占一行,過長導致需要橫向滾動閱讀剩余的內容,應該盡量使得閱讀順序縱向化
- .custom .header .title,
- .other .header .title {
- color: #f0f;
- }
4. 嵌套層級
瀏覽器在解析css時,是按照從右到左遞歸匹配的,過深的層級嵌套不僅影響性能,而且還會導致樣式閱讀性和代碼維護性降低,一般層架控制在5層之內。
5. 雙引號
屬性選擇器中的值必須用雙引號包圍,不允許使用單引號,也不允許不使用引號,html的屬性值也是推薦使用雙引號,js中使用單引號:
- .custom-style{
- font-family: "PingFang SC", "STHeitiSC-Light";
- }
6. 屬性順序
同一 規則下的屬性在書寫時,應按功能進行分組。并以 Formatting Model(布局方式、位置) > Box Model(尺寸) > Typographic(文本相關) > Visual(視覺效果) 的順序書寫,以提高代碼的可讀性。
解釋:
- Formatting Model 相關屬性包括:position / top / right / bottom / left / float / display / overflow 等
- Box Model 相關屬性包括:border / margin / padding / width / height 等
- Typographic 相關屬性包括:font / line-height / text-align / word-wrap 等
- Visual 相關屬性包括:background / color / transition / list-style 等
另外,為增加可讀性,如果包含 content 屬性,應放在屬性的最前面。