這裏是官方的 Vue 特有代碼的風格指南。若是在工程中使用 Vue,爲了迴避錯誤、小糾結和反模式,該指南是份不錯的參考。vue
這些規則會幫你規避錯誤,因此學習並接受它們帶來的所有代價吧。這裏面可能存在例外,但應該很是少,且只有你同時精通 JavaScript 和 Vue 才能夠這樣作。vuex
這些規則可以在絕大多數工程中改善可讀性和開發體驗。即便你違反了,代碼仍是能照常運行,但例外應該儘量少且有合理的理由。數組
有些 Vue 特性的存在是爲了照顧極端狀況或幫助老代碼的平穩遷移。當被過分使用時,這些特性會讓你的代碼難於維護甚至變成 bug 的來源。這些規則是爲了給有潛在風險的特性敲個警鐘,並說明它們何時不該該使用以及爲何。編輯器
組件名應該始終是多個單詞的,根組件 App
以及 <transition>
、 <component>
之類的 Vue 內置組件除外。函數
這樣作能夠避免跟現有的以及將來的 HTML 元素相沖突,由於全部的 HTML 元素名稱都是單個單詞的。工具
Vue.component('todo', {
// ...
})
export default {
name: 'Todo',
// ...
}
Vue.component('todo-item', {
// ...
})
export default {
name: 'TodoItem',
// ...
}
組件的 data
必須是一個函數。學習
當在組件中使用 data
屬性的時候 (除了 newVue
外的任何地方),它的值必須是返回一個對象的函數。測試
當 data
的值是一個對象時,它會在這個組件的全部實例之間共享。想象一下,假如一個 TodoList
組件的數據是這樣的:ui
data: {
listTitle: '',
todos: []
}
咱們可能但願重用這個組件,容許用戶維護多個列表 (好比分爲購物、心願單、平常事務等)。這時就會產生問題。由於每一個組件的實例都引用了相同的數據對象,更改其中一個列表的標題就會改變其它每個列表的標題。增刪改一個待辦事項的時候也是如此。this
取而代之的是,咱們但願每一個組件實例都管理其本身的數據。爲了作到這一點,每一個實例必須生成一個獨立的數據對象。在 JavaScript 中,在一個函數中返回這個對象就能夠了:
data: function () {
return {
listTitle: '',
todos: []
}
}
Vue.component('some-comp', {
data: {
foo: 'bar'
}
})
export default {
data: {
foo: 'bar'
}
}
Vue.component('some-comp', {
data: function () {
return {
foo: 'bar'
}
}
})
// In a .vue file
export default {
data () {
return {
foo: 'bar'
}
}
}
// 在一個 Vue 的根實例上直接使用對象是能夠的,
// 由於只存在一個這樣的實例。
new Vue({
data: {
foo: 'bar'
}
})
Prop 定義應該儘可能詳細。
在你提交的代碼中,prop 的定義應該儘可能詳細,至少須要指定其類型。
細緻的 prop 定義有兩個好處:
它們寫明瞭組件的 API,因此很容易看懂組件的用法;
在開發環境下,若是向一個組件提供格式不正確的 prop,Vue 將會告警,以幫助你捕獲潛在的錯誤來源。
// 這樣作只有開發原型系統時能夠接受
props: ['status']
props: {
status: String
}
// 更好的作法!
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}
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-for
比 v-if
具備更高的優先級,因此這個模板:
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
將會通過以下運算:
this.users.map(function (user) {
if (user.isActive) {
return user.name
}
})
所以哪怕咱們只渲染出一小部分用戶的元素,也得在每次重渲染的時候遍歷整個列表,不論活躍用戶是否發生了變化。
經過將其更換爲在以下的一個計算屬性上遍歷:
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
咱們將會得到以下好處:
過濾後的列表只會在 users
數組發生相關變化時才被從新運算,過濾更高效。
使用 v-for="user in activeUsers"
以後,咱們在渲染的時候只遍歷活躍用戶,渲染更高效。
解藕渲染層的邏輯,可維護性 (對邏輯的更改和擴展) 更強。
爲了得到一樣的好處,咱們也能夠把:
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
更新爲:
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
經過將 v-if
移動到容器元素,咱們不會再對列表中的每一個用戶檢查 shouldShowUsers
。取而代之的是,咱們只檢查它一次,且不會在 shouldShowUsers
爲否的時候運算 v-for
。
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
只要有可以拼接文件的構建系統,就把每一個組件單獨分紅文件。
當你須要編輯一個組件或查閱一個組件的用法時,能夠更快速的找到它。
Vue.component('TodoList', {
// ...
})
Vue.component('TodoItem', {
// ...
})
components/
|- TodoList.js
|- TodoItem.js
components/
|- TodoList.vue
|- TodoItem.vue
組件名應該以高級別的 (一般是通常化描述的) 單詞開頭,以描述性的修飾詞結尾。
要注意在你的應用中所謂的「高級別」是跟語境有關的。好比對於一個帶搜索表單的應用來講,它可能包含這樣的組件:
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
你可能注意到了,咱們很難看出來哪些組件是針對搜索的。如今咱們來根據規則給組件從新命名:
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputExcludeGlob.vue
|- SearchInputQuery.vue
|- SettingsCheckboxLaunchOnStartup.vue
|- SettingsCheckboxTerms.vue
由於編輯器一般會按字母順序組織文件,因此如今組件之間的重要關係一目瞭然。
你可能想換成多級目錄的方式,把全部的搜索組件放到「search」目錄,把全部的設置組件放到「settings」目錄。咱們只推薦在很是大型 (若有 100+ 個組件) 的應用下才考慮這麼作,由於:
在多級目錄間找來找去,要比在單個 components
目錄下滾動查找要花費更多的精力。
存在組件重名 (好比存在多個 ButtonDelete
組件) 的時候在編輯器裏更難快速定位。
讓重構變得更難,由於爲一個移動了的組件更新相關引用時,查找/替換一般並不高效。
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue
組件名應該傾向於完整單詞而不是縮寫。
編輯器中的自動補全已經讓書寫長命名的代價很是之低了,而其帶來的明確性倒是很是寶貴的。不經常使用的縮寫尤爲應該避免。
components/
|- SdSettings.vue
|- UProfOpts.vue
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
在聲明 prop 的時候,其命名應該始終使用 camelCase,而在模板和 JSX 中應該始終使用 kebab-case。
咱們單純的遵循每一個語言的約定。在 JavaScript 中更天然的是 camelCase。而在 HTML 中則是 kebab-case。
props: {
'greeting-text': String
}
props: {
greetingText: String
}
多個特性的元素應該分多行撰寫,每一個特性一行。
在 JavaScript 中,用多行分隔對象的多個屬性是很常見的最佳實踐,由於這樣更易讀。模板和 JSX 值得咱們作相同的考慮。
<img src="https://vuejs.org/images/logo.png" alt="Vue Logo">
<MyComponent foo="a" bar="b" baz="c"/>
<img
src="https://vuejs.org/images/logo.png"
alt="Vue Logo"
>
<MyComponent
foo="a"
bar="b"
baz="c"
/>
應該把複雜計算屬性分割爲儘量多的更簡單的屬性。
更簡單、命名得當的計算屬性是這樣的:
易於測試
當每一個計算屬性都包含一個很是簡單且不多依賴的表達式時,撰寫測試以確保其正確工做就會更加容易。
易於閱讀
簡化計算屬性要求你爲每個值都起一個描述性的名稱,即使它不可複用。這使得其餘開發者 (以及將來的你) 更容易專一在他們關心的代碼上並搞清楚發生了什麼。
更好的「擁抱變化」
任何可以命名的值均可能用在視圖上。舉個例子,咱們可能打算展現一個信息,告訴用戶他們存了多少錢;也可能打算計算稅費,可是可能會分開展示,而不是做爲總價的一部分。
小的、專一的計算屬性減小了信息使用時的假設性限制,因此需求變動時也用不着那麼多重構了。
computed: {
price: function () {
var basePrice = this.manufactureCost / (1 - this.profitMargin)
return (
basePrice -
basePrice * (this.discountPercent || 0)
)
}
}
computed: {
basePrice: function () {
return this.manufactureCost / (1 - this.profitMargin)
},
discount: function () {
return this.basePrice * (this.discountPercent || 0)
},
finalPrice: function () {
return this.basePrice - this.discount
}
}
v-if
/ v-else-if
/ v-else
中使用 key
謹慎使用若是一組 v-if
+ v-else
的元素類型相同,最好使用 key
(好比兩個 <div>
元素)。
默認狀況下,Vue 會盡量高效的更新 DOM。這意味着其在相同類型的元素之間切換時,會修補已存在的元素,而不是將舊的元素移除而後在同一位置添加一個新元素。若是本不相同的元素被識別爲相同,則會出現意料以外的結果。
<div v-if="error">
錯誤:{{ error }}
</div>
<div v-else>
{{ results }}
</div>
<div
v-if="error"
key="search-status"
>
錯誤:{{ error }}
</div>
<div
v-else
key="search-results"
>
{{ results }}
</div>
scoped
中的元素選擇器 謹慎使用元素選擇器應該避免在 scoped
中出現。
在 scoped
樣式中,類選擇器比元素選擇器更好,由於大量使用元素選擇器是很慢的。
爲了給樣式設置做用域,Vue 會爲元素添加一個獨一無二的特性,例如 data-v-f3f3eg9
。而後修改選擇器,使得在匹配選擇器的元素中,只有帶這個特性纔會真正生效 (好比 button[data-v-f3f3eg9]
)。
問題在於大量的元素和特性組合的選擇器 (好比 button[data-v-f3f3eg9]
) 會比類和特性組合的選擇器慢,因此應該儘量選用類選擇器。
<template>
<button>X</button>
</template>
<style scoped>
button {
background-color: red;
}
</style>
<template>
<button class="btn btn-close">X</button>
</template>
<style scoped>
.btn-close {
background-color: red;
}
</style>
應該優先經過 prop 和事件進行父子組件之間的通訊,而不是 this.$parent
或改變 prop。
一個理想的 Vue 應用是 prop 向下傳遞,事件向上傳遞的。遵循這一約定會讓你的組件更易於理解。然而,在一些邊界狀況下 prop 的變動或 this.$parent
可以簡化兩個深度耦合的組件。
問題在於,這種作法在不少簡單的場景下可能會更方便。但請小心,不要爲了一時方便 (少寫代碼) 而犧牲數據流向的簡潔性 (易於理解)。
Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: '<input v-model="todo.text">'
})
Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
methods: {
removeTodo () {
var vm = this
vm.$parent.todos = vm.$parent.todos.filter(function (todo) {
return todo.id !== vm.todo.id
})
}
},
template: `
<span>
{{ todo.text }}
<button @click="removeTodo">
X
</button>
</span>
`
})
Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: `
<input
:value="todo.text"
@input="$emit('input', $event.target.value)"
>
`
})
Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: `
<span>
{{ todo.text }}
<button @click="$emit('delete')">
X
</button>
</span>
`
})
應該優先經過 Vuex 管理全局狀態,而不是經過 this.$root
或一個全局事件總線。
經過 this.$root
和/或全局事件總線管理狀態在不少簡單的狀況下都是很方便的,可是並不適用於絕大多數的應用。Vuex 提供的不只是一個管理狀態的中心區域,仍是組織、追蹤和調試狀態變動的好工具。
// main.js
new Vue({
data: {
todos: []
},
created: function () {
this.$on('remove-todo', this.removeTodo)
},
methods: {
removeTodo: function (todo) {
var todoIdToRemove = todo.id
this.todos = this.todos.filter(function (todo) {
return todo.id !== todoIdToRemove
})
}
}
})
// store/modules/todos.js
export default {
state: {
list: []
},
mutations: {
REMOVE_TODO (state, todoId) {
state.list = state.list.filter(todo => todo.id !== todoId)
}
},
actions: {
removeTodo ({ commit, state }, todo) {
commit('REMOVE_TODO', todo.id)
}
}
}
<!-- TodoItem.vue -->
<template>
<span>
{{ todo.text }}
<button @click="removeTodo(todo)">
X
</button>
</span>
</template>
<script>
import { mapActions } from 'vuex'
export default {
props: {
todo: {
type: Object,
required: true
}
},
methods: mapActions(['removeTodo'])
}
</script>