# 代码规范


# 组件标签排序

组件中必须按照 <template>, <script>, <style> 排序

# script 标签内部结构顺序

name > components > props > data > computed > watch > filter > 钩子函数(钩子函数按其执行顺序) > methods

# 组件规范

# 组件名为多个单词

组件名应该始终是多个单词的,根组件 App 以及 <transition><component> 之类的 Vue 内置组件除外。

这样做可以避免跟现有的以及未来的 HTML 元素相冲突 (opens new window),因为所有的 HTML 元素名称都是单个单词的。

# 反例

Vue.component("todo", {
  // ...
});
1
2
3
export default {
  name: "Todo",
  // ...
};
1
2
3
4

# 好例子

Vue.component("todo-item", {
  // ...
});
1
2
3
export default {
  name: "TodoItem",
  // ...
};
1
2
3
4

# 组件数据

组件的 data 必须是一个函数。

当在组件中使用 data property 的时候 (除了 new Vue 外的任何地方),它的值必须是返回一个对象的函数。

data 的值是一个对象时,它会在这个组件的所有实例之间共享。想象一下,假如一个 TodoList 组件的数据是这样的:

data: {
  listTitle: '',
  todos: []
}
1
2
3
4

我们可能希望重用这个组件,允许用户维护多个列表 (比如分为购物、心愿单、日常事务等)。这时就会产生问题。因为每个组件的实例都引用了相同的数据对象,更改其中一个列表的标题就会改变其它每一个列表的标题。增删改一个待办事项的时候也是如此。

取而代之的是,我们希望每个组件实例都管理其自己的数据。为了做到这一点,每个实例必须生成一个独立的数据对象。在 JavaScript 中,在一个函数中返回这个对象就可以了:

data: function () {
  return {
    listTitle: '',
    todos: []
  }
}
1
2
3
4
5
6

# 反例

Vue.component("some-comp", {
  data: {
    foo: "bar",
  },
});
1
2
3
4
5
export default {
  data: {
    foo: "bar",
  },
};
1
2
3
4
5

# 好例子

Vue.component("some-comp", {
  data: function() {
    return {
      foo: "bar",
    };
  },
});
1
2
3
4
5
6
7
// In a .vue file
export default {
  data() {
    return {
      foo: "bar",
    };
  },
};
1
2
3
4
5
6
7
8
// 在一个 Vue 的根实例上直接使用对象是可以的,
// 因为只存在一个这样的实例。
new Vue({
  data: {
    foo: "bar",
  },
});
1
2
3
4
5
6
7

# Prop 定义

Prop 定义应该尽量详细。

在你提交的代码中,prop 的定义应该尽量详细,至少需要指定其类型。

细致的 prop 定义有两个好处:

  • 它们写明了组件的 API,所以很容易看懂组件的用法;
  • 在开发环境下,如果向一个组件提供格式不正确的 prop,Vue 将会告警,以帮助你捕获潜在的错误来源。

# 反例

// 这样做只有开发原型系统时可以接受
props: ["status"];
1
2

# 好例子

