Vue.js 元件編碼規範

蔡俊鋒發表於2017-10-26

目標

本規範提供了一種統一的編碼規範來編寫 Vue.js 程式碼。這使得程式碼具有如下的特性:

  • 其它開發者或是團隊成員更容易閱讀和理解。
  • IDEs 更容易理解程式碼,從而提供高亮、格式化等輔助功能
  • 更容易使用現有的工具
  • 更容易實現快取以及程式碼包的分拆

本指南為 De Voorhoede 參考 RiotJS 編碼規範 而寫。

目錄

  • 將 this 賦值給 component 變數
  • 使用元件名作為樣式作用域空間
  • 對元件檔案進行程式碼校驗

基於模組開發

始終基於模組的方式來構建你的 app,每一個子模組只做一件事情。

Vue.js 的設計初衷就是幫助開發者更好的開發介面模組。一個模組是應用程式中獨立的一個部分。

怎麼做?

每一個 Vue 元件(等同於模組)首先必須專注於解決一個 單一的問題 , 獨立的 , 可複用的 , 微小的 and 可測試的 。

如果你的元件做了太多的事或是變得臃腫,請將其拆分成更小的元件並保持單一的原則。一般來說,儘量保證每一個檔案的程式碼行數不要超過 100 行。也請保證元件可獨立的執行。比較好的做法是增加一個單獨的 demo 示例。

Vue 元件命名

元件的命名需遵從以下原則:

  • 有意義的 : 不過於具體,也不過於抽象
  • 簡短 : 2 到 3 個單詞
  • 具有可讀性 : 以便於溝通交流

同時還需要注意:

  • 必須符合 自定義元素規範 : 使用連字元 分隔單詞,切勿使用保留字。
  • app- 字首作為名稱空間 : 如果非常通用的話可使用一個單詞來命名,這樣可以方便於其它專案裡複用。

為什麼?

  • 元件是通過元件名來呼叫的。所以元件名必須簡短、富有含義並且具有可讀性。

如何做?

<!-- 推薦 -->
<app-header></app-header>
<user-list></user-list>
<range-slider></range-slider>

<!-- 避免 -->
<btn-group></btn-group> <!-- 雖然簡短但是可讀性差. 使用 `button-group` 替代 -->
<ui-slider></ui-slider> <!-- ui 字首太過於寬泛,在這裡意義不明確 -->
<slider></slider> <!-- 與自定義元素規範不相容 -->

元件表示式簡單化

Vue.js 的表示式是 100% 的 Javascript 表示式。這使得其功能性很強大,但也帶來潛在的複雜性。因此,你應該儘量 保持表示式的簡單化 。

為什麼?

  • 複雜的行內表示式難以閱讀。
  • 行內表示式是不能夠通用的,這可能會導致重複編碼的問題。
  • IDE 基本上不能識別行內表示式語法,所以使用行內表示式 IDE 不能提供自動補全和語法校驗功能。

怎麼做?

如果你發現寫了太多複雜並難以閱讀的行內表示式,那麼可以使用 method 或是 computed 屬性來替代其功能。

<!-- 推薦 -->
<template>
    <h1>
        {{ `${year}-${month}` }}
    </h1>
</template>
<script type="text/javascript">
  export default {
    computed: {
      month() {
        return this.twoDigits((new Date()).getUTCMonth() + 1);
      },
      year() {
        return (new Date()).getUTCFullYear();
      }
    },
    methods: {
      twoDigits(num) {
        return ('0' + num).slice(-2);
      }
    },
  };
</script>

<!-- 避免 -->
<template>
    <h1>
        {{ `${(new Date()).getUTCFullYear()}-${('0' + ((new Date()).getUTCMonth()+1)).slice(-2)}` }}
    </h1>
</template>

元件 props 原子化

雖然 Vue.js 支援傳遞複雜的 JavaScript 物件通過 props 屬性,但是你應該儘可能的使用原始型別的資料。儘量只使用 JavaScript 原始型別 (字串、數字、布林值) 和 函式。儘量避免複雜的物件。

為什麼?

  • 使得元件 API 清晰直觀
  • 只使用原始型別和函式作為 props 使得元件的 API 更接近於 HTML(5) 原生元素。
  • 其它開發者更好的理解每一個 prop 的含義,作用
  • 傳遞過於複雜的物件使得我們不能夠清楚的知道哪些屬性或方法被自定義元件使用,這使得程式碼難以重構和維護。

