Vue 代碼風格指南

前言

這裏是官方的 Vue 特有代碼的風格指南。若是在工程中使用 Vue,爲了迴避錯誤、小糾結和反模式,該指南是份不錯的參考。vue

規則歸類

優先級 A:必要

這些規則會幫你規避錯誤,因此學習並接受它們帶來的所有代價吧。這裏面可能存在例外,但應該很是少,且只有你同時精通 JavaScript 和 Vue 才能夠這樣作。vuex

優先級 B:推薦

這些規則可以在絕大多數工程中改善可讀性和開發體驗。即便你違反了,代碼仍是能照常運行,但例外應該儘量少且有合理的理由。數組

優先級 C:謹慎使用

有些 Vue 特性的存在是爲了照顧極端狀況或幫助老代碼的平穩遷移。當被過分使用時,這些特性會讓你的代碼難於維護甚至變成 bug 的來源。這些規則是爲了給有潛在風險的特性敲個警鐘,並說明它們何時不該該使用以及爲何。編輯器

優先級 A 的規則:必要的 (規避錯誤)

組件名爲多個單詞 必要

組件名應該始終是多個單詞的,根組件 App 以及 <transition><component> 之類的 Vue 內置組件除外。函數

這樣作能夠避免跟現有的以及將來的 HTML 元素相沖突,由於全部的 HTML 元素名稱都是單個單詞的。工具

反例

  1. Vue.component('todo', {
    
    // ...
    
    })
    
    export default {
    
    name: 'Todo',
    
    // ...
    
    }

     

好例子

  1. Vue.component('todo-item', {
    
    // ...
    
    })
    
    export default {
    
      name: 'TodoItem',
    
    // ...
    
    }

     

組件數據 必要

組件的 data 必須是一個函數。學習

當在組件中使用 data 屬性的時候 (除了 newVue 外的任何地方),它的值必須是返回一個對象的函數。測試

詳解

data 的值是一個對象時,它會在這個組件的全部實例之間共享。想象一下,假如一個 TodoList 組件的數據是這樣的:ui

  1. data: {
    
       listTitle: '',
    
      todos: []
    
    }

     

咱們可能但願重用這個組件,容許用戶維護多個列表 (好比分爲購物、心願單、平常事務等)。這時就會產生問題。由於每一個組件的實例都引用了相同的數據對象,更改其中一個列表的標題就會改變其它每個列表的標題。增刪改一個待辦事項的時候也是如此。this

取而代之的是,咱們但願每一個組件實例都管理其本身的數據。爲了作到這一點,每一個實例必須生成一個獨立的數據對象。在 JavaScript 中,在一個函數中返回這個對象就能夠了:

  1. data: function () {

  2. return {

  3. listTitle: '',

  4. todos: []

  5. }

  6. }

反例

  1. Vue.component('some-comp', {

  2. data: {

  3. foo: 'bar'

  4. }

  5. })

  1. export default {

  2. data: {

  3. foo: 'bar'

  4. }

  5. }

好例子

  1. Vue.component('some-comp', {

  2. data: function () {

  3. return {

  4. foo: 'bar'

  5. }

  6. }

  7. })

  1. // In a .vue file

  2. export default {

  3. data () {

  4. return {

  5. foo: 'bar'

  6. }

  7. }

  8. }

  1. // 在一個 Vue 的根實例上直接使用對象是能夠的,

  2. // 由於只存在一個這樣的實例。

  3. new Vue({

  4. data: {

  5. foo: 'bar'

  6. }

  7. })

Prop 定義 必要

Prop 定義應該儘可能詳細。

在你提交的代碼中,prop 的定義應該儘可能詳細,至少須要指定其類型。

詳解

細緻的 prop 定義有兩個好處:

  • 它們寫明瞭組件的 API,因此很容易看懂組件的用法;

  • 在開發環境下,若是向一個組件提供格式不正確的 prop,Vue 將會告警,以幫助你捕獲潛在的錯誤來源。

反例

  1. // 這樣作只有開發原型系統時能夠接受

  2. props: ['status']

好例子

  1. props: {

  2. status: String

  3. }

  1. // 更好的作法!

  2. props: {

  3. status: {

  4. type: String,

  5. required: true,

  6. validator: function (value) {

  7. return [

  8. 'syncing',

  9. 'synced',

  10. 'version-conflict',

  11. 'error'

  12. ].indexOf(value) !== -1

  13. }

  14. }

  15. }

避免 v-if 和 v-for 用在一塊兒 必要

通常咱們在兩種常見的狀況下會傾向於這樣作:

  •  

    爲了過濾一個列表中的項目 (好比 v-for="user in users"v-if="user.isActive")。在這種情形下,請將 users 替換爲一個計算屬性 (好比 activeUsers),讓其返回過濾後的列表。

     

  •  

    爲了不渲染本應該被隱藏的列表 (好比 v-for="user in users"v-if="shouldShowUsers")。這種情形下,請將 v-if 移動至容器元素上 (好比 ul, ol)。

     

詳解

當 Vue 處理指令時, v-forv-if 具備更高的優先級,因此這個模板:

  1. <ul>

  2. <li

  3. v-for="user in users"

  4. v-if="user.isActive"

  5. :key="user.id"

  6. >

  7. {{ user.name }}

  8. </li>

  9. </ul>

將會通過以下運算:

  1. this.users.map(function (user) {

  2. if (user.isActive) {

  3. return user.name

  4. }

  5. })

所以哪怕咱們只渲染出一小部分用戶的元素,也得在每次重渲染的時候遍歷整個列表,不論活躍用戶是否發生了變化。

經過將其更換爲在以下的一個計算屬性上遍歷:

  1. computed: {

  2. activeUsers: function () {

  3. return this.users.filter(function (user) {

  4. return user.isActive

  5. })

  6. }

  7. }

  1. <ul>

  2. <li

  3. v-for="user in activeUsers"

  4. :key="user.id"

  5. >

  6. {{ user.name }}

  7. </li>

  8. </ul>

咱們將會得到以下好處:

  • 過濾後的列表只會在 users 數組發生相關變化時才被從新運算,過濾更高效。

  • 使用 v-for="user in activeUsers" 以後,咱們在渲染的時候只遍歷活躍用戶,渲染更高效。

  • 解藕渲染層的邏輯,可維護性 (對邏輯的更改和擴展) 更強。

爲了得到一樣的好處,咱們也能夠把:

  1. <ul>

  2. <li

  3. v-for="user in users"

  4. v-if="shouldShowUsers"

  5. :key="user.id"

  6. >

  7. {{ user.name }}

  8. </li>

  9. </ul>

更新爲:

  1. <ul v-if="shouldShowUsers">

  2. <li

  3. v-for="user in users"

  4. :key="user.id"

  5. >

  6. {{ user.name }}

  7. </li>

  8. </ul>

經過將 v-if 移動到容器元素,咱們不會再對列表中的每一個用戶檢查 shouldShowUsers。取而代之的是,咱們只檢查它一次,且不會在 shouldShowUsers 爲否的時候運算 v-for

反例

  1. <ul>

  2. <li

  3. v-for="user in users"

  4. v-if="user.isActive"

  5. :key="user.id"

  6. >

  7. {{ user.name }}

  8. </li>

  9. </ul>

  1. <ul>

  2. <li

  3. v-for="user in users"

  4. v-if="shouldShowUsers"

  5. :key="user.id"

  6. >

  7. {{ user.name }}

  8. </li>

  9. </ul>

好例子

  1. <ul>

  2. <li

  3. v-for="user in activeUsers"

  4. :key="user.id"

  5. >

  6. {{ user.name }}

  7. </li>

  8. </ul>

  1. <ul v-if="shouldShowUsers">

  2. <li

  3. v-for="user in users"

  4. :key="user.id"

  5. >

  6. {{ user.name }}

  7. </li>

  8. </ul>

優先級 B 的規則:推薦 (加強可讀性)

組件文件 推薦

只要有可以拼接文件的構建系統,就把每一個組件單獨分紅文件。

當你須要編輯一個組件或查閱一個組件的用法時,能夠更快速的找到它。

反例

  1. Vue.component('TodoList', {

  2. // ...

  3. })

  4.  

  5. Vue.component('TodoItem', {

  6. // ...

  7. })

好例子

  1. components/

  2. |- TodoList.js

  3. |- TodoItem.js

  1. components/

  2. |- TodoList.vue

  3. |- TodoItem.vue

組件名中的單詞順序 推薦

組件名應該以高級別的 (一般是通常化描述的) 單詞開頭,以描述性的修飾詞結尾。

要注意在你的應用中所謂的「高級別」是跟語境有關的。好比對於一個帶搜索表單的應用來講,它可能包含這樣的組件:

  1. components/

  2. |- ClearSearchButton.vue

  3. |- ExcludeFromSearchInput.vue

  4. |- LaunchOnStartupCheckbox.vue

  5. |- RunSearchButton.vue

  6. |- SearchInput.vue

  7. |- TermsCheckbox.vue

你可能注意到了,咱們很難看出來哪些組件是針對搜索的。如今咱們來根據規則給組件從新命名:

  1. components/

  2. |- SearchButtonClear.vue

  3. |- SearchButtonRun.vue

  4. |- SearchInputExcludeGlob.vue

  5. |- SearchInputQuery.vue

  6. |- SettingsCheckboxLaunchOnStartup.vue

  7. |- SettingsCheckboxTerms.vue

由於編輯器一般會按字母順序組織文件,因此如今組件之間的重要關係一目瞭然。

你可能想換成多級目錄的方式,把全部的搜索組件放到「search」目錄,把全部的設置組件放到「settings」目錄。咱們只推薦在很是大型 (若有 100+ 個組件) 的應用下才考慮這麼作,由於:

  • 在多級目錄間找來找去,要比在單個 components 目錄下滾動查找要花費更多的精力。

  • 存在組件重名 (好比存在多個 ButtonDelete 組件) 的時候在編輯器裏更難快速定位。

  • 讓重構變得更難,由於爲一個移動了的組件更新相關引用時,查找/替換一般並不高效。

反例

  1. components/

  2. |- ClearSearchButton.vue

  3. |- ExcludeFromSearchInput.vue

  4. |- LaunchOnStartupCheckbox.vue

  5. |- RunSearchButton.vue

  6. |- SearchInput.vue

  7. |- TermsCheckbox.vue

好例子

  1. components/

  2. |- SearchButtonClear.vue

  3. |- SearchButtonRun.vue

  4. |- SearchInputQuery.vue

  5. |- SearchInputExcludeGlob.vue

  6. |- SettingsCheckboxTerms.vue

  7. |- SettingsCheckboxLaunchOnStartup.vue

完整單詞的組件名 推薦

組件名應該傾向於完整單詞而不是縮寫。

編輯器中的自動補全已經讓書寫長命名的代價很是之低了,而其帶來的明確性倒是很是寶貴的。不經常使用的縮寫尤爲應該避免。

反例

  1. components/

  2. |- SdSettings.vue

  3. |- UProfOpts.vue

好例子

  1. components/

  2. |- StudentDashboardSettings.vue

  3. |- UserProfileOptions.vue

Prop 名大小寫 推薦

在聲明 prop 的時候,其命名應該始終使用 camelCase,而在模板和 JSX 中應該始終使用 kebab-case。

咱們單純的遵循每一個語言的約定。在 JavaScript 中更天然的是 camelCase。而在 HTML 中則是 kebab-case。

反例

  1. props: {

  2. 'greeting-text': String

  3. }

好例子

  1. props: {

  2. greetingText: String

  3. }

多個特性的元素 推薦

多個特性的元素應該分多行撰寫,每一個特性一行。

