Vue開發(fā)的團(tuán)隊(duì)代碼規(guī)范

規(guī)范與每個(gè)團(tuán)隊(duì)和個(gè)人都是息息相關(guān)的,因?yàn)槠溆绊懙牟恢皇侵皇谴a的維護(hù)和理解成本,嚴(yán)重的時(shí)候是會(huì)影響成員開發(fā)的心情。

一個(gè)團(tuán)隊(duì)的編碼規(guī)范、git規(guī)范等,并沒有絕對(duì)的最優(yōu)解,心里要清楚明白沒有銀彈,規(guī)范是為了讓團(tuán)隊(duì)統(tǒng)一,提高代碼閱讀性、降低代碼維護(hù)成本等,

本文是記錄一些在項(xiàng)目code review中常見的規(guī)范,僅供參考。

image

JS 部 分

和渲染無關(guān)的數(shù)據(jù)

vue中data的數(shù)據(jù)默認(rèn)便會(huì)進(jìn)行雙向數(shù)據(jù)綁定,若是將大量的和渲染無關(guān)的數(shù)據(jù)直接放置在data中,將會(huì)浪費(fèi)雙向數(shù)據(jù)綁定時(shí)所消耗的性能,將這些和渲染無關(guān)的數(shù)據(jù)進(jìn)行抽離并配合Object.freeze進(jìn)行處理。

table中columns數(shù)據(jù)可以單獨(dú)提取一個(gè)外部js文件作為配置文件,也可以在當(dāng)前.vue文件中定義一個(gè)常量定義columns數(shù)據(jù),

因?yàn)闊o論如何都是固定且不會(huì)修改的數(shù)據(jù),應(yīng)該使用Object.freeze進(jìn)行包裹,既可以提高性能還可以將固定的數(shù)據(jù)抽離,一些下拉框前端固定的數(shù)據(jù)也建議此操作。

const columnList = Object.freeze([
  { title: '姓名', key: 'name', align: 'center' },
  { title: '性別', key: 'gender', align: 'center' }
])

需要注意的是 Object.freeze() 凍結(jié)的是值,這時(shí)仍然可以將變量的引用替換掉,還有確保數(shù)據(jù)不會(huì)變才可以使用這個(gè)語法,

如果要對(duì)數(shù)據(jù)進(jìn)行修改和交互,就不適合使用凍結(jié)了。

Modal框的控制

一個(gè)頁面種通常會(huì)存在很多個(gè)不同功能的彈框,若是每一個(gè)彈框都設(shè)置一個(gè)對(duì)應(yīng)的變量來控制其顯示,則會(huì)導(dǎo)致變量數(shù)量比較冗余和命名困難,

可以使用一個(gè)變量來控制同一頁面中的所有Modal彈框的展示。

比如某個(gè)頁面中存在三個(gè)Modal彈框

// bad
// 每一個(gè)數(shù)據(jù)控制對(duì)應(yīng)的Modal展示與隱藏
new Vue({
    data() {
        return {
            modal1: false,
            modal2: false,
            modal3: false,
        }
    }
})

// good
// 當(dāng)modalType為對(duì)應(yīng)的值時(shí) 展示其對(duì)應(yīng)的彈框
new Vue({
    data() {
        return {
            modalType: '' // modalType值為 modal1,modal2,modal3
        }
    }
})

debounce使用

例如遠(yuǎn)程搜索時(shí)需要通過接口動(dòng)態(tài)的獲取數(shù)據(jù),若是每次用戶輸入都接口請(qǐng)求,是浪費(fèi)帶寬和性能的。

當(dāng)一個(gè)按鈕多次點(diǎn)擊時(shí)會(huì)導(dǎo)致多次觸發(fā)事件,可以結(jié)合場(chǎng)景是否立即執(zhí)行immediate

<Select :remote-method="remoteMethod">
    <Option v-for="item in temoteList" :value="item.value" :key="item.id">{{item.label}}</Option>
</Select>
import {debounce} from 'lodash'

