风格指南 beta
这里是官方的 Vue 特有代码的风格指南。如果在工程中使用 Vue,为了回避错误、小纠结和反模式,该指南是份不错的参考。不过我们也不确信风格指南的所有内容对于所有的团队或工程都是理想的。所以根据过去的经验、周围的技术栈、个人价值观做出有意义的偏差是可取的。
对于其绝大部分,我们也总体上避免就 JavaScript 或 HTML 的本身提出建议。我们不介意你是否使用分号或结尾的逗号。我们不介意你在 HTML 特性中使用单引号还是双引号。不过当我们发现在 Vue 的情景下有帮助的特定模式时,也会存在例外。
不久之后,我们还会提供操作层面的技巧。有的时候你只需要遵守规则,而我们会尽可能向你展示如何使用 ESLint 及其它自动化程序把操作层面弄得更简单。
最终,我们把所有的规则归为了四个大类:
规则归类
优先级 A:必要的
这些规则会帮你规避错误,所以学习并接受它们带来的全部代价吧。这里面可能存在例外,但应该非常少,且只有你同时精通 JavaScript 和 Vue 才可以这样做。
优先级 B:强烈推荐
这些规则能够在绝大多数工程中改善可读性和开发体验。即使你违反了,代码还是能照常运行,但例外应该尽可能少且有合理的理由。
优先级 C:推荐
当存在多个同样好的选项,选任意一个都可以确保一致性。在这些规则里,我们描述了每个选项并建议一个默认的选择。也就是说只要保持一致且理由充分,你可以随意在你的代码库中做出不同的选择。请务必给出一个好的理由!通过接受社区的标准,你将会:
- 训练你的大脑,以便更容易的处理你在社区遇到的代码;
- 不做修改就可以直接复制粘贴社区的代码示例;
- 能够经常招聘到和你编码习惯相同的新人,至少跟 Vue 相关的东西是这样的。
优先级 D:谨慎使用
有些 Vue 特性的存在是为了照顾极端情况或帮助老代码的平稳迁移。当被过度使用时,这些特性会让你的代码难于维护甚至变成 bug 的来源。这些规则是为了给有潜在风险的特性敲个警钟,并说明它们什么时候不应该使用以及为什么。
优先级 A 的规则:必要的 (规避错误)
组件名为多个单词 必要
组件名应该始终是多个单词的,根组件 App 除外。
这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。
反例
Vue.component('todo', {
// ...
})
export default {
name: 'Todo',
// ...
}
好例子
Vue.component('todo-item', {
// ...
})
export default {
name: 'TodoItem',
// ...
}
组件数据 必要
组件的 data 必须是一个函数。
当在组件中使用 data 属性的时候 (除了 new Vue 外的任何地方),它的值必须是返回一个对象的函数。
详解
当 data 的值是一个对象时,它会在这个组件的所有实例之间共享。想象一下,假如一个 TodoList 组件的数据是这样的:
data: {
listTitle: '',
todos: []
}
我们可能希望重用这个组件,允许用户维护多个列表 (比如分为购物、心愿单、日常事务等)。这时就会产生问题。因为每个组件的实例都引用了相同的数据对象,更改其中一个列表的标题就会改变其它每一个列表的标题。增删改一个待办事项的时候也是如此。
取而代之的是,我们希望每个组件实例都管理其自己的数据。为了做到这一点,每个实例必须生成一个独立的数据对象。在 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 定义应该尽量详细。
In committed code, prop definitions should always be as detailed as possible, specifying at least type(s).
反例
// 这样做只有开发原型系统时可以接受
props: ['status']
好例子
props: {
status: String
}
// 更好的做法!
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}
为 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>
为组件样式设置作用域 必要
对于应用来说,顶级 App 组件和布局组件中的样式可以是全局的,但是其它所有组件都应该是有作用域的。
这条规则只和单文件组件有关。你不一定要使用 scoped 特性。设置作用域也可以通过 CSS Modules,那是一个基于
class 的类似 BEM 的策略,当然你也可以使用其它的库或规约。
不管怎样,对于组件库,我们应该更倾向于选用基于 class 的策略而不是 scoped 特性。
这让覆写内部样式更容易:使用了常人可理解的 class 名称且没有太高的选择器优先级,而且不太会导致冲突。
详解
如果你和其他开发者一起开发一个大型工程,或有时引入三方 HTML/CSS (比如来自 Auth0),设置一致的作用域会确保你的样式只会运用在它们想要作用的组件上。
不止要使用 scoped 特性,使用唯一的 class 名可以帮你确保那些三方库的 CSS 不会运用在你自己的 HTML 上。比如许多工程都使用了 button、btn 或 icon 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` 特性 -->
<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>
私有属性名 必要
在插件、混入等扩展中始终为自定义的私有属性使用 $_ 前缀。并附带一个命名作用于以回避和其它作者的冲突 (比如 $_yourPluginName_)。
详解
Vue 使用 _ 前缀来定义其自身的私有属性,所以使用相同的前缀 (比如 _update) 有覆写实例属性的风险。即便你检查确认 Vue 当前版本没有用到这个属性名,也不能保证和将来的版本没有冲突。
对于 & 前缀来说,其在 Vue 生态系统中的目的是暴露给用户的一个特殊的实例属性,所以把它用于私有属性并不合适。
不过,我们推荐把这两个前缀结合为 &_,作为一个用户定义的私有属性的规约,以确保不会和 Vue 自身相冲突。
反例
var myGreatMixin = {
// ...
methods: {
update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
_update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
$update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
$_update: function () {
// ...
}
}
}
好例子
var myGreatMixin = {
// ...
methods: {
$_myGreatMixin_update: function () {
// ...
}
}
}
Priority B Rules: Strongly Recommended (Improving Readability)
Component files strongly recommended
Whenever a build system is available to concatenate files, each component should be in its own file.
This helps you to more quickly find a component when you need to edit it or review how to use it.
反例
Vue.component('TodoList', {
// ...
})
Vue.component('TodoItem', {
// ...
})
好例子
components/
|- TodoList.js
|- TodoItem.js
components/
|- TodoList.vue
|- TodoItem.vue
Single-file component filename casing strongly recommended
Filenames of single-file components should either be always PascalCase or always kebab-case.
PascalCase works best with autocompletion in code editors, as it’s consistent with how we reference components in JS(X) and templates, wherever possible. However, mixed case filenames can sometimes create issues on case-insensitive filesystems, which is why kebab-case is also perfectly acceptable.
反例
components/
|- mycomponent.vue
components/
|- myComponent.vue
好例子
components/
|- MyComponent.vue
components/
|- my-component.vue
Base component names strongly recommended
Base components (a.k.a. presentational, dumb, or pure components) that apply app-specific styling and conventions should all begin with a specific prefix, such as Base, App, or V.
详解
These components lay the foundation for consistent styling and behavior in your application. They may only contain:
- HTML elements,
- other
Base-prefixed components, and - 3rd-party UI components.
But they’ll never contain global state (e.g. from a Vuex store).
Their names often include the name of an element they wrap (e.g. BaseButton, BaseTable), unless no element exists for their specific purpose (e.g. BaseIcon). If you build similar components for a more specific
context, they will almost always consume these components (e.g. BaseButton may be used in ButtonSubmit).
Some advantages of this convention:
-
When organized alphabetically in editors, your app’s base components are all listed together, making them easier to identify.
-
Since component names should always be multi-word, this convention prevents you from having to choose an arbitrary prefix for simple component wrappers (e.g.
MyButton,VueButton). -
Since these components are so frequently used, you may want to simply make them global instead of importing them everywhere. A prefix makes this possible with Webpack:
var requireComponent = require.context("./src", true, /^Base[A-Z]/) requireComponent.keys().forEach(function (fileName) { var baseComponentConfig = requireComponent(fileName) baseComponentConfig = baseComponentConfig.default || baseComponentConfig var baseComponentName = baseComponentConfig.name || ( fileName .replace(/^.+\//, '') .replace(/\.\w+$/, '') ) Vue.component(baseComponentName, baseComponentConfig) })
反例
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
好例子
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
Single-instance component names strongly recommended
Components that should only ever have a single active instance should begin with the The prefix, to denote that there can be only one.
This does not mean the component is only used in a single page, but it will only be used once per page. These components never accept any props, since they are specific to your app, not their context within your app. If you find the need to add props, it’s a good indication that this is actually a reusable component that is only used once per page for now.
反例
components/
|- Heading.vue
|- MySidebar.vue
好例子
components/
|- TheHeading.vue
|- TheSidebar.vue
Tightly coupled component names strongly recommended
Child components that are tightly coupled with their parent should include the parent component name as a prefix.
If a component only makes sense in the context of a single parent component, that relationship should be evident in its name. Since editors typically organize files alphabetically, this also keeps these related files next to each other.
反例
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue
好例子
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue
Order of words in component names strongly recommended
Component names should start with the highest-level (often most general) words and end with descriptive modifying words.
详解
You may be wondering:
“Why would we force component names to use less natural language?”
In natural English, adjectives and other descriptors do typically appear before the nouns, while exceptions require connector words. For example:
- Coffee with milk
- Soup of the day
- Visitor to the museum
You can definitely include these connector words in component names if you’d like, but the order is still important.
Also note that what’s considered “highest-level” will be contextual to your app. For example, imagine an app with a search form. It may include components like this one:
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
As you might notice, it’s quite difficult to see which components are specific to the search. Now let’s rename the components according to the rule:
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputExcludeGlob.vue
|- SearchInputQuery.vue
|- SettingsCheckboxLaunchOnStartup.vue
|- SettingsCheckboxTerms.vue
Since editors typically organize files alphabetically, all the important relationships between components are now evident at a glance.
You might be tempted to solve this problem differently, nesting all the search components under a “search” directory, then all the settings components under a “settings” directory. We only recommend considering this approach in very large apps (e.g. 100+ components), for these reasons:
- It generally takes more time to navigate through nested sub-directories, than scrolling through a single
componentsdirectory. - Name conflicts (e.g. multiple
ButtonDelete.vuecomponents) make it more difficult to quickly navigate to a specific component in a code editor. - Refactoring becomes more difficult, because find-and-replace often isn’t sufficient to update relative references to a moved component.
反例
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
Self-closing components strongly recommended
Components with no content should be self-closing in single-file components, string templates, and JSX - but never in DOM templates.
Components that self-close communicate that they not only have no content, but are meant to have no content. It’s the difference between a blank page in a book and one labeled “This page intentionally left blank.” Your code is also cleaner without the unnecessary closing tag.
Unfortunately, HTML doesn’t allow custom elements to be self-closing - only official “void” elements. That’s why the strategy is only possible when Vue’s template compiler can reach the template before the DOM, then serve the DOM spec-compliant HTML.
反例
<!-- In single-file components, string templates, and JSX -->
<MyComponent></MyComponent>
<!-- In DOM templates -->
<my-component/>
好例子
<!-- In single-file components, string templates, and JSX -->
<MyComponent/>
<!-- In DOM templates -->
<my-component></my-component>
Component name casing in templates strongly recommended
Component names should always be PascalCase in single-file components and string templates - but kebab-case in DOM templates.
PascalCase has a few advantages over kebab-case:
- Editors can autocomplete component names in templates, because PascalCase is also used in JavaScript.
<MyComponent>is more visually distinct from a single-word HTML element than<my-component>, because there are two character differences (the two capitals), rather than just one (a hyphen).- If you use any non-Vue custom elements in your templates, such as a web component, PascalCase ensures that your Vue components remain distinctly visible.
Unfortunately, due to HTML’s case insensitivity, DOM templates must still use kebab-case.
反例
<!-- In single-file components and string templates -->
<mycomponent/>
<!-- In single-file components and string templates -->
<my-component/>
<!-- In single-file components and string templates -->
<myComponent/>
<!-- In DOM templates -->
<MyComponent></MyComponent>
好例子
<!-- In single-file components and string templates -->
<MyComponent/>
<!-- In DOM templates -->
<my-component></my-component>
Component name casing in JS/JSX strongly recommended
Component names in JS/JSX should always be PascalCase, though may be kebab-case inside strings for simpler applications that only use global component registration through Vue.component.
详解
In JavaScript, PascalCase is the convention for classes and prototype constructors - essentially, anything that can have distinct instances. Vue components also have instances, so it makes sense to also use PascalCase. As an added benefit, using PascalCase within JSX (and templates) allows readers of the code to more easily distinguish between components and HTML elements.
However, for applications that use only global component definitions via Vue.component, we recommend kebab-case instead. The reasons are:
- It’s rare that global components are ever referenced in JavaScript, so following a convention for JavaScript makes less sense.
- These applications always include many in-DOM components, where kebab-case must be used.
反例
Vue.component('myComponent', {
// ...
})
import myComponent from './MyComponent.vue'
export default {
name: 'myComponent',
// ...
}
export default {
name: 'my-component',
// ...
}
好例子
Vue.component('MyComponent', {
// ...
})
Vue.component('my-component', {
// ...
})
import MyComponent from './MyComponent.vue'
export default {
name: 'MyComponent',
// ...
}
Full-word component names strongly recommended
Component names should prefer full words over abbreviations.
The autocompletion in editors make the cost of writing longer names very low, while the clarity they provide is invaluable. Uncommon abbreviations, in particular, should always be avoided.
反例
components/
|- SdSettings.vue
|- UProfOpts.vue
好例子
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
Prop name casing strongly recommended
Prop names should always use camelCase during declaration, but kebab-case in templates and JSX.
We’re simply following the conventions of each language. Within JavaScript, camelCase is more natural. Within HTML, kebab-case is.
反例
props: {
'greeting-text': String
}
<WelcomeMessage greetingText="hi"/>
好例子
props: {
greetingText: String
}
<WelcomeMessage greeting-text="hi"/>
Multi-attribute elements strongly recommended
Elements with multiple attributes should span multiple lines, with one attribute per line.
In JavaScript, splitting objects with multiple properties over multiple lines is widely considered a good convention, because it’s much easier to read. Our templates and JSX deserve the same consideration.
反例
<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"
/>
Complex expressions in templates strongly recommended
Component templates should only include simple expressions, with more complex expressions refactored into computed properties or methods.
Complex expressions in your templates make them less declarative. We should strive to describe what should appear, not how we’re computing that value. Computed properties and methods also allow the code to be reused.
反例
{{
fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}
好例子
<!-- In a template -->
{{ normalizedFullName }}
// The complex expression has been moved to a computed property
computed: {
normalizedFullName: function () {
return this.fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}
}
Complex computed properties strongly recommended
Complex computed properties should be split into as many simpler properties as possible.
详解
Simpler, well-named computed properties are:
-
Easier to test
When each computed property contains only a very simple expression, with very few dependencies, it’s much easier to write tests confirming that it works correctly.
-
Easier to read
Simplifying computed properties forces you to give each value a descriptive name, even if it’s not reused. This makes it much easier for other developers (and future you) to focus in on the code they care about and figure out what’s going on.
-
More adaptable to changing requirements
Any value that can be named might be useful to the view. For example, we might decide to display a message telling the user how much money they saved. We might also decide to calculate sales tax, but perhaps display it separately, rather than as part of the final price.
Small, focused computed properties make fewer assumptions about how information will be used, so require less refactoring as requirements change.
反例
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
}
}
Quoted attribute values strongly recommended
Non-empty HTML attribute values should always be inside quotes (single or double, whichever is not used in JS).
While attribute values without any spaces are not required to have quotes in HTML, this practice often leads to avoiding spaces, making attribute values less readable.
反例
<input type=text>
<AppSidebar :style={width:sidebarWidth+'px'}>
好例子
<input type="text">
<AppSidebar :style="{ width: sidebarWidth + 'px' }">
Directive shorthands strongly recommended
Directive shorthands (: for v-bind: and @ for v-on:) should be used always or never.
反例
<input
v-bind:value="newTodoText"
:placeholder="newTodoInstructions"
>
<input
v-on:input="onInput"
@focus="onFocus"
>
好例子
<input
:value="newTodoText"
:placeholder="newTodoInstructions"
>
<input
v-bind:value="newTodoText"
v-bind:placeholder="newTodoInstructions"
>
<input
@input="onInput"
@focus="onFocus"
>
<input
v-on:input="onInput"
v-on:focus="onFocus"
>
Priority C Rules: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
Component/instance options order recommended
Component/instance options should be ordered consistently.
This is the default order we recommend for component options. They’re split into categories, so you’ll know where to add new properties from plugins.
-
Side Effects (triggers effects outside the component)
el
-
Global Awareness (requires knowledge beyond the component)
nameparent
-
Component Type (changes the type of the component)
functional
-
Template Modifiers (changes the way templates are compiled)
delimiterscomments
-
Template Dependencies (assets used in the template)
componentsdirectivesfilters
-
Composition (merges properties into the options)
extendsmixins
-
Interface (the interface to the component)
inheritAttrsmodelprops/propsData
-
Local State (local reactive properties)
datacomputed
-
Events (callbacks triggered by reactive events)
watch- Lifecycle Events (in the order they are called)
-
Non-Reactive Properties (instance properties independent of the reactivity system)
methods
-
Rendering (the declarative description of the component output)
template/renderrenderError
Element attribute order recommended
The attributes of elements (including components) should be ordered consistently.
This is the default order we recommend for component options. They’re split into categories, so you’ll know where to add custom attributes and directives.
-
Definition (provides the component options)
is
-
List Rendering (creates multiple variations of the same element)
v-for
-
Conditionals (whether the element is rendered/shown)
v-ifv-else-ifv-elsev-showv-cloak
-
Render Modifiers (changes the way the element renders)
v-prev-once
-
Global Awareness (requires knowledge beyond the component)
id
-
Unique Attributes (attributes that require unique values)
refkeyslot
-
Two-Way Binding (combining binding and events)
v-model
-
Other Attributes (all unspecified bound & unbound attributes)
-
Events (component event listeners)
v-on
-
Content (overrides the content of the element)
v-htmlv-text
Empty lines in component/instance options recommended
You may want to add one empty line between multi-line properties, particularly if the options can no longer fit on your screen without scrolling.
When components begin to feel cramped or difficult to read, adding spaces between multi-line properties can make them easier to skim again. In some editors, such as Vim, formatting options like this can also make them easier to navigate with the keyboard.
好例子
props: {
value: {
type: String,
required: true
},
focused: {
type: Boolean,
default: false
},
label: String,
icon: String
},
computed: {
formattedValue: function () {
// ...
},
inputClasses: function () {
// ...
}
}
// No spaces are also fine, as long as the component
// is still easy to read and navigate.
props: {
value: {
type: String,
required: true
},
focused: {
type: Boolean,
default: false
},
label: String,
icon: String
},
computed: {
formattedValue: function () {
// ...
},
inputClasses: function () {
// ...
}
}
Single-file component top-level element order recommended
Single-file components should always order template, script, and style tags consistently, with <style> last, because at least one of the other two is always necessary.
反例
<style>/* ... */</style>
<template>...</template>
<script>/* ... */</script>
<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>
<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>
好例子
<!-- ComponentA.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>
<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>
<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>
<!-- ComponentB.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>
Priority D Rules: Use with Caution (Potentially Dangerous Patterns)
v-if/v-if-else/v-else without key use with caution
It’s usually best to use key with v-if + v-else, if they are the same element type (e.g. both <div> elements).
By default, Vue updates the DOM as efficiently as possible. That means when switching between elements of the same type, it simply patches the existing element, rather than removing it and adding a new one in its place. This can have unintended side effects if these elements should not actually be considered the same.
反例
<div v-if="error">
Error: {{ error }}
</div>
<div v-else>
{{ results }}
</div>
好例子
<div v-if="error" key="search-status">
Error: {{ error }}
</div>
<div v-else key="search-results">
{{ results }}
</div>
<p v-if="error">
Error: {{ error }}
</p>
<div v-else>
{{ results }}
</div>
Element selectors with scoped use with caution
Element selectors should be avoided with scoped.
Prefer class selectors over element selectors in scoped styles, because large numbers of element selectors are slow.
详解
To scope styles, Vue adds a unique attribute to component elements, such as data-v-f3f3eg9. Then selectors are modified so that only matching elements with this attribute are selected (e.g. button[data-v-f3f3eg9]).
The problem is that large numbers of element-attribute selectors (e.g. button[data-v-f3f3eg9])
will be considerably slower than class-attribute selectors (e.g. .btn-close[data-v-f3f3eg9]),
so class selectors should be preferred whenever possible.
反例
<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>
Parent-child communication use with caution
Props and events should be preferred for parent-child component communication, instead of this.$parent or mutating props.
An ideal Vue application is props down, events up. Sticking to this convention makes your components much easier to understand. However, there are edge cases where prop mutation or this.$parent can simplify two components that are already
deeply coupled.
The problem is, there are also many simple cases where these patterns may offer convenience. Beware: do not be seduced into trading simplicity (being able to understand the flow of your state) for short-term convenience (writing less code).
反例
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>
`
})
Global state management use with caution
Vuex should be preferred for global state management, instead of this.$root or a global event bus.
Managing state on this.$root and/or using a global event bus can be convenient for very simple cases, but are not appropriate for most applications.
Vuex offers not only a central place to manage state, but also tools for organizing, tracking, and debugging state changes.
反例
// 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> 发现错误?想参与编辑?
在 Github 上编辑此页!