在 JavaScript 中,用多行分隔對象的多個屬性是很常見的最佳實踐,由於這樣更易讀。模板和 JSX 值得咱們作相同的考慮。

反例

  1. <img src="https://vuejs.org/images/logo.png" alt="Vue Logo">

  1. <MyComponent foo="a" bar="b" baz="c"/>

好例子

  1. <img

  2. src="https://vuejs.org/images/logo.png"

  3. alt="Vue Logo"

  4. >

  1. <MyComponent

  2. foo="a"

  3. bar="b"

  4. baz="c"

  5. />

簡單的計算屬性 推薦

應該把複雜計算屬性分割爲儘量多的更簡單的屬性。

詳解

更簡單、命名得當的計算屬性是這樣的:

  • 易於測試

    當每一個計算屬性都包含一個很是簡單且不多依賴的表達式時,撰寫測試以確保其正確工做就會更加容易。

  • 易於閱讀

    簡化計算屬性要求你爲每個值都起一個描述性的名稱,即使它不可複用。這使得其餘開發者 (以及將來的你) 更容易專一在他們關心的代碼上並搞清楚發生了什麼。

  • 更好的「擁抱變化」

    任何可以命名的值均可能用在視圖上。舉個例子,咱們可能打算展現一個信息,告訴用戶他們存了多少錢;也可能打算計算稅費,可是可能會分開展示,而不是做爲總價的一部分。

    小的、專一的計算屬性減小了信息使用時的假設性限制,因此需求變動時也用不着那麼多重構了。

反例

  1. computed: {

  2. price: function () {

  3. var basePrice = this.manufactureCost / (1 - this.profitMargin)

  4. return (

  5. basePrice -

  6. basePrice * (this.discountPercent || 0)

  7. )

  8. }

  9. }

好例子

  1. computed: {

  2. basePrice: function () {

  3. return this.manufactureCost / (1 - this.profitMargin)

  4. },

  5. discount: function () {

  6. return this.basePrice * (this.discountPercent || 0)

  7. },

  8. finalPrice: function () {

  9. return this.basePrice - this.discount

  10. }

  11. }

 

優先級 C 的規則:謹慎使用 (有潛在危險的模式)

沒有在 v-ifv-else-ifv-else 中使用 key 謹慎使用

若是一組 v-if + v-else 的元素類型相同,最好使用 key (好比兩個 <div> 元素)。

默認狀況下,Vue 會盡量高效的更新 DOM。這意味着其在相同類型的元素之間切換時,會修補已存在的元素,而不是將舊的元素移除而後在同一位置添加一個新元素。若是本不相同的元素被識別爲相同,則會出現意料以外的結果。

反例

  1. <div v-if="error">

  2. 錯誤:{{ error }}

  3. </div>

  4. <div v-else>

  5. {{ results }}

  6. </div>

好例子

  1. <div

  2. v-if="error"

  3. key="search-status"

  4. >

  5. 錯誤:{{ error }}

  6. </div>

  7. <div

  8. v-else

  9. key="search-results"

  10. >

  11. {{ results }}

  12. </div>

scoped 中的元素選擇器 謹慎使用

元素選擇器應該避免在 scoped 中出現。

scoped 樣式中,類選擇器比元素選擇器更好,由於大量使用元素選擇器是很慢的。

詳解

爲了給樣式設置做用域,Vue 會爲元素添加一個獨一無二的特性,例如 data-v-f3f3eg9。而後修改選擇器,使得在匹配選擇器的元素中,只有帶這個特性纔會真正生效 (好比 button[data-v-f3f3eg9])。

問題在於大量的元素和特性組合的選擇器 (好比 button[data-v-f3f3eg9]) 會比類和特性組合的選擇器慢,因此應該儘量選用類選擇器。

反例

  1. <template>

  2. <button>X</button>

  3. </template>

  4.  

  5. <style scoped>

  6. button {

  7. background-color: red;

  8. }

  9. </style>

好例子

  1. <template>

  2. <button class="btn btn-close">X</button>

  3. </template>

  4.  

  5. <style scoped>

  6. .btn-close {

  7. background-color: red;

  8. }

  9. </style>

