【風格指南】

這裏是官方的 Vue 特有代碼的風格指南。如果在工程中使用 Vue,爲了迴避錯誤、小糾結和反模式,該指南是份不錯的參考。不過我們也不確信風格指南的所有內容對於所有的團隊或工程都是理想的。所以根據過去的經驗、周圍的技術棧、個人價值觀做出有意義的偏差是可取的。

對於其絕大部分,我們也總體上避免就 JavaScript 或 HTML 的本身提出建議。我們不介意你是否使用分號或結尾的逗號。我們不介意你在 HTML 屬性中使用單引號還是雙引號。不過當我們發現在 Vue 的情景下有幫助的特定模式時,也會存在例外。

**不久之後,我們還會提供操作層面的技巧。**有的時候你只需要遵守規則,而我們會盡可能向你展示如何使用 ESLint 及其它自動化程序把操作層面弄得更簡單。

最終,我們把所有的規則歸爲了四個大類:

1. 規則歸類

1.1 優先級 A:必要的

這些規則會幫你規避錯誤,所以學習並接受它們帶來的全部代價吧。這裏面可能存在例外,但應該非常少,且只有你同時精通 JavaScript 和 Vue 纔可以這樣做。

1.2 優先級 B:強烈推薦

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

1.3 優先級 C:推薦

當存在多個同樣好的選項,選任意一個都可以確保一致性。在這些規則裏,我們描述了每個選項並建議一個默認的選擇。也就是說只要保持一致且理由充分,你可以隨意在你的代碼庫中做出不同的選擇。請務必給出一個好的理由!通過接受社區的標準,你將會:

  1. 訓練你的大腦,以便更容易的處理你在社區遇到的代碼;
  2. 不做修改就可以直接複製粘貼社區的代碼示例;
  3. 能夠經常招聘到和你編碼習慣相同的新人,至少跟 Vue 相關的東西是這樣的。

1.4 優先級 D:謹慎使用

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

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

2.1 組件名爲多個單詞👿

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

這樣做可以避免跟現有的以及未來的 HTML 元素相沖突,因爲所有的 HTML 元素名稱都是單個單詞的。

❌ 反例

// 反例 1
Vue.component('todo', {
  // ...
})

// 反例 2
export default {
  name: 'Todo',
  // ...
}

✔️ 好例子

// 好例子 1
Vue.component('todo-item', {
  // ...
})

// 好例子 2
export default {
  name: 'TodoItem',
  // ...
}

2.2 組件數據👿

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

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

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

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

取而代之的是,我們希望每個組件實例都管理其自己的數據。爲了做到這一點,每個實例必鬚生成一個獨立的數據對象。在 JavaScript 中,在一個函數中返回這個對象就可以了:

data: function () {
  return {
    listTitle: '',
    todos: []
  }
}

❌ 反例

// 反例1
Vue.component('some-comp', {
  data: {
    foo: 'bar'
  }
})

// 反例2
export default {
  data: {
    foo: 'bar'
  }
}

✔️ 好例子

// 好例子 1
Vue.component('some-comp', {
  data: function () {
    return {
      foo: 'bar'
    }
  }
})

// 好例子 2
// In a .vue file
export default {
  data () {
    return {
      foo: 'bar'
    }
  }
}

// 好例子 3
// 在一個 Vue 的根實例上直接使用對象是可以的,
// 因爲只存在一個這樣的實例。
new Vue({
  data: {
    foo: 'bar'
  }
})

2.3 Prop 定義👿

**Prop 定義應該儘量詳細。**在你提交的代碼中,prop 的定義應該儘量詳細,至少需要指定其類型。

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

  • 它們寫明瞭組件的 API,所以很容易看懂組件的用法;
  • 在開發環境下,如果向一個組件提供格式不正確的 prop,Vue 將會告警,以幫助你捕獲潛在的錯誤來源。

❌ 反例

// 這樣做只有開發原型系統時可以接受
props: ['status']

✔️ 好例子

// 好例子 1
props: {
  status: String
}

// 好例子 2
// 更好的做法!
props: {
  status: {
    type: String,
    required: true,
    validator: function (value) {
      return [
        'syncing',
        'synced',
        'version-conflict',
        'error'
      ].indexOf(value) !== -1
    }
  }
}

2.4 爲 v-for 設置鍵值👿

