精读 Vue 官方文档系列 🎉
必要的
data 必须是一个函数
为了防止组件在复用时多组件实例共享一份数据源而带来的隐患,组件的 data 选项应该是一个函数,用来每次实例化时返回一个独立的对象。
因为
new Vue({})只会被实例化一次,所以你可以在根实例上使用对象的方式。
new Vue({
data:{
bar:'foo'
},
el:'#app'
});
Prop 定义尽可能细致
至少要包含“类型”声明。 细致的 Prop 有以下好处:
- 规范组件文档。
- 类型检测与提示,减少潜在错误。
总是为 v-for 设置 key
可以更好的帮助 Vue 保维护子组件内部的状态与 DOM 元素对象的复用。
避免 v-for 与 v-if 使用在一起
由于 v-for 的优先级高于 v-if,所以在每次遍历时都会执行一次条件判断。
更好的方式:
- 是否可以将
v-if提取到容器外部?将其作为v-for的前置条件。 - 循环之前就已经对数据进行了过滤。
为样式设置作用域
- 对于应用来说,顶级的 App 组件以及布局组件中的样式应该是全局的。
- 对于 UI 组件库来说,其组件的样式应该是基于 class 策略,例如选用
BEM的方式,这样组件库的使用者就可以按照常规的方式来覆写样式,而不会因为 CSS 作用域、选择器优先级等心智负担。 - 对于常规的 Vue 单文件组件,总应该是去开启
scopedattribute 实现 CSS Module。
【拓展阅读】
CSS 这门样式语言从被设计出来时一直都是全局作用域。而随着 Web 应用的快速发展,样式命名冲突的问题越来越频发,每次定义一个选择器名称时,都要考虑到是否在别的样式文件中存在了同名的情况,这无疑为开发人员增加了额外的开发成本和潜在问题。为此,我们希望得到一个理想的模型,例如在编写样式时,我们可以随意命名,只需要保证选择器语义性、可读性,而不必担心它是否会样式命名冲突。
现在,带着这个目标,并目睹了 JavaScript 社区模块化方案的成熟, CSS 社区也提出了类似的概念 —— CSS Module。
CSS Module 既 CSS 模块化,目前还没有一个统一的官方概念,它更多的是从工程实施的角度出发,如何实现样式的作用域隔离,避免多个组件之间的样式污染,在一个独立的组件内编写样式,开发人员无需为样式冲突产生额外的心里负担。
目前实现 CSS Module 的方案主要有以下几点:
BEM- Vue 的
scoped - CSS Modules
- CSS in js
BEM
通过人工的方式来规范命名风格。可以更方便的从 CSS 中获取所关联的 HTML 层级与结构关系,并同时产生了一定的作用域隔离效果(用很长的有规则的命名来尽量实现选择器名称的唯一)。
Vue 的 scoped
对于带有作用域标识的组件,Vue 会将组件内所有的元素都加上一个全局唯一的属性选择器。例如 [data-v-5298c6bf],然后组件内的所有 CSS 选择器名也会加上这段属性选择器,这样组件内的 CSS 就只会作用于当前组件中的元素,而不会影响组件作用域外的样式。
<template>
<header class="header">header</header>
</template>
<style scoped>
.header {
background-color: green;
}
</style>
编译后:
<header class="header" data-v-5298c6bf>header</header>
<style>
.header[data-v-5298c6bf] {
background-color: green;
}
</style>
CSS Modules
由 Webpack css-loader 提供的 module 特性。编译构建时用生成的 hash 来映射和替换掉对应的真实 CSS 选择器名称,这样便可以从命名的方式上实现对 CSS 样式的作用域隔离,保证每个 CSS 选择器都是唯一。
//style.css
.title {
color:red
}
编译后
{
"title":"title_DW78T"
}
而具体使用则看不同的场景,例如在 React 中会像一般导入模板的方式来导入样式文件,你只需要将导入的样式文件按照 xxx.module.css 的风格进行命名即可,然后使用 className={styles.title} 的方式来使用。
Vue 默认采用的是 scoped 方式,如果需要切换为 CSS Modules 方式,则可以将 scoped 替换为 module,在 SFC 中样式与 HTML 统一写在一起,因此 Vue 会自动将生产好的 CSS 对象注入到组件的一个名叫 $style 的计算属性,你可以在你的模块中使用动态 class 绑定:
<template>
<p :class="$style.red">
This should be red
</p>
</template>
<style module>
.red {
color: red;
}
.bold {
font-weight: bold;
}
</style>
CSS in JS
通常来说,像 Styled-components 这个 CSS in JS 的库对于 CSS 的作用域实现方式与 CSS Modules 并没有什么不同,都是用唯一的 hash 作为选择器名称。但也存在另一种不同风格的路线,例如 Radium 这个 CSS in JS 库,它完全将 CSS 样式内联 (inline) 到标签上,这样每个标签都是一个独立的作用域。
私有 property 名
私有 property 名应该要附带一个命名空间以回避和其它作者的冲突。
var myGreatMixin = {
// ...
methods: {
$_myGreatMixin_update: function () {
// ...
}
}
}
强烈推荐
单文件组件文件命名
推荐使用 PascalCase 方式命名。
基础组件名
应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base、App 或 V。
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
这里需要指出一个问题,有很多的 UI 组件库并在为其组件文件命名的时候并没有采用这规范,但是都相同的会用这个规范为组件实例进行命名,例如 AntV。
const Card = defineComponent({
name: 'ACard',
});
紧密耦合的组件名
和父组件紧密耦合的子组件应该以父组件名作为前缀命名。
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
组件文件命名的单词顺序
组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。 简单来说,就是你的组件名称在前,这个组件的功能描述在后,因为按照编辑器的排列顺序,会让组件文件之间更易于浏览。
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue
反例:
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
自闭合组件
所谓的自合并组件,实际上就是 HTML 标签中的单标签,这个在规范中又被称之为 空元素。
由于浏览器上只支持原生标签的单标签书写,所以,对那些不会被 Vue 编译器处理的模板,请一定不要再其中书写自定义标签的单标签形式,因为这不符合 HTML 的标准规范。
主要场景就是在纯 JavaScript 驱动的环境下,直接在一个 HTML 文件中使用全局组件,例如:
<!---index.html-->
<div id="app">
<!---这是不好的🙅♀️--->
<side-nav />
</div>
如果是在字符串模板、X-template、SFC 中,这些会编译器处理进行转换的情况下,则不做硬性要求。
模板中的组件名大小写
由于 HTML 对大小写不敏感,所以在基于最通用的场景考量,推荐在模板中使用 kebab-case 的方式来使用自定义组件。
JS/JSX 中的组件名大小写
JS/JSX 中的组件名应该始终是 PascalCase 。
js
Vue.component('MyComponent',{});
jsx
<MyComponent/>
完整单词的组件名
编辑器中的自动补全已经让书写长命名的代价非常之低了,而其带来的明确性却是非常宝贵的。不常用的缩写尤其应该避免。
反例
components/
|- SdSettings.vue
|- UProfOpts.vue
好例子
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
Prop 名称的大小写
组件逻辑中的 props 始终都应该使用 camelCase,而在模板与 JSX 中使用应该是用 kebab-case 方式。
我们单纯的遵循每个语言的约定。在 JavaScript 中更自然的是 camelCase。而在 HTML 中则是 kebab-case。
js
Vue.component("MyComponent", {props:["postTitle"]});
jsx
<MyComponent post-title='title' />
多 attribute 换行
多个 attribute 的元素应该分多行撰写,每个 attribute 一行。
模板中简单的表达式
组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。
简单的计算属性
应该把复杂的计算属性分割为尽可能多的更简单的计算属性。
如果一个计算属性中会用到多个响应式值来参与计算,并且每个响应式值的背后还需要再经过简单的计算才能得出结果,对应此种情况,应该继续拆分计算属性的粒度。
最简单的方式就是观察你代码中的计算属性,如果存在有 3,4 行以上的代码,就应该要考虑进行分割。
引号的 attribute 值
非空 HTML attribute 值应该始终带引号,增强代码的可读性。
<input type="text">
<AppSidebar :style="{ width: sidebarWidth + 'px' }">
指令缩写
#->v-slot@->v-on:->v-bind
推荐
元素 attribute 的顺序【自定】
根据我们已有的编程经验,我们推荐将 Vue 内置的 attribute 写在组件标签的前面,用户自定义的 attribute 书写在后面。这有助于我们更早获知 Vue 的内置行为。
<component
:is="my-component"
v-if="isRead"
ref="target"
:content="post-content"
/>
谨慎使用
没有在 v-if/v-else-if/v-else 中使用 key
如果一组 v-if + v-else 的元素类型相同,最好使用 key (比如两个
<div
v-if="error"
key="search-status"
>
错误:{{ error }}
</div>
<div
v-else
key="search-results"
>
{{ results }}
</div>
默认情况下,Vue 会尽可能高效的更新 DOM。这意味着其在相同类型的元素之间切换时,会修补已存在的元素,而不是将旧的元素移除然后在同一位置添加一个新元素。如果本不相同的元素被识别为相同,则会出现意料之外的结果。
scoped 中的元素选择器
元素选择器应该避免在 scoped 中出现。
在 scoped 样式中,类选择器比元素选择器更好,因为大量使用元素选择器是很慢的。
为了给样式设置作用域,Vue 会为元素添加一个独一无二的 attribute,例如 data-v-f3f3eg9。然后修改选择器,使得在匹配选择器的元素中,只有带这个 attribute 才会真正生效 (比如 button[data-v-f3f3eg9])。 问题在于大量的元素和 attribute 组合的选择器 (比如 button[data-v-f3f3eg9]) 会比类和 attribute 组合的选择器慢,所以应该尽可能选用类选择器。
隐性的父子组件传值
应该优先通过 prop 和事件进行父子组件之间的通信,而不是 this.$parent 或直接在子组件中变更父组件传递的 prop。
使用 Prop 向下传递数据,使用事件向上传递事件触发值的更改。
非 Flux 的全局状态管理
应该优先通过 Vuex 管理全局状态,而不是通过 this.$root 或一个全局事件总线。
理由非常简单,Vuex 提供的功能更多,解决的问题更全面。
通过 this.$root 和或全局事件总线管理状态在很多简单的情况下都是很方便的,但是并不适用于绝大多数的应用。
Vuex 是 Vue 的官方类 flux 实现,其提供的不仅是一个管理状态的中心区域,还是组织、追踪和调试状态变更的好工具。它很好地集成在了 Vue 生态系统之中 (包括完整的 Vue DevTools 支持)。