隱性的父子組件通訊 謹慎使用

應該優先經過 prop 和事件進行父子組件之間的通訊,而不是 this.$parent 或改變 prop。

一個理想的 Vue 應用是 prop 向下傳遞,事件向上傳遞的。遵循這一約定會讓你的組件更易於理解。然而,在一些邊界狀況下 prop 的變動或 this.$parent 可以簡化兩個深度耦合的組件。

問題在於,這種作法在不少簡單的場景下可能會更方便。但請小心,不要爲了一時方便 (少寫代碼) 而犧牲數據流向的簡潔性 (易於理解)。

反例

  1. Vue.component('TodoItem', {

  2. props: {

  3. todo: {

  4. type: Object,

  5. required: true

  6. }

  7. },

  8. template: '<input v-model="todo.text">'

  9. })

  1. Vue.component('TodoItem', {

  2. props: {

  3. todo: {

  4. type: Object,

  5. required: true

  6. }

  7. },

  8. methods: {

  9. removeTodo () {

  10. var vm = this

  11. vm.$parent.todos = vm.$parent.todos.filter(function (todo) {

  12. return todo.id !== vm.todo.id

  13. })

  14. }

  15. },

  16. template: `

  17. <span>

  18. {{ todo.text }}

  19. <button @click="removeTodo">

  20. X

  21. </button>

  22. </span>

  23. `

  24. })

好例子

  1. Vue.component('TodoItem', {

  2. props: {

  3. todo: {

  4. type: Object,

  5. required: true

  6. }

  7. },

  8. template: `

  9. <input

  10. :value="todo.text"

  11. @input="$emit('input', $event.target.value)"

  12. >

  13. `

  14. })

  1. Vue.component('TodoItem', {

  2. props: {

  3. todo: {

  4. type: Object,

  5. required: true

  6. }

  7. },

  8. template: `

  9. <span>

  10. {{ todo.text }}

  11. <button @click="$emit('delete')">

  12. X

  13. </button>

  14. </span>

  15. `

  16. })

非 Flux 的全局狀態管理 謹慎使用

應該優先經過 Vuex 管理全局狀態,而不是經過 this.$root 或一個全局事件總線。

經過 this.$root 和/或全局事件總線管理狀態在不少簡單的狀況下都是很方便的,可是並不適用於絕大多數的應用。Vuex 提供的不只是一個管理狀態的中心區域,仍是組織、追蹤和調試狀態變動的好工具。

反例

  1. // main.js

  2. new Vue({

  3. data: {

  4. todos: []

  5. },

  6. created: function () {

  7. this.$on('remove-todo', this.removeTodo)

  8. },

  9. methods: {

  10. removeTodo: function (todo) {

  11. var todoIdToRemove = todo.id

  12. this.todos = this.todos.filter(function (todo) {

  13. return todo.id !== todoIdToRemove

  14. })

  15. }

  16. }

  17. })

好例子

  1. // store/modules/todos.js

  2. export default {

  3. state: {

  4. list: []

  5. },

  6. mutations: {

  7. REMOVE_TODO (state, todoId) {

  8. state.list = state.list.filter(todo => todo.id !== todoId)

  9. }

  10. },

  11. actions: {

  12. removeTodo ({ commit, state }, todo) {

  13. commit('REMOVE_TODO', todo.id)

  14. }

  15. }

  16. }

  1. <!-- TodoItem.vue -->

  2. <template>

  3. <span>

  4. {{ todo.text }}

  5. <button @click="removeTodo(todo)">

  6. X

  7. </button>

  8. </span>

  9. </template>

  10.  

  11. <script>

  12. import { mapActions } from 'vuex'

  13.  

  14. export default {

  15. props: {

  16. todo: {

  17. type: Object,

  18. required: true

  19. }

  20. },

  21. methods: mapActions(['removeTodo'])

  22. }

  23. </script>

相關文章
相關標籤/搜索