props: {
  status: String;
}
1
2
3
// 更好的做法!
props: {
  status: {
    type: String,
    required: true,
    validator: function (value) {
      return [
        'syncing',
        'synced',
        'version-conflict',
        'error'
      ].indexOf(value) !== -1
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#v-for 设置键值

总是用 key 配合 v-for

在组件上总是必须用 key 配合 v-for,以便维护内部组件及其子树的状态。甚至在元素上维护可预测的行为,比如动画中的对象固化 (object constancy) (opens new window),也是一种好的做法。

假设你有一个待办事项列表:

data: function () {
  return {
    todos: [
      {
        id: 1,
        text: '学习使用 v-for'
      },
      {
        id: 2,
        text: '学习使用 key'
      }
    ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后你把它们按照字母顺序排序。在更新 DOM 的时候,Vue 将会优化渲染把可能的 DOM 变更降到最低。即可能删掉第一个待办事项元素,然后把它重新加回到列表的最末尾。

这里的问题在于,不要删除仍然会留在 DOM 中的元素。比如你想使用 <transition-group> 给列表加过渡动画,或想在被渲染元素是 <input> 时保持聚焦。在这些情况下,为每一个项目添加一个唯一的键值 (比如 :key="todo.id") 将会让 Vue 知道如何使行为更容易预测。

根据我们的经验,最好始终添加一个唯一的键值,以便你和你的团队永远不必担心这些极端情况。也在少数对性能有严格要求的情况下,为了避免对象固化,你可以刻意做一些非常规的处理。

# 反例

<ul>
  <li v-for="todo in todos">
    {{ todo.text }}
  </li>
</ul>
1
2
3
4
5

# 好例子

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

# 避免 v-ifv-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>
1
2
3
4
5

将会经过如下运算:

this.users.map(function(user) {
  if (user.isActive) {
    return user.name;
  }
});
1
2
3
4
5

因此哪怕我们只渲染出一小部分用户的元素,也得在每次重渲染的时候遍历整个列表,不论活跃用户是否发生了变化。

通过将其更换为在如下的一个计算属性上遍历:

computed: {
  activeUsers: function () {
    return this.users.filter(function (user) {
      return user.isActive
    })
  }
}
1
2
3
4
5
6
7
<ul>
  <li v-for="user in activeUsers" :key="user.id">
    {{ user.name }}
  </li>
</ul>
1
2
3
4
5

我们将会获得如下好处:

  • 过滤后的列表会在 users 数组发生相关变化时才被重新运算,过滤更高效。
  • 使用 v-for="user in activeUsers" 之后,我们在渲染的时候遍历活跃用户,渲染更高效。
  • 解耦渲染层的逻辑,可维护性 (对逻辑的更改和扩展) 更强。

为了获得同样的好处,我们也可以把:

<ul>
  <li v-for="user in users" v-if="shouldShowUsers" :key="user.id">
    {{ user.name }}
  </li>
</ul>
1
2
3
4
5

更新为:

<ul v-if="shouldShowUsers">
  <li v-for="user in users" :key="user.id">
    {{ user.name }}
  </li>
</ul>
1
2
3
4
5

通过将 v-if 移动到容器元素,我们不会再对列表中的每个用户检查 shouldShowUsers。取而代之的是,我们只检查它一次,且不会在 shouldShowUsers 为否的时候运算 v-for

# 反例

<ul>
  <li v-for="user in users" v-if="user.isActive" :key="user.id">
    {{ user.name }}
  </li>
</ul>
1
2
3
4
5
<ul>
  <li v-for="user in users" v-if="shouldShowUsers" :key="user.id">
    {{ user.name }}
  </li>
</ul>
1
2
3
4
5

# 好例子

<ul>
  <li v-for="user in activeUsers" :key="user.id">
    {{ user.name }}
  </li>
</ul>
1
2
3
4
5
<ul v-if="shouldShowUsers">
  <li v-for="user in users" :key="user.id">
    {{ user.name }}
  </li>
</ul>
1
2
3
4
5

# 为组件样式设置作用域

对于应用来说,顶级 App 组件和布局组件中的样式可以是全局的,但是其它所有组件都应该是有作用域的。

不管怎样,对于组件库,我们应该更倾向于选用基于 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>
1
2
3
4
5
6
7
8
9

# 好例子

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 私有 property 名

使用模块作用域保持不允许外部访问的函数的私有性。如果无法做到这一点,就始终为插件、混入等不考虑作为对外公共 API 的自定义私有 property 使用 $_ 前缀。并附带一个命名空间以回避和其它作者的冲突 (比如 $_yourPluginName_)。

Vue 使用 _ 前缀来定义其自身的私有 property,所以使用相同的前缀 (比如 _update) 有覆写实例 property 的风险。即便你检查确认 Vue 当前版本没有用到这个 property 名,也不能保证和将来的版本没有冲突。

对于 $ 前缀来说,其在 Vue 生态系统中的目的是暴露给用户的一个特殊的实例 property,所以把它用于私有 property 并不合适。

不过,我们推荐把这两个前缀结合为 $_,作为一个用户定义的私有 property 的约定,以确保不会和 Vue 自身相冲突。

# 反例

var myGreatMixin = {
  // ...
  methods: {
    update: function() {
      // ...
    },
  },
};
1
2
3
4
5
6
7
8
var myGreatMixin = {
  // ...
  methods: {
    _update: function() {
      // ...
    },
  },
};
1
2
3
4
5
6
7
8
var myGreatMixin = {
  // ...
  methods: {
    $update: function() {
      // ...
    },
  },
};
1
2
3
4
5
6
7
8
var myGreatMixin = {
  // ...
  methods: {
    $_update: function() {
      // ...
    },
  },
};
1
2
3
4
5
6
7
8

# 好例子

var myGreatMixin = {
  // ...
  methods: {
    $_myGreatMixin_update: function() {
      // ...
    },
  },
};
1
2
3
4
5
6
7
8
// 甚至更好!
var myGreatMixin = {
  // ...
  methods: {
    publicMethod() {
      // ...
      myPrivateFunction();
    },
  },
};

function myPrivateFunction() {
  // ...
}

export default myGreatMixin;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 组件文件

只要有能够拼接文件的构建系统,就把每个组件单独分成文件。

当你需要编辑一个组件或查阅一个组件的用法时,可以更快速的找到它。

# 反例

Vue.component("TodoList", {
  // ...
});

Vue.component("TodoItem", {
  // ...
});
1
2
3
4
5
6
7

# 好例子

components/
|- TodoList.js
|- TodoItem.js
1
2
3
components/
|- TodoList.vue
|- TodoItem.vue
1
2
3

# 单文件组件文件的大小写

单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)。

单词大写开头对于代码编辑器的自动补全最为友好,因为这使得我们在 JS(X) 和模板中引用组件的方式尽可能的一致。然而,混用文件命名方式有的时候会导致大小写不敏感的文件系统的问题,这也是横线连接命名同样完全可取的原因。

# 反例

components/
|- mycomponent.vue
1
2
components/
|- myComponent.vue
1
2

# 好例子

components/
|- MyComponent.vue
1
2
components/
|- my-component.vue
1
2

# 基础组件名

应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 BaseAppV

这些组件为你的应用奠定了一致的基础样式和行为。它们可能包括:

  • HTML 元素
  • 其它基础组件
  • 第三方 UI 组件库

但是它们绝不会包括全局状态 (比如来自 Vuex store)。

它们的名字通常包含所包裹元素的名字 (比如 BaseButtonBaseTable),除非没有现成的对应功能的元素 (比如 BaseIcon)。如果你为特定的上下文构建类似的组件,那它们几乎总会消费这些组件 (比如 BaseButton 可能会用在 ButtonSubmit 上)。

这样做的几个好处:

  • 当你在编辑器中以字母顺序排序时,你的应用的基础组件会全部列在一起,这样更容易识别。

  • 因为组件名应该始终是多个单词,所以这样做可以避免你在包裹简单组件时随意选择前缀 (比如 MyButtonVueButton)。

  • 因为这些组件会被频繁使用,所以你可能想把它们放到全局而不是在各处分别导入它们。使用相同的前缀可以让 webpack 这样工作:

    var requireComponent = require.context(
      "./src",
      true,
      /Base[A-Z]\w+\.(vue|js)$/
    );
    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);
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

# 反例

components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
1
2
3
4

# 好例子

components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
1
2
3
4
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
1
2
3
4
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
1
2
3
4

# 单例组件名

只应该拥有单个活跃实例的组件应该以 The 前缀命名,以示其唯一性。

这不意味着组件只可用于一个单页面,而是每个页面只使用一次。这些组件永远不接受任何 prop,因为它们是为你的应用定制的,而不是它们在你的应用中的上下文。如果你发现有必要添加 prop,那就表明这实际上是一个可复用的组件,只是目前在每个页面里只使用一次。

# 反例

components/
|- Heading.vue
|- MySidebar.vue
1
2
3

# 好例子

components/
|- TheHeading.vue
|- TheSidebar.vue
1
2
3

# 紧密耦合的组件名

和父组件紧密耦合的子组件应该以父组件名作为前缀命名。

如果一个组件只在某个父组件的场景下有意义,这层关系应该体现在其名字上。因为编辑器通常会按字母顺序组织文件,所以这样做可以把相关联的文件排在一起。

你可以试着通过在其父组件命名的目录中嵌套子组件以解决这个问题。比如:

components/
|- TodoList/
   |- Item/
      |- index.vue
      |- Button.vue
   |- index.vue
1
2
3
4
5
6

或:

components/
|- TodoList/
   |- Item/
      |- Button.vue
   |- Item.vue
|- TodoList.vue
1
2
3
4
5
6

但是这种方式并不推荐,因为这会导致:

  • 许多文件的名字相同,使得在编辑器中快速切换文件变得困难。
  • 过多嵌套的子目录增加了在编辑器侧边栏中浏览组件所花的时间。

# 反例

components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
1
2
3
4
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue
1
2
3

# 好例子

components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
1
2
3
4
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue
1
2
3

# 组件名中的单词顺序

组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。

你可能会疑惑:

“为什么我们给组件命名时不多遵从自然语言呢?”

在自然的英文里,形容词和其它描述语通常都出现在名词之前,否则需要使用连接词。比如:

  • Coffee with milk
  • Soup of the day
  • Visitor to the museum

如果你愿意,你完全可以在组件名里包含这些连接词,但是单词的顺序很重要。

同样要注意在你的应用中所谓的“高级别”是跟语境有关的。比如对于一个带搜索表单的应用来说,它可能包含这样的组件:

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
1
2
3
4
5
6
7

你可能注意到了,我们很难看出来哪些组件是针对搜索的。现在我们来根据规则给组件重新命名:

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputExcludeGlob.vue
|- SearchInputQuery.vue
|- SettingsCheckboxLaunchOnStartup.vue
|- SettingsCheckboxTerms.vue
1
2
3
4
5
6
7

因为编辑器通常会按字母顺序组织文件,所以现在组件之间的重要关系一目了然。

你可能想换成多级目录的方式,把所有的搜索组件放到“search”目录,把所有的设置组件放到“settings”目录。我们只推荐在非常大型 (如有 100+ 个组件) 的应用下才考虑这么做,因为:

  • 在多级目录间找来找去,要比在单个 components 目录下滚动查找要花费更多的精力。
  • 存在组件重名 (比如存在多个 ButtonDelete 组件) 的时候在编辑器里更难快速定位。
  • 让重构变得更难,因为为一个移动了的组件更新相关引用时,查找/替换通常并不高效。

# 反例

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
1
2
3
4
5
6
7

# 好例子

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue
1
2
3
4
5
6
7

# 模板中的组件名大小写

对于绝大多数项目来说,在单文件组件和字符串模板中组件名应该总是 PascalCase 的——但是在 DOM 模板中总是 kebab-case 的。

PascalCase 相比 kebab-case 有一些优势:

  • 编辑器可以在模板里自动补全组件名,因为 PascalCase 同样适用于 JavaScript。
  • <MyComponent> 视觉上比 <my-component> 更能够和单个单词的 HTML 元素区别开来,因为前者的不同之处有两个大写字母,后者只有一个横线。
  • 如果你在模板中使用任何非 Vue 的自定义元素,比如一个 Web Component,PascalCase 确保了你的 Vue 组件在视觉上仍然是易识别的。

不幸的是,由于 HTML 是大小写不敏感的,在 DOM 模板中必须仍使用 kebab-case。

还请注意,如果你已经是 kebab-case 的重度用户,那么与 HTML 保持一致的命名约定且在多个项目中保持相同的大小写规则就可能比上述优势更为重要了。在这些情况下,在所有的地方都使用 kebab-case 同样是可以接受的。

# 反例

<!-- 在单文件组件和字符串模板中 -->
<mycomponent />
1
2
<!-- 在单文件组件和字符串模板中 -->
<myComponent />
1
2
<!-- 在 DOM 模板中 -->
<MyComponent></MyComponent>
1
2

# 好例子

<!-- 在单文件组件和字符串模板中 -->
<MyComponent />
1
2
<!-- 在 DOM 模板中 -->
<my-component></my-component>
1
2

或者

<!-- 在所有地方 -->
<my-component></my-component>
1
2

# JS/JSX 中的组件名大小写

JS/JSX 中的组件名应该始终是 PascalCase 的,尽管在较为简单的应用中只使用 Vue.component 进行全局组件注册时,可以使用 kebab-case 字符串。

在 JavaScript 中,PascalCase 是类和构造函数 (本质上任何可以产生多份不同实例的东西) 的命名约定。Vue 组件也有多份实例,所以同样使用 PascalCase 是有意义的。额外的好处是,在 JSX (和模板) 里使用 PascalCase 使得代码的读者更容易分辨 Vue 组件和 HTML 元素。

然而,对于通过 Vue.component 定义全局组件的应用来说,我们推荐 kebab-case 作为替代。原因是:

  • 全局组件很少被 JavaScript 引用,所以遵守 JavaScript 的命名约定意义不大。
  • 这些应用往往包含许多 DOM 内的模板,这种情况下是必须使用 kebab-case 的。

# 反例

Vue.component("myComponent", {
  // ...
});
1
2
3
import myComponent from "./MyComponent.vue";
1
export default {
  name: "myComponent",
  // ...
};
1
2
3
4
export default {
  name: "my-component",
  // ...
};
1
2
3
4

# 好例子

Vue.component("MyComponent", {
  // ...
});
1
2
3
Vue.component("my-component", {
  // ...
});
1
2
3
import MyComponent from "./MyComponent.vue";
1
export default {
  name: "MyComponent",
  // ...
};
1
2
3
4

# 完整单词的组件名

组件名应该倾向于完整单词而不是缩写。

编辑器中的自动补全已经让书写长命名的代价非常之低了,而其带来的明确性却是非常宝贵的。不常用的缩写尤其应该避免。

# 反例

components/
|- SdSettings.vue
|- UProfOpts.vue
1
2
3

# 好例子

components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
1
2
3

# Prop 名大小写

在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 kebab-case。

我们单纯的遵循每个语言的约定。在 JavaScript 中更自然的是 camelCase。而在 HTML 中则是 kebab-case。

# 反例

props: {
  'greeting-text': String
}
1
2
3

{% codeblock lang:html %} {% endcodeblock %}

# 好例子

props: {
  greetingText: String;
}
1
2
3

{% codeblock lang:html %} {% endcodeblock %}

# 多个 attribute 的元素

多个 attribute 的元素应该分多行撰写,每个 attribute 一行。

在 JavaScript 中,用多行分隔对象的多个 property 是很常见的最佳实践,因为这样更易读。模板和 JSX 值得我们做相同的考虑。

# 反例

<img src="https://vuejs.org/images/logo.png" alt="Vue Logo" />
1
<MyComponent foo="a" bar="b" baz="c" />
1

# 好例子

<img src="https://vuejs.org/images/logo.png" alt="Vue Logo" />
1
<MyComponent foo="a" bar="b" baz="c" />
1

# 模板中简单的表达式

组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。

复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。

# 反例

{{ fullName.split(' ').map(function (word) { return word[0].toUpperCase() +
word.slice(1) }).join(' ') }}
1
2

# 好例子

<!-- 在模板中 -->
{{ normalizedFullName }}
1
2
// 复杂表达式已经移入一个计算属性
computed: {
  normalizedFullName: function () {
    return this.fullName.split(' ').map(function (word) {
      return word[0].toUpperCase() + word.slice(1)
    }).join(' ')
  }
}
1
2
3
4
5
6
7
8

# 简单的计算属性

应该把复杂计算属性分割为尽可能多的更简单的 property。

更简单、命名得当的计算属性是这样的:

  • 易于测试

    当每个计算属性都包含一个非常简单且很少依赖的表达式时,撰写测试以确保其正确工作就会更加容易。

  • 易于阅读

    简化计算属性要求你为每一个值都起一个描述性的名称,即便它不可复用。这使得其他开发者 (以及未来的你) 更容易专注在他们关心的代码上并搞清楚发生了什么。

  • 更好的“拥抱变化”

    任何能够命名的值都可能用在视图上。举个例子,我们可能打算展示一个信息,告诉用户他们存了多少钱;也可能打算计算税费,但是可能会分开展现,而不是作为总价的一部分。

    小的、专注的计算属性减少了信息使用时的假设性限制,所以需求变更时也用不着那么多重构了。

# 反例

computed: {
  price: function () {
    var basePrice = this.manufactureCost / (1 - this.profitMargin)
    return (
      basePrice -
      basePrice * (this.discountPercent || 0)
    )
  }
}
1
2
3
4
5
6
7
8
9

# 好例子

computed: {
  basePrice: function () {
    return this.manufactureCost / (1 - this.profitMargin)
  },
  discount: function () {
    return this.basePrice * (this.discountPercent || 0)
  },
  finalPrice: function () {
    return this.basePrice - this.discount
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 带引号的 attribute 值

非空 HTML attribute 值应该始终带引号 (单引号或双引号,选你 JS 里不用的那个)。

在 HTML 中不带空格的 attribute 值是可以没有引号的,但这鼓励了大家在特征值里不写空格,导致可读性变差。

# 反例

<input type="text" />
1
<AppSidebar :style={width:sidebarWidth+'px'}>
1

# 好例子

<input type="text" />
1
<AppSidebar :style="{ width: sidebarWidth + 'px' }"></AppSidebar>
1

# 指令缩写

指令缩写 (用 : 表示 v-bind:、用 @ 表示 v-on: 和用 # 表示 v-slot:) 应该要么都用要么都不用。

# 反例

<input v-bind:value="newTodoText" :placeholder="newTodoInstructions" />
1
<input v-on:input="onInput" @focus="onFocus" />
1
<template v-slot:header>
  <h1>Here might be a page title</h1>
</template>

<template #footer>
  <p>Here's some contact info</p>
</template>
1
2
3
4
5
6
7

# 好例子

<input :value="newTodoText" :placeholder="newTodoInstructions" />
1
<input v-bind:value="newTodoText" v-bind:placeholder="newTodoInstructions" />
1
<input @input="onInput" @focus="onFocus" />
1
<input v-on:input="onInput" v-on:focus="onFocus" />
1
<template v-slot:header>
  <h1>Here might be a page title</h1>
</template>

<template v-slot:footer>
  <p>Here's some contact info</p>
</template>
1
2
3
4
5
6
7
<template #header>
  <h1>Here might be a page title</h1>
</template>

<template #footer>
  <p>Here's some contact info</p>
</template>
1
2
3
4
5
6
7

# 其他

  1. 尽量不要手动操作 DOM 因使用 Vue 框架,所以在项目开发中尽量使用 Vue 的数据驱动更新 DOM,尽量(不到万不得已)不要手动操作 DOM,包括:增删改 dom 元素、以及更改样式、添加事件等。

  2. 删除无用代码 因使用了 git/svn 等代码版本工具,对于无用代码必须及时删除,例如:一些调试的 console 语句、无用的弃用功能代码。

Last Updated: 6/6/2022, 3:06:15 PM