**總是用 key 配合 v-for。**在組件上總是必須用 key 配合 v-for,以便維護內部組件及其子樹的狀態。甚至在元素上維護可預測的行爲,比如動畫中的對象固化 (object constancy),也是一種好的做法。

假設你有一個待辦事項列表:

data: function () {
  return {
    todos: [
      {
        id: 1,
        text: '學習使用 v-for'
      },
      {
        id: 2,
        text: '學習使用 key'
      }
    ]
  }
}

然後你把它們按照字母順序排序。在更新 DOM 的時候,Vue 將會優化渲染把可能的 DOM 變更降到最低。即可能刪掉第一個待辦事項元素,然後把它重新加回到列表的最末尾。

這裏的問題在於,不要刪除仍然會留在 DOM 中的元素。比如你想使用 <transition-group> 給列表加過渡動畫,或想在被渲染元素是 <input> 時保持聚焦。在這些情況下,爲每一個項目添加一個唯一的鍵值 (比如 :key="todo.id") 將會讓 Vue 知道如何使行爲更容易預測。

根據我們的經驗,最好始終添加一個唯一的鍵值,以便你和你的團隊永遠不必擔心這些極端情況。也在少數對性能有嚴格要求的情況下,爲了避免對象固化,你可以刻意做一些非常規的處理。

❌ 反例

<ul>
  <li v-for="todo in todos">
    {{ todo.text }}
  </li>
</ul>

✔️ 好例子

<ul>
  <li
    v-for="todo in todos"
    :key="todo.id"
  >
    {{ todo.text }}
  </li>
</ul>

2.5 避免 v-if 和 v-for 用在一起

**永遠不要把 v-ifv-for 同時用在同一個元素上。**一般我們在兩種常見的情況下會傾向於這樣做:

  • 爲了過濾一個列表中的項目 (比如 v-for="user in users" v-if="user.isActive")。在這種情形下,請將 users 替換爲一個計算屬性 (比如 activeUsers),讓其返回過濾後的列表。
  • 爲了避免渲染本應該被隱藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers")。這種情形下,請將 v-if 移動至容器元素上 (比如 ulol)。

當 Vue 處理指令時,v-forv-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>

對於應用來說,頂級 App 組件和佈局組件中的樣式可以是全局的,但是其它所有組件都應該是有作用域的。

這條規則只和單文件組件有關。你不一定要使用 scoped attribute。設置作用域也可以通過 CSS Modules,那是一個基於 class 的類似 BEM 的策略,當然你也可以使用其它的庫或約定。

不管怎樣,對於組件庫,我們應該更傾向於選用基於 class 的策略而不是 scoped attribute。

這讓覆寫內部樣式更容易:使用了常人可理解的 class 名稱且沒有太高的選擇器優先級,而且不太會導致衝突。

如果你和其他開發者一起開發一個大型工程,或有時引入三方 HTML / CSS (比如來自 Auth0),設置一致的作用域會確保你的樣式只會運用在它們想要作用的組件上。

不止要使用 scoped attribute,使用唯一的 class 名可以幫你確保那些三方庫的 CSS 不會運用在你自己的 HTML 上。比如許多工程都使用了 buttonbtnicon class 名,所以即便你不使用類似 BEM 的策略,添加一個 app 專屬或組件專屬的前綴 (比如 ButtonClose-icon) 也可以提供很多保護。

❌ 反例

<template>
  <button class="btn btn-close">X</button>
</template>

<style>
.btn-close {
  background-color: red;
}
</style>

✔️ 好例子

<template>
  <button class="button button-close">X</button>
</template>

<!-- 使用 `scoped` attribute -->
<style scoped>
.button {
  border: none;
  border-radius: 2px;
}

.button-close {
  background-color: red;
}
</style>
<template>
  <button :class="[$style.button, $style.buttonClose]">X</button>
</template>

<!-- 使用 CSS Modules -->
<style module>
.button {
  border: none;
  border-radius: 2px;
}

.buttonClose {
  background-color: red;
}
</style>
<template>
  <button class="c-Button c-Button--close">X</button>
</template>

<!-- 使用 BEM 約定 -->
<style>
.c-Button {
  border: none;
  border-radius: 2px;
}

.c-Button--close {
  background-color: red;
}
</style>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章