methods:{
    remoteMethod:debounce(function (query) {
        // to do ...
       // this 的指向沒有問題
    }, 200),
}

圖片

功能的開發(fā)過程中,圖片的處理往往是比較容易被忽略的環(huán)節(jié),也會(huì)在一定程度影響開發(fā)的效率和頁面的性能:

圖片壓縮問題,除非特別要求圖片必須高質(zhì)量的顯示,否則都應(yīng)該進(jìn)行對(duì)應(yīng)的壓縮處理;

不同業(yè)務(wù)場(chǎng)景進(jìn)行圖片格式的選型:

JPG 適用于呈現(xiàn)色彩豐富的圖片,JPG 圖片經(jīng)常作為大的背景圖、輪播圖或 Banner 圖出現(xiàn)等;

Logo、顏色簡單且對(duì)比強(qiáng)烈的圖片或背景、需要透明度等;

將常用且變動(dòng)頻率很低的小圖片進(jìn)行合并成雪碧圖,對(duì)于變動(dòng)比較頻繁和小于6KB的圖片進(jìn)行base64處理;

根據(jù)項(xiàng)目圖片數(shù)量和項(xiàng)目的用戶機(jī)型分布等,考慮采取webp進(jìn)行圖片的處理。

路由組件傳參

在組件中使用 $route 會(huì)使之與其對(duì)應(yīng)路由形成高度耦合,從而使組件只能在某些特定的 URL 上使用,限制了其靈活性。

使用 props 將組件和路由解耦:

取代與 $route 的耦合

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})

通過 props 解耦

這樣你便可以在任何地方使用該組件,使得該組件更易于重用和測(cè)試。

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User, props: true },

    // 對(duì)于包含命名視圖的路由,你必須分別為每個(gè)命名視圖添加 `props` 選項(xiàng):
    {
      path: '/user/:id',
      components: { default: User, sidebar: Sidebar },
      props: { default: true, sidebar: false }
    }
  ]
})

參考:路由組件傳參

https://router.vuejs.org/zh/guide/essentials/passing-props.html

Vue生命周期

在父子組件中,掌握父子組件對(duì)應(yīng)的生命周期鉤子加載順序可以讓開發(fā)者在更合適的時(shí)候做適合的事情。

父組件

<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>

加載時(shí)父子組件的加載順序

home beforeCreate --> home created --> home beforeMount --> list created --> list beforeMount --> list mounted

銷毀時(shí)父子組件的銷毀順序

home beforeDestroy --> list beforeDestroy --> list destroyed --> home destroyed

實(shí)際開發(fā)過程中會(huì)遇到當(dāng)子組件某個(gè)生命周期完成之后通知父組件,然后在父組件做對(duì)應(yīng)的處理。

emit up

// 子組件在對(duì)應(yīng)的鉤子中發(fā)布事件
created(){
  this.$emit('done')
}

// 父組件訂閱其方發(fā)
<list @done="childDone">

hook

通過@hook監(jiān)聽子組件的生命周期

<list @hook:mounted="listMounted" />

Select優(yōu)化

下拉框遍歷時(shí),需要注意options標(biāo)簽保持同一行,若是存在換行,會(huì)導(dǎo)致選中時(shí)的值存在多余的空白。

<!-- 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>

data數(shù)據(jù)層級(jí)

data數(shù)據(jù)具有數(shù)據(jù)層級(jí)結(jié)構(gòu),切勿過度扁平化或者嵌套層級(jí)過深,

若是過度扁平化會(huì)導(dǎo)致數(shù)據(jù)命名空間沖突,參數(shù)傳遞和處理,

若是層級(jí)嵌套過深也會(huì)導(dǎo)致vue數(shù)據(jù)劫持的時(shí)候遞歸層級(jí)過深,

若是嵌套層級(jí)喪心病狂那種的,小心遞歸爆棧的問題。

而且層級(jí)過深會(huì)導(dǎo)致數(shù)據(jù)操作和處理不便,獲取數(shù)據(jù)做容錯(cuò)處理也比較繁瑣。一般層級(jí)保持2-3層最好。

