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