怎麼做?

元件的每一個屬性單獨使用一個 props,並且使用函式或是原始型別的值。

<!-- 推薦 -->
<range-slider
  :values="[10, 20]"
  min="0"
  max="100"
  step="5"
  :on-slide="updateInputs"
  :on-end="updateResults">
</range-slider>

<!-- 避免 -->
<range-slider :config="complexConfigObject"></range-slider>

驗證元件的 props

在 Vue.js 中,元件的 props 即 API,一個穩定並可預測的 API 會使得你的元件更容易被其他開發者使用。

元件 props 通過自定義標籤的屬性來傳遞。屬性的值可以是 Vue.js 字串( :attr="value" 或v-bind:attr="value" )或是不傳。你需要保證元件的 props 能應對不同的情況。

為什麼?

驗證元件 props 可以保證你的元件永遠是可用的(防禦性程式設計)。即使其他開發者並未按照你預想的方法使用時也不會出錯。

怎麼做?

  • 提供預設值
  • 使用 type 屬性 校驗型別
  • 使用 props 之前先檢查該 prop 是否存在
<template>
  <input type="range" v-model="value" :max="max" :min="min">
</template>
<script type="text/javascript">
  export default {
    props: {
      max: {
        type: Number, // 這裡新增了數字型別的校驗
        default() { return 10; },
      },
      min: {
        type: Number,
        default() { return 0; },
      },
      value: {
        type: Number,
        default() { return 4; },
      },
    },
  };
</script>