若是只有一層數(shù)據(jù),過于扁平

{
    name: '',
    age: '',
    gender: ''
}

導(dǎo)致處理不方便

// 作為接口參數(shù)傳遞
ajax({
 this.name, this.age, this.gender
})

// 接口獲取數(shù)據(jù),批量處理
ajax().then(res => {
 const {name, age, gender} = res.data
    this.name = name
    this.age = age
    this.gender = gender
})

適當(dāng)?shù)膶蛹?jí)結(jié)構(gòu)不僅增加代碼的維護(hù)和閱讀性,還可以增加操作和處理的便捷性

{
    person: { // 個(gè)人信息
        name: '',
        age: '',
        gender: ''
    }
}

可以針對(duì)person進(jìn)行操作

// 作為接口參數(shù)傳遞
ajax(this.person)

// 接口獲取數(shù)據(jù),批量處理
ajax().then(res => {
 const {name, age, gender} = res.data
    this.$set(this, 'person', {name, age, gender})
})

策略模式

策略模式的使用,避免過多的if else判斷,也可以替代簡單邏輯的switch

const formatDemandItemType = (value) => {
    switch (value) {
        case 1:
            return '基礎(chǔ)'
        case 2:
            return '高級(jí)'
        case 3:
            return 'VIP'
    }
}

// 策略模式
const formatDemandItemType2 = (value) => {
    const obj = {
        1: '基礎(chǔ)',
        2: '高級(jí)',
        3: 'VIP',
    }
    
    return obj[value]
}

解構(gòu)

解構(gòu)賦值以及默認(rèn)值,當(dāng)解構(gòu)的數(shù)量小于多少時(shí)適合直接解構(gòu)并賦值默認(rèn)值,數(shù)據(jù)是否進(jìn)行相關(guān)的聚合處理

const {
  naem = '',
  age = 10,
  gender = 'man'
} = res.data

// bad
this.name = name
this.age = age
this.gender = gender

// good
this.person = {
  naem,
  age,
  gender
}

職責(zé)單一

任何時(shí)候盡量是的一個(gè)函數(shù)就做一件事情,而不是將各種邏輯全部耦合在一起,提高單個(gè)函數(shù)的復(fù)用性和可讀性。

每個(gè)頁面都會(huì)在加載完成時(shí)進(jìn)行數(shù)據(jù)的請(qǐng)求并展示到頁面

created() {
  this.init();
},
methods: {
  // 將全部的請(qǐng)求行為聚合在init函數(shù)中
  // 將每個(gè)請(qǐng)求單獨(dú)拆分
  init() {
    this.getList1()
    this.getList2()
  },
  getList1() {
    // to do ...
  },
  getList2() {
    // to do ...
  }
}

v-bind

在日常的開發(fā)過程中, 提取和封裝組件是一件很常規(guī)的操作,但是當(dāng)組件需要的參數(shù)非常多時(shí),會(huì)導(dǎo)致傳遞一堆的prop,不僅書寫上面比較繁瑣,對(duì)代碼的維護(hù)和閱讀不是一件有利的事情

例如組件test-demo需要一堆props傳遞

使用時(shí)

<template>
  <test-demo 
    :data1="data1"
    :data2="data2"
    :data3="data3"
    ...假設(shè)還有一堆
  />
</template>

test-demo中需要接收處理

{
  props: ['data1', 'data2', 'data3', ...]
}
// or
props: {
  modalVisible: {
    // 控制展示modal
    type: Boolean,
    default: false
  },
  data1: {
    type: String,
    default: '1'
  },
  data2: {
    type: String,
    default: '2'
  },
  data3: {
    type: String,
    default: '3'
  }
}

建議將子組件需要的數(shù)據(jù)收集起來,集中在一個(gè)對(duì)象中,使用v-bind傳遞將這個(gè)對(duì)象傳遞,子組件的使用和普通的props一樣

<template>
  <test-demo 
    v-bind="obj"
  />
