代码风格指南
良好的代码风格是团队协作和项目维护的基础。本指南提供了 Vue.js 项目的代码风格最佳实践。
官方风格指南
Vue.js 官方提供了详细的风格指南,分为四个优先级:
- A 级(必要): 避免错误
- B 级(强烈推荐): 增强可读性
- C 级(推荐): 最小化认知开销
- D 级(谨慎使用): 潜在危险模式
命名规范
组件命名
javascript
// ✅ 好的命名(PascalCase)
const UserProfile = defineComponent({
name: 'UserProfile'
})
// ✅ 单文件组件文件名(PascalCase)
// UserProfile.vue
// TodoItem.vue
// BaseButton.vue
// ❌ 避免的命名
// userprofile.vue
// user-profile.vue
// User_Profile.vue
基础组件命名
javascript
// ✅ 基础组件以特定前缀开头
// BaseButton.vue
// BaseTable.vue
// BaseIcon.vue
// ✅ 或者使用 App 前缀
// AppButton.vue
// AppTable.vue
// AppIcon.vue
// ❌ 避免通用名称
// Button.vue
// Table.vue
// Icon.vue
单例组件命名
javascript
// ✅ 单例组件以 "The" 前缀开头
// TheHeader.vue
// TheSidebar.vue
// TheFooter.vue
// ❌ 避免的命名
// Header.vue
// Sidebar.vue
// Footer.vue
紧密耦合组件命名
javascript
// ✅ 子组件以父组件名作为前缀
// TodoList.vue
// TodoListItem.vue
// TodoListItemButton.vue
// ✅ 或者使用文件夹组织
// components/
// TodoList/
// index.vue
// TodoListItem.vue
// TodoListItemButton.vue
Props 命名
javascript
// ✅ Props 使用 camelCase
defineProps({
greetingText: String,
isVisible: Boolean,
maxCount: Number
})
// ✅ 在模板中使用 kebab-case
// <WelcomeMessage greeting-text="hello" :is-visible="true" />
// ❌ 避免的命名
defineProps({
'greeting-text': String, // 在 JavaScript 中使用 kebab-case
greeting_text: String // 使用 snake_case
})
事件命名
javascript
// ✅ 事件使用 kebab-case
const emit = defineEmits([
'update-value',
'item-selected',
'form-submitted'
])
// ✅ 触发事件
emit('update-value', newValue)
emit('item-selected', item)
// ❌ 避免的命名
const emit = defineEmits([
'updateValue', // camelCase
'itemSelected', // camelCase
'update_value' // snake_case
])
组件结构
单文件组件顺序
vue
<!-- ✅ 推荐的组件结构 -->
<template>
<!-- 模板内容 -->
</template>
<script setup>
// 导入
import { ref, computed, onMounted } from 'vue'
import SomeComponent from './SomeComponent.vue'
// Props 定义
const props = defineProps({
title: String,
items: Array
})
// Emits 定义
const emit = defineEmits(['update', 'delete'])
// 响应式数据
const count = ref(0)
const isVisible = ref(true)
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法
function increment() {
count.value++
}
function handleUpdate() {
emit('update', count.value)
}
// 生命周期
onMounted(() => {
console.log('Component mounted')
})
</script>
<style scoped>
/* 样式 */
.component {
padding: 20px;
}
</style>
组件选项顺序(选项式 API)
javascript
export default {
name: 'ComponentName',
// 组件
components: {
SomeComponent
},
// 指令
directives: {
focus
},
// 混入
mixins: [
someMixin
],
// Props
props: {
title: String,
items: Array
},
// Emits
emits: ['update', 'delete'],
// 数据
data() {
return {
count: 0,
isVisible: true
}
},
// 计算属性
computed: {
doubleCount() {
return this.count * 2
}
},
// 侦听器
watch: {
count(newVal, oldVal) {
console.log('Count changed:', newVal)
}
},
// 生命周期钩子
created() {
console.log('Component created')
},
mounted() {
console.log('Component mounted')
},
// 方法
methods: {
increment() {
this.count++
},
handleUpdate() {
this.$emit('update', this.count)
}
}
}
模板规范
元素属性顺序
vue
<!-- ✅ 推荐的属性顺序 -->
<template>
<div
id="app"
class="container"
:class="{ active: isActive }"
:style="containerStyle"
v-if="isVisible"
v-for="item in items"
:key="item.id"
v-model="inputValue"
:disabled="isDisabled"
@click="handleClick"
@input="handleInput"
>
{{ item.name }}
</div>
</template>
属性顺序优先级:
- 定义(
is
,v-is
) - 列表渲染(
v-for
) - 条件渲染(
v-if
,v-else-if
,v-else
,v-show
,v-cloak
) - 渲染修饰符(
v-pre
,v-once
) - 全局感知(
id
) - 唯一特性(
ref
,key
) - 双向绑定(
v-model
) - 其他特性(所有普通的绑定或未绑定的特性)
- 事件(
v-on
) - 内容(
v-html
,v-text
)
指令缩写
vue
<!-- ✅ 一致使用指令缩写 -->
<template>
<input
:value="newTodoText"
:placeholder="newTodoInstructions"
@input="onInput"
@focus="onFocus"
>
<router-link :to="{ name: 'home' }">
Home
</router-link>
</template>
<!-- ❌ 混合使用完整形式和缩写 -->
<template>
<input
v-bind:value="newTodoText"
:placeholder="newTodoInstructions"
v-on:input="onInput"
@focus="onFocus"
>
</template>
模板表达式
vue
<!-- ✅ 简单表达式 -->
<template>
<div>{{ message }}</div>
<div>{{ user.name }}</div>
<div>{{ items.length }}</div>
</template>
<!-- ✅ 复杂逻辑使用计算属性 -->
<template>
<div>{{ normalizedFullName }}</div>
</template>
<script setup>
const props = defineProps({
user: Object
})
const normalizedFullName = computed(() => {
return props.user.firstName.trim() + ' ' + props.user.lastName.trim()
})
</script>
<!-- ❌ 避免复杂表达式 -->
<template>
<div>
{{
user.firstName.split(' ').map(word =>
word[0].toUpperCase() + word.slice(1)
).join(' ')
}}
</div>
</template>
JavaScript 规范
变量声明
javascript
// ✅ 使用 const 和 let
const API_URL = 'https://api.example.com'
const users = ref([])
let currentPage = 1
// ❌ 避免使用 var
var count = 0 // 不推荐
函数定义
javascript
// ✅ 使用箭头函数(简短函数)
const double = (x) => x * 2
const greet = (name) => `Hello, ${name}!`
// ✅ 使用函数声明(复杂函数)
function calculateTotal(items) {
return items.reduce((total, item) => {
return total + item.price * item.quantity
}, 0)
}
// ✅ 组合式 API 中的方法
function handleSubmit() {
// 处理提交逻辑
}
function validateForm() {
// 验证表单逻辑
}
对象和数组
javascript
// ✅ 使用对象字面量
const user = {
name: 'John',
age: 30,
email: 'john@example.com'
}
// ✅ 使用数组字面量
const items = ['apple', 'banana', 'orange']
// ✅ 使用解构赋值
const { name, age } = user
const [first, second] = items
// ✅ 使用扩展运算符
const newUser = { ...user, age: 31 }
const newItems = [...items, 'grape']
异步处理
javascript
// ✅ 使用 async/await
async function fetchUserData(userId) {
try {
const response = await api.getUser(userId)
return response.data
} catch (error) {
console.error('Failed to fetch user:', error)
throw error
}
}
// ✅ 错误处理
async function handleSubmit() {
loading.value = true
error.value = null
try {
await submitForm(formData.value)
message.success('提交成功')
} catch (err) {
error.value = err.message
message.error('提交失败')
} finally {
loading.value = false
}
}
CSS 规范
类命名
scss
// ✅ 使用 BEM 命名法
.user-card {
padding: 20px;
border: 1px solid #ddd;
&__header {
display: flex;
align-items: center;
margin-bottom: 15px;
}
&__avatar {
width: 50px;
height: 50px;
border-radius: 50%;
}
&__name {
font-size: 18px;
font-weight: bold;
margin-left: 10px;
}
&__content {
color: #666;
line-height: 1.5;
}
&--featured {
border-color: #007bff;
background-color: #f8f9fa;
}
&--compact {
padding: 10px;
.user-card__name {
font-size: 16px;
}
}
}
// ❌ 避免的命名
.userCard { } // camelCase
.user_card { } // snake_case
.UserCard { } // PascalCase
CSS 变量
scss
// ✅ 使用 CSS 变量
:root {
--color-primary: #007bff;
--color-secondary: #6c757d;
--color-success: #28a745;
--color-danger: #dc3545;
--color-warning: #ffc107;
--color-info: #17a2b8;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.25rem;
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 3rem;
}
.button {
background-color: var(--color-primary);
font-size: var(--font-size-base);
padding: var(--spacing-sm) var(--spacing-md);
}
作用域样式
vue
<!-- ✅ 使用 scoped 样式 -->
<template>
<div class="component">
<h2 class="title">标题</h2>
<p class="content">内容</p>
</div>
</template>
<style scoped>
.component {
padding: 20px;
}
.title {
color: #333;
margin-bottom: 10px;
}
.content {
color: #666;
line-height: 1.5;
}
</style>
<!-- ✅ 深度选择器 -->
<style scoped>
.component :deep(.child-component) {
margin-top: 20px;
}
.component :deep(.el-button) {
margin-right: 10px;
}
</style>
TypeScript 规范
类型定义
typescript
// ✅ 接口定义
interface User {
id: number
name: string
email: string
avatar?: string
createdAt: Date
}
interface ApiResponse<T> {
data: T
message: string
success: boolean
}
// ✅ 类型别名
type Status = 'pending' | 'approved' | 'rejected'
type EventHandler = (event: Event) => void
// ✅ 泛型
interface Repository<T> {
findById(id: number): Promise<T | null>
create(data: Omit<T, 'id'>): Promise<T>
update(id: number, data: Partial<T>): Promise<T>
delete(id: number): Promise<void>
}
组件类型
typescript
// ✅ Props 类型定义
interface Props {
title: string
items: User[]
maxCount?: number
onItemClick?: (item: User) => void
}
const props = withDefaults(defineProps<Props>(), {
maxCount: 10
})
// ✅ Emits 类型定义
interface Emits {
'update:modelValue': [value: string]
'item-selected': [item: User]
'form-submitted': [data: FormData]
}
const emit = defineEmits<Emits>()
// ✅ Ref 类型
const count = ref<number>(0)
const user = ref<User | null>(null)
const users = ref<User[]>([])
// ✅ 计算属性类型
const filteredUsers = computed<User[]>(() => {
return users.value.filter(user => user.name.includes(searchText.value))
})
文件组织
目录结构
src/
├── assets/ # 静态资源
│ ├── images/
│ ├── icons/
│ └── styles/
├── components/ # 公共组件
│ ├── base/ # 基础组件
│ ├── common/ # 通用组件
│ └── layout/ # 布局组件
├── composables/ # 组合式函数
├── directives/ # 自定义指令
├── plugins/ # 插件
├── router/ # 路由配置
├── stores/ # 状态管理
├── types/ # TypeScript 类型定义
├── utils/ # 工具函数
├── views/ # 页面组件
├── App.vue
└── main.ts
文件命名
// ✅ 组件文件
UserProfile.vue
TodoList.vue
BaseButton.vue
// ✅ 工具文件
api.ts
utils.ts
validators.ts
// ✅ 类型文件
user.types.ts
api.types.ts
common.types.ts
// ✅ 样式文件
variables.scss
mixins.scss
components.scss
注释规范
JavaScript 注释
javascript
/**
* 用户服务类
* 提供用户相关的 API 操作
*/
class UserService {
/**
* 获取用户信息
* @param {number} userId - 用户 ID
* @returns {Promise<User>} 用户信息
* @throws {Error} 当用户不存在时抛出错误
*/
async getUser(userId) {
// 验证用户 ID
if (!userId || userId <= 0) {
throw new Error('Invalid user ID')
}
try {
const response = await api.get(`/users/${userId}`)
return response.data
} catch (error) {
// 记录错误日志
console.error('Failed to fetch user:', error)
throw error
}
}
}
// TODO: 添加缓存机制
// FIXME: 处理网络超时问题
// NOTE: 这个方法在下个版本中会被废弃
Vue 组件注释
vue
<template>
<!-- 用户信息卡片 -->
<div class="user-card">
<!-- 用户头像和基本信息 -->
<div class="user-card__header">
<img :src="user.avatar" :alt="user.name" class="user-card__avatar">
<div class="user-card__info">
<h3 class="user-card__name">{{ user.name }}</h3>
<p class="user-card__email">{{ user.email }}</p>
</div>
</div>
<!-- 用户操作按钮 -->
<div class="user-card__actions">
<button @click="editUser">编辑</button>
<button @click="deleteUser">删除</button>
</div>
</div>
</template>
<script setup>
/**
* 用户信息卡片组件
* 显示用户的基本信息和操作按钮
*/
// Props 定义
const props = defineProps({
/** 用户信息对象 */
user: {
type: Object,
required: true
},
/** 是否显示操作按钮 */
showActions: {
type: Boolean,
default: true
}
})
// 事件定义
const emit = defineEmits([
/** 编辑用户事件 */
'edit',
/** 删除用户事件 */
'delete'
])
/**
* 编辑用户
*/
function editUser() {
emit('edit', props.user)
}
/**
* 删除用户
*/
function deleteUser() {
emit('delete', props.user.id)
}
</script>
代码检查工具
ESLint 配置
javascript
// .eslintrc.js
module.exports = {
root: true,
env: {
node: true,
browser: true,
es2022: true
},
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/typescript/recommended',
'@vue/prettier'
],
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module'
},
rules: {
// Vue 规则
'vue/multi-word-component-names': 'error',
'vue/component-definition-name-casing': ['error', 'PascalCase'],
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
'vue/prop-name-casing': ['error', 'camelCase'],
'vue/custom-event-name-casing': ['error', 'kebab-case'],
// JavaScript 规则
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'prefer-const': 'error',
'no-var': 'error',
'object-shorthand': 'error',
'prefer-template': 'error',
// TypeScript 规则
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'warn'
}
}
Prettier 配置
javascript
// .prettierrc.js
module.exports = {
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
trailingComma: 'es5',
bracketSpacing: true,
bracketSameLine: false,
arrowParens: 'avoid',
printWidth: 80,
tabWidth: 2,
useTabs: false,
endOfLine: 'lf',
vueIndentScriptAndStyle: false
}
Stylelint 配置
javascript
// .stylelintrc.js
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-recommended-vue'
],
rules: {
'selector-class-pattern': '^[a-z][a-z0-9]*(-[a-z0-9]+)*(__[a-z0-9]+(-[a-z0-9]+)*)?(--[a-z0-9]+(-[a-z0-9]+)*)?$',
'custom-property-pattern': '^[a-z][a-z0-9]*(-[a-z0-9]+)*$',
'declaration-block-trailing-semicolon': 'always',
'string-quotes': 'single'
}
}
Git 提交规范
提交信息格式
bash
# 格式:<type>(<scope>): <subject>
# 示例
feat(user): add user profile component
fix(api): handle network timeout error
docs(readme): update installation guide
style(button): fix button hover state
refactor(utils): extract common validation logic
test(user): add user service unit tests
chore(deps): update vue to 3.3.0
提交类型
feat
: 新功能fix
: 修复 bugdocs
: 文档更新style
: 代码格式修改refactor
: 代码重构test
: 测试相关chore
: 构建过程或辅助工具的变动perf
: 性能优化ci
: CI 配置文件和脚本的变动
性能最佳实践
组件优化
vue
<!-- ✅ 使用 v-memo 优化列表渲染 -->
<template>
<div
v-for="item in list"
:key="item.id"
v-memo="[item.id, item.selected]"
>
{{ item.name }}
</div>
</template>
<!-- ✅ 使用 v-once 优化静态内容 -->
<template>
<div v-once>
{{ expensiveCalculation() }}
</div>
</template>
<!-- ✅ 使用 v-show 而不是 v-if(频繁切换) -->
<template>
<div v-show="isVisible">
频繁切换的内容
</div>
</template>
响应式优化
javascript
// ✅ 使用 shallowRef 优化大对象
const largeObject = shallowRef({
// 大量数据
})
// ✅ 使用 markRaw 标记非响应式对象
const nonReactiveData = markRaw({
// 不需要响应式的数据
})
// ✅ 使用 readonly 保护数据
const readonlyData = readonly(reactiveData)
下一步
遵循良好的代码风格规范能够提高代码质量、增强团队协作效率,并降低维护成本!