Vue 3 风格指南解析
其实好多人即便使用vue三五年了, 也没有看过vue风格指南
根据官方描述, 目前vue3的风格指南后续将继续完善, 以适用
Composition API
风格代码
规则概述
规则分为 A / B / C / D 四个优先级
-
A: 必要规则
除非你精通Vue和JS, 否则请遵守
-
B: 强烈推荐
不这样做容易造人嫌弃(手动狗头), 如果没有充分的理由不这样做, 请遵守
-
C: 推荐
编码风格保持一致, 会让你更容易融入社区和团队开发中来
-
D: 谨慎使用
基本上代码能跑, 但不太好(主要是性能)
A 级: 必要规则
-
组件名使用多个单词
可以避免和html元素冲突
正例:
<!-- 预编译模板 --> <TodoItem /> <!-- dom模板 --> <todo-item></todo-item>
反例:
<!-- 预编译模板 --> <Item /> <!-- dom模板 --> <item></item>
注:
-
预编译模板和dom模板的区别: 可简单理解为一个在vue文件中, 一个在html文件中
-
个人推荐统一使用kebab-case写法, 所有地方适用, 且有些字母大小写不易区分
如小写L, 大写的i, 和数字1, 费劲
-
-
详细的prop定义
至少应当指定类型, 可以更好的利用IDE的代码提示, vue的开发环境也会有警告提示. 而且有利于代码维护
正例:
props: { status: String } // 最好这样 props: { status: { type: String, required: true, validator: value => { return [ 'syncing', 'synced', 'version-conflict', 'error' ].includes(value) } } } // 见注1 const props = defineProps({ // 文本对齐方式 align: String as PropType<'left' | 'center' | 'right'> })
反例:
// 能用, 但不太好, 容易让人头大 props: ['status']
注:
- 这条是我自己补充的, 我一般写validator, 让IDE来完成提示即可
- vue3支持类型声明定义props, 对此不做评价, 由于默认值用起来比较麻烦, 所以我目前很少使用
-
v-for添加key
记得唯一, 如果没有手动生成一个, 不要使用下标作为key
利于性能, 如果不提供的话,
<transition-group>
动画列表排序可能产生bug正例:
<ul> <li v-for="user in activeUsers" :key="user.id" > {{ user.name }} </li> </ul> <ul> <template v-for="user in users" :key="user.id"> <li v-if="user.isActive"> {{ user.name }} </li> </template> </ul>
反例:
<ul> <li v-for="user in users" v-if="user.isActive" :key="user.id"> {{ user.name }} </li> </ul>
还要注意一点: 反例中v-if与v-for混用, 更好的写法见 正例 中第二种用法
-
组件样式作用域
style标签尽量设置作用域, 推荐scoped, 当然也可以使用CSS Modules或其他约束(如BEM)
正例:
<template> <button class="button button-close">×</button> </template> <!-- 使用scoped属性 --> <style scoped> .button { border: none; border-radius: 2px; } .button-close { background-color: red; } </style>
<template> <button :class="[$style.button, $style.buttonClose]">×</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">×</button> </template> <!-- 使用 BEM 约定 --> <style> .c-Button { border: none; border-radius: 2px; } .c-Button--close { background-color: red; } </style>
B 级: 强烈推荐
-
把组件拆分成文件
每个组件都应该在自己的文件中, 有利于IDE提示以及维护
正例:
components/ |- TodoList.js |- TodoItem.js
components/ |- TodoList.vue |- TodoItem.vue
反例:
app.component('TodoList', { // ... }) app.component('TodoItem', { // ... })
注: 我比较推荐文件名全部使用中划线(kebab-case)命名, 原因上面说过一次了, 大小写和数字在UI字体中更容易混淆
-
单文件组件文件名大小写
推荐PascalCase或kebab-case
PascalCase在IDE加持下没什么优势, 而且文档中补充, 有些文件系统是不区分大小写的(我没遇到过), 会产生问题
就不贴示例了, 篇幅太长了
-
基础组件名
应当以特定前缀开头, 如
Base
/App
/V
基础组件定义: 仅包含HTML元素 / 其他基本组件 / 第三方UI组件, 且不包含Vuex或Pinia的状态管理, 由于基础组件使用频繁, 建议全局注册
正例:
components/ |- BaseButton.vue |- BaseTable.vue |- BaseIcon.vue
components/ |- AppButton.vue |- AppTable.vue |- AppIcon.vue
components/ |- VButton.vue |- VTable.vue |- VIcon.vue
反例:
components/ |- MyButton.vue |- VueTable.vue |- Icon.vue
注:
官方文档中举了一个组件批量注册的例子, 但vite不适用, 而且会造成WebStorm不能正确提示, 因此我使用此方案
另外推荐一个前缀
ly
, 意为领域 -
单例组件名称
只应该拥有单个活跃实例的组件应该以
The
前缀命名,以示其唯一性。这不意味着组件只可用于一个单页面,而是每个页面只使用一次。这些组件永远不接受任何 prop,因为它们是为你的应用定制的,而不是它们在你的应用中的上下文。如果你发现有必要添加 prop,那就表明这实际上是一个可复用的组件,只是目前在每个页面里只使用一次。
注: 并不苟同, 不抄文档了
-
紧密耦合的组件名称
和父组件紧密耦合的子组件应该以父组件名作为前缀命名。
如果一个组件只在某个父组件的场景下有意义,这层关系应该体现在其名字上。因为编辑器通常会按字母顺序组织文件,所以这样做可以把相关联的文件排在一起。
正例:
components/ |- TodoList.vue |- TodoListItem.vue |- TodoListItemButton.vue
components/ |- SearchSidebar.vue |- SearchSidebarNavigation.vue
反例:
components/ |- TodoList.vue |- TodoItem.vue |- TodoButton.vue
components/ |- SearchSidebar.vue |- NavigationForSearchSidebar.vue
-
组件名中的单词排序
组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。
有利于代码提示, btn前缀的组件如btn-search / btn-reset / btn-submit等按钮, 在众多文件中可以起到分类的作用, 而且在编码过程中通过btn前缀来过滤IDE的提示结果会很方便, 即便是忘掉search/reset/submit等后缀也能更快想起来, 避免思路被打断
正例:
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
-
自闭合标签
dom模板中不要使用自闭合标签, 而单文件模板, 预编译模板, jsx中推荐自闭合写法
自闭合写法目的是简洁, 而不是强制
正例:
<!-- 单文件组件, 字符串模板, JSX --> <MyComponent/>
<!-- dom模板 --> <my-component></my-component>
-
模板中组件名大小写
概要:
单文件组件, 字符串模板, jsx中使用大驼峰(PascalCase), dom模板中使用中划线(kebab-case)
但如果已经习惯了中划线了, 或者有项目约定, 则统一中划线命名即可
-
JS / JSX中组件名大小写
JS/ JSX中的组件名称应该始终是 PascalCase
但通过
app.component
注册组件时, 应当使用中划线我们不用jsx, 不好评价, 举例见文档
-
组件名称避免缩写
这个好说, 因为缩写并不能减少什么工作量, 反而会引起歧义
但是对于上方举例中的btn这种通用的缩写我觉得没必要使用全拼
-
Prop命名大小写
入乡随俗, 在js中使用驼峰
在模板中使用中划线(当然如果有项目约定, 则可以统一使用小驼峰)
正例:
props: { greetingText: String }
// SFC中, 两种均可, 但应当统一 <WelcomeMessage greeting-text="hi"/> <WelcomeMessage greetingText="hi"/>
// dom模板 <welcome-message greeting-text="hi"></welcome-message>
个人推荐SFC中组件名和prop和attribute全都小写
-
多个 attribute 的元素
每行一个, 易于阅读, 模板和jsx中均推荐
正例:
<MyComponent foo="a" bar="b" baz="c" />
<img src="https://vuejs.org/images/logo.png" alt="Vue Logo" >
反例:
<img src="https://vuejs.org/images/logo.png" alt="Vue Logo"> <MyComponent foo="a" bar="b" baz="c"/>
我意见是闭合标签不另起一行, 而且对齐方式不同(WebStorm默认对齐), 感觉这样更有层次感:
<el-input v-model="model" clearable disabled/>
-
不在模板中使用复杂表达式
组件模板应该只包含简单的表达式,将更复杂的表达式重构为计算属性或方法。
模板中的复杂表达式会降低它们的声明性。我们应该努力描述应该出现什么,而不是我们*如何计算该值。*计算属性和方法还允许重用代码。
正例:
<!-- 模板中使用 --> {{ normalizedFullName }}
// 使用计算属性完成复杂表达式 computed: { normalizedFullName() { return this.fullName.split(' ') .map(word => word[0].toUpperCase() + word.slice(1)) .join(' ') } }
反例:
{{ fullName.split(' ').map((word) => { return word[0].toUpperCase() + word.slice(1) }).join(' ') }}
-
计算属性应当尽可能拆分
好处:
- 更容易测试
- 更容易阅读
- 更容易应对需求变更
computed: { price() { const basePrice = this.manufactureCost / (1 - this.profitMargin) return ( basePrice - basePrice * (this.discountPercent || 0) ) } }
computed: { basePrice() { return this.manufactureCost / (1 - this.profitMargin) }, discount() { return this.basePrice * (this.discountPercent || 0) }, finalPrice() { return this.basePrice - this.discount } }
注: 应该是想表达避免多个computed使用相同的代码片段, 到时候需求变了都不知道要改哪些地方, 而且可能会扩大测试范围1
-
attribute值应当带引号
非空 HTML attribute 值应该始终带引号 (单引号或双引号,以 JS 中未使用的为准)。
在 HTML 中不带空格的 attribute 值是可以没有引号的,但这鼓励了大家在特征值里不写空格,导致可读性变差。
正例:
<input type="text"> <AppSidebar :style="{ width: sidebarWidth + 'px' }">
反例:
<input type=text> <AppSidebar :style={width:sidebarWidth+'px'}>
-
指令缩写
要么都用, 要么都不用
缩写如下:
v-bind:
缩写:
v-on:
缩写@
v-slot:
缩写#
个人建议都用
C 级: 推荐
-
组件 / 实例的选项的顺序
组件/实例的选项应该有统一的顺序, 推荐顺序如下:
-
元素attribute的顺序
-
组件/实例选项中的空行
个人并推荐不加空行, 仅
<script setup>
时推荐 -
单文件组件顶层元素顺序
即
<script>
、<template>
和<style>
三者之间的顺序官方推荐
<style>
标签放最后, 剩下两个随意我习惯了vue2时写法template在前, 但官方demo一般script在前, 尝试适应中
D 级: 谨慎使用
-
scoped
中的元素选择器为了给样式设置作用域,Vue 会为元素添加一个独一无二的 attribute,例如
data-v-f3f3eg9
。然后修改选择器,使得在匹配选择器的元素中,只有带这个 attribute 才会真正生效 (比如button[data-v-f3f3eg9]
)。问题在于大量的元素和 attribute 组合的选择器 (比如
button[data-v-f3f3eg9]
) 会比类和 attribute 组合的选择器 (比如.btn-close[data-v-f3f3eg9]
) 慢,所以应该尽可能选用类选择器。正例:
<template> <button class="btn btn-close">×</button> </template> <style scoped> .btn-close { background-color: red; } </style>
反例:
<template> <button>×</button> </template> <style scoped> button { background-color: red; } </style>
-
优先通过 prop 和 emits 进行父子组件之间的通信
不要使用**
this.$parent
** 或修改props的值来达到通讯目的一个理想的 Vue 应用是 prop 向下传递,事件向上传递的。遵循这一约定会让你的组件更易于理解。然而,在一些边界情况下 prop 的变更或
this.$parent
能够简化两个深度耦合的组件。问题在于,这种做法在很多简单的场景下可能会更方便。但请当心,不要为了一时方便 (少写代码) 而牺牲数据流向的简洁性 (易于理解)
正例:
app.component('TodoItem', { props: { todo: { type: Object, required: true } }, emits: ['input'], template: ` <input :value="todo.text" @input="$emit('input', $event.target.value)" > ` })
app.component('TodoItem', { props: { todo: { type: Object, required: true } }, emits: ['delete'], template: ` <span> {{ todo.text }} <button @click="$emit('delete')"> × </button> </span> ` })
反例:
app.component('TodoItem', { props: { todo: { type: Object, required: true } }, template: '<input v-model="todo.text">' })
app.component('TodoItem', { props: { todo: { type: Object, required: true } }, methods: { removeTodo() { this.$parent.todos = this.$parent.todos.filter( (todo) => todo.id !== vm.todo.id ) } }, template: ` <span> {{ todo.text }} <button @click="removeTodo"> × </button> </span> ` })
再聊点
-
关于命名方式
我推荐在(模板 / 文件名)中优先使用中划线命名, 因为除了jsx, 都能用, 而我不用jsx
我认为统一的小写可读性更高一些, 就像后端写SQL, 关键字大写, 字段小写, 在我看来就显得有些杂乱. 包括我看大部分人的代码也都是中划线居多, 很少见到大驼峰的, 至于尤大为何优先推荐这种写法, 我也不清楚原因, 如有朋友知道的话欢迎补充
当然好多朋友觉得vue组件是一个类, 文件名/组件名应当大写, 这个嘛, 看个人习惯好了, 我js中的类名是大驼峰, 但是文件名依然是中划线
对于上面提到的容易混淆的字母数字, 其实一般影响也不大(Il1-iL1, 0oO, q9g-Q9G), 主要影响在协同时字体不同或字体功能时, 会稍有影响
-
下班了, 有空再补充