將 this 賦值給 component 變數(

在 Vue.js 元件上下文中, this 指向了元件例項。因此當你切換到了不同的上下文時,要確保this 指向一個可用的 component 變數。

換句話說,不要在編寫這樣的程式碼 const self = this; ,而是應該直接使用變數 component

為什麼?

  • 將元件 this 賦值給變數 component 可用讓開發者清楚的知道任何一個被使用的地方,它代表的是元件例項。

怎麼做?

<script type="text/javascript">
export default {
  methods: {
    hello() {
      return 'hello';
    },
    printHello() {
      console.log(this.hello());
    },
  },
};
</script>

<!-- 避免 -->
<script type="text/javascript">
export default {
  methods: {
    hello() {
      return 'hello';
    },
    printHello() {
      const self = this; // 沒有必要
      console.log(self.hello());
    },
  },
};
</script>

元件結構化

按照一定的結構組織,使得元件便於理解。

為什麼?

  • 匯出一個清晰、組織有序的元件,使得程式碼易於閱讀和理解。同時也便於標準化。
  • 按首字母排序properties, data, computed, watches 和 methods使得這些物件內的屬性便於查詢。
  • 合理組織,使得元件易於閱讀。(name; extends; props, data and computed; components; watch and methods; lifecycle methods, 等.);
  • 使用 name 屬性。藉助於 vue devtools 可以讓你更方便的測試
  • 合理的 CSS 結構,如 BEM 或 rscss -;
  • 使用單檔案 .vue 檔案格式來元件程式碼

怎麼做?

元件結構化

<template lang="html">
    <div class="Ranger__Wrapper">
        <!-- ... -->
    </div>
</template>

<script type="text/javascript">
  export default {
        // 不要忘記了 name 屬性
    name: 'RangeSlider',
    // 組合其它元件
    extends: {},
    // 元件屬性、變數
    props: {
            bar: {}, // 按字母順序
            foo: {},
            fooBar: {},
        },
    // 變數
    data() {},
    computed: {},
    // 使用其它元件
    components: {},
    // 方法
    watch: {},
    methods: {},
    // 生命週期函式
    beforeCreate() {},
    mounted() {},
};
</script>

<style scoped>
  .Ranger__Wrapper { /* ... */ }
</style>

元件事件命名

Vue.js 提供的處理函式和表示式都是繫結在 ViewModel 上的,元件的每一個事件都應該按照一個好的命名規範來,這樣可以避免不少的開發問題,具體可見如下 ** 為什麼**。

為什麼?

  • 開發者可以隨意給事件命名,即使是原生事件的名字,這樣會帶來迷惑性。
  • 過於寬鬆的事件命名可能與 DOM模板不相容 。

怎麼做?

  • 事件命名也連字元命名
  • 一個事件的名字對應元件外的一組意義操作,如:upload-success, upload-error 以及 dropzone-upload-success, dropzone-upload-error (如果需要字首的話)。
  • 事件命名應該以動詞(如 client-api-load) 或是 形容詞(如 drive-upload-success)結尾。( 出處 )

避免 this.$parent

Vue.js 支援元件巢狀,並且子元件可訪問父元件的上下文。訪問元件之外的上下文違反了 基於模組開發 的 第一原則 。因此你應該儘量避免使用 this.$parent 

為什麼?

  • 元件必須相互保持獨立,Vue 元件也是。如果元件需要訪問其父層的上下文就違反了該原則。
  • 如果一個元件需要訪問其父元件的上下文,那麼該元件將不能再其它上下文中複用。

怎麼做?

  • 通過 props 將值傳遞給子元件
  • 通過 props 傳遞迴調函式給子元件來達到呼叫父元件方法的目的
  • 通過在子元件觸發事件來通知父元件

謹慎使用 this.$refs

Vue.js 支援通過 ref 屬性來訪問其它元件和 HTML 元素。並通過 this.$refs 可以得到元件或 HTML 元素的上下文。在大多數情況下,通過 this.$refs 來訪問其它元件的上下文是可以避免的。在使用的的時候你需要注意避免呼叫了不恰當的元件 API,所以應該儘量避免使用 this.$refs 。

為什麼?

  • 元件必須是保持獨立的,如果一個元件的 API 不能夠提供所需的功能,那麼這個元件在設計、實現上是有問題的。
  • 元件的屬性和事件必須足夠的給大多數的元件使用

怎麼做?

  • 提供良好的元件 API
  • 總是關注於元件本身的目的
  • 拒絕定製程式碼。如果你在一個通用的元件內部編寫特定需求的程式碼,那麼代表這個元件的 API 不夠通用,或者你可能需要一個新的元件來應對該需求
  • 檢查所有的 props 是否有缺失的,如果有提一個 issue 或是完善這個元件
  • 檢查所有的事件。子元件向父元件通訊一般是通過事件來實現的,但是大多數的開發者更多的關注於 props 從忽視了這點。
  • Props向下傳遞,事件向上傳遞! 。以此為目標升級你的元件,提供良好的 API 和 獨立性。
  • 當遇到 props 和 events 難以實現的功能時,通過 this.$refs 來實現。
  • 當需要操作 DOM 無法通過指令來做的時候可使用 this..$ref 而不是 JQuery , document.getElement* , document.queryElement 。
<!-- 推薦,並未使用 this.$refs -->
<range :max="max"
  :min="min"
  @current-value="currentValue"
  :step="1"></range>
<!-- 使用 this.$refs 的適用情況-->
<modal ref="basicModal">
  <h4>Basic Modal</h4>
  <button class="primary" @click="$refs.basicModal.close()">Close</button>
</modal>
<button @click="$refs.basicModal.open()">Open modal</button>

<!-- Modal component -->
<template>
  <div v-show="active">
    <!-- ... -->
  </div>
</template>

<script>
  export default {
    // ...
    data() {
        return {
            active: false,
        };
    },
    methods: {
      open() {
        this.active = true;
      },
      hide() {
        this.active = false;
      },
    },
    // ...
  };
</script>
<!-- 如果可通過 emited 來做則避免通過 this.$refs 直接訪問 -->
<template>
  <range :max="max"
    :min="min"
    ref="range"
    :step="1"></range>
</template>

<script>
  export default {
    // ...
    methods: {
      getRangeCurrentValue() {
        return this.$refs.range.currentValue;
      },
    },
    // ...
  };
</script>

使用元件名作為樣式作用域空間

Vue.js 的元件是自定義元素,這非常適合用來作為樣式的根作用域空間。可以將元件名作為 css 類的名稱空間。

為什麼?

  • 給樣式加上作用域空間可以避免元件樣式影響外部的樣式
  • 保持模組名、目錄名、樣式根作用域名一樣,可以很好的將其關聯起來,便於開發者理解。

怎麼做?

使用元件名作為樣式命名的字首,可基於 BEM 或 OOCSS 正規化。同時給style標籤加上 scoped 屬性。加上 scoped 屬性編譯後會給元件的 class 自動加上唯一的字首從而避免樣式的衝突。

<style scoped>
    /* 推薦 */
    .MyExample { }
    .MyExample li { }
    .MyExample__item { }

    /* 避免 */
    .My-Example { } /* not scoped to component or module name, not BEM compliant */
</style>

提供元件 API 文件

使用 Vue.js 元件的過程中會建立 Vue 元件例項,這個例項是通過自定義屬性配置的。為了便於其他開發者使用該元件,對於這些自定義屬性即元件API應該在 README.md 檔案中進行說明。

為什麼?

  • 良好的文件可以讓開發者比較容易的對元件有一個整體的認識,而不用去閱讀元件的原始碼,也更方便開發者使用
  • 元件配置屬性即元件的 API,對於元件的使用者來說他們更感興趣的是 API 而不是實現原理。
  • 正式的文件會告訴開發者元件 API 變更以及向後的相容性情況
  • README.md 是標準的我們應該首先閱讀的文件檔案。程式碼託管網站 (github/bitbucket/gitlab 等) 會預設在倉庫中展示 該檔案作為倉庫的介紹。

怎麼做?

在模組目錄中新增 README.md 檔案:

range-slider/
├── range-slider.vue
├── range-slider.less
└── README.md

在 README 檔案中說明模組的功能以及使用場景。對於 vue 元件來說,比較有用的描述是元件的自定義屬性即 API 的描述介紹。

Range slider

功能

range slider 元件可通過拖動的方式來設定一個給定範圍內的數值。

該模組使用 noUiSlider 來實現誇瀏覽器和 touch 功能的支援。

如何使用

<range-slider> 支援如下的自定義屬性:

attribute type description
min Number 可拖動的最小值.
max Number 可拖動的最大值.
values Number[] optional 包含最大值和最小值的陣列. 如. values="[10, 20]" . Defaults to [opts.min, opts.max] .
step Number optional 增加減小的數值單位,預設為 1.
on-slide Function optional 使用者拖動開始按鈕或者結束按鈕時的回撥函式,函式接受 (values, HANDLE) 格式的引數。 如: on-slide={ updateInputs } , component.updateInputs = (values, HANDLE) => { const value = values[HANDLE]; } .
on-end Function optional 當使用者停止拖動時觸發的回撥函式,函式接受 (values, HANDLE) 格式的引數。

如需要自定義 slider 的樣式可參考 noUiSlider 文件

提供元件 demo

新增 index.html 檔案作為元件的 demo 示例,並提供不同配置情況的效果,說明元件是如何使用的。

為什麼?

  • demo 可以說明元件是獨立可使用的
  • demo 可以讓開發者預覽元件的功能效果
  • demo 可以展示元件各種配置引數下的功能

對元件檔案進行程式碼校驗

程式碼校驗可以保持程式碼的統一性以及追蹤語法錯誤。.vue 檔案可以通過使用 eslint-plugin-html 外掛來校驗程式碼。你可以通過 vue-cli 來開始你的專案, vue-cli 預設會開啟程式碼校驗功能。

為什麼?

  • 保證所有的開發者使用同樣的編碼規範。
  • 更早的感知到語法錯誤

怎麼做?

為了校驗工具能夠校驗 *.vue 檔案,你需要將程式碼編寫在 <script> 標籤中,並使,因為校驗工具無法理解行內表示式,配置校驗工具可以訪問全域性變數 vue 和元件的 props 。

ESLint

ESLint 需要通過 ESLint HTML 外掛 來抽取元件中的程式碼。

通過 .eslintrc 檔案來配置 ESlint,這樣 IED 可以更好的理解校驗配置項。 ESlint,這樣.

{
  "extends": "eslint:recommended",
  "plugins": ["html"],
  "env": {
    "browser": true
  },
  "globals": {
    "opts": true,
    "vue": true
  }
}

執行 ESLint

eslint src/**/*.vue

JSHint

JSHint 可以解析 HTML (使用 --extra-ext 命令引數) 和 抽取程式碼(使用 --extract=auto 命令引數).

通過 .jshintrc 檔案來配置 ESlint,這樣 IED 可以更好的理解校驗配置項。

{
  "browser": true,
  "predef": ["opts", "vue"]
}

執行 JSHint

jshint --config modules/.jshintrc --extra-ext=html --extract=auto modules/

注:JSHint 不接受 vue 副檔名的檔案,只支援 html 。

相關文章