Vue.js 組件編碼規范
目標
本規范提供了一種統一的編碼規范來編寫 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> <!-- 與自定義元素規范不兼容 --></code></pre>
組件表達式簡單化
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></code></pre>
組件 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></code></pre>
驗證組件的 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></code></pre>
組件結構化
按照一定的結構組織,使得組件便于理解。
為什么?
- 導出一個清晰、組織有序的組件,使得代碼易于閱讀和理解。同時也便于標準化。
- 按首字母排序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></code></pre>
組件事件命名
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></code></pre>
<!-- 如果可通過 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></code></pre>
使用組件名作為樣式作用域空間
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></code></pre>
提供組件 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 。
如何提供幫助?
Fork 和 提 PR 以幫助我們改進或者可以給我們提 Issue .
來自:https://github.com/pablohpsilva/vuejs-component-style-guide/blob/master/README-CN.md