</ template>
<script>
export default {
data () {
return {
obj: { // 將需要傳遞給子組件的數(shù)據(jù)收集
data1: '1',
data2: '2',
data3: '3'
}
}
}
}
</script>
image

HTML部分

html書寫

編寫template模板時(shí),屬性過多時(shí),是否換行

<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>

實(shí)體使用

html中展示一些如<,>,&等字符時(shí),使用字符實(shí)體代替

<!-- bad -->
<div>
  > 1 & < 12
</div>
  
<!-- bad -->
<div>
  &gt; 1 &amp; &lt; 12
</div>
image

CSS部分

樣式穿透

在開發(fā)中修改第三方組件樣式是很常見,但由于 scoped 屬性的樣式隔離,可能需要去除 scoped 或是另起一個(gè) style 。

這些做法都會(huì)帶來副作用(組件樣式污染、不夠優(yōu)雅),樣式穿透在css預(yù)處理器中使用才生效。

less使用 /deep/

<style scoped lang="less">
.content /deep/ .el-button {
 height: 60px;
}
</style>

scss使用 ::v-deep

<style scoped lang="scss">
.content ::v-deep .el-button {
  height: 60px;
}
</style>

stylus使用 >>>

<style scoped ang="stylus">
外層 >>> .custon-components{
  height: 60px;
}
</style>

空格

適當(dāng)?shù)目崭窨梢蕴嵘a的閱讀體驗(yàn),顯得更為優(yōu)雅和美觀。

選擇器后、屬性值

.custom-style { // 選擇器和{ 之間空格
  margin: 0; // 屬性值前
  transform: scale(1.5, 2.2); // 逗號(hào)之后增加空格
}

換行

和html類型,當(dāng)某行的屬性很多,適當(dāng)?shù)膿Q行可以提高閱讀和美觀

.custom-style{
  // 可以在一次聲明中定義一個(gè)或多個(gè)屬性
  background: background-clip
    background-color
    background-image
    background-origin
    background-position
    background-repeat
    background-size;
}

當(dāng)一個(gè)規(guī)則包含多個(gè)選擇器時(shí),每個(gè)選擇器聲明必須獨(dú)占一行,過長導(dǎo)致需要橫向滾動(dòng)閱讀剩余的內(nèi)容,應(yīng)該盡量使得閱讀順序縱向化。

.custom .header .title,
.other .header .title {
  color: #f0f;
}

嵌套層級(jí)

瀏覽器在解析css時(shí),是按照從右到左遞歸匹配的,過深的層級(jí)嵌套不僅影響性能,而且還會(huì)導(dǎo)致樣式閱讀性和代碼維護(hù)性降低,一般層架控制在5層之內(nèi)。

雙引號(hào)

屬性選擇器中的值必須用雙引號(hào)包圍,不允許使用單引號(hào),也不允許不使用引號(hào),html的屬性值也是推薦使用雙引號(hào),js中使用單引號(hào)。

.custom-style{
font-family: "PingFang SC", "STHeitiSC-Light";
}

屬性順序

同一 規(guī)則下的屬性在書寫時(shí),應(yīng)按功能進(jìn)行分組。

并以 Formatting Model(布局方式、位置) > Box Model(尺寸) > Typographic(文本相關(guān)) > Visual(視覺效果) 的順序書寫,以提高代碼的可讀性。

解釋:

Formatting Model 相關(guān)屬性包括:position / top / right / bottom / left / float / display / overflow 等;

Box Model 相關(guān)屬性包括:border / margin / padding / width / height 等;

Typographic 相關(guān)屬性包括:font / line-height / text-align / word-wrap 等;

Visual 相關(guān)屬性包括:background / color / transition / list-style 等。

另外,為增加可讀性,如果包含 content 屬性,應(yīng)放在屬性的最前面。

參 考

三年 Vue 前端開發(fā)的血與淚總結(jié)

https://gitbook.cn/books/5dd7de04c023a6766a83cfc4/index.html#-1

編碼規(guī)范作用

https://www.zhihu.com/search?type=content&q=編碼規(guī)范作用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡信或評(píng)論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容