心動不如行動的Vue 編碼風格指南

規則歸類

優先級 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 fileexport

default

{ 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>
心動不如行動的Vue 編碼風格指南

<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>

收藏

舉報

爭渡渡鳥


分享到:


相關文章: