Skip to content

代码风格指南

良好的代码风格是团队协作和项目维护的基础。本指南提供了 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>

属性顺序优先级:

  1. 定义(is, v-is
  2. 列表渲染(v-for
  3. 条件渲染(v-if, v-else-if, v-else, v-show, v-cloak
  4. 渲染修饰符(v-pre, v-once
  5. 全局感知(id
  6. 唯一特性(ref, key
  7. 双向绑定(v-model
  8. 其他特性(所有普通的绑定或未绑定的特性)
  9. 事件(v-on
  10. 内容(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: 修复 bug
  • docs: 文档更新
  • 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)

下一步

遵循良好的代码风格规范能够提高代码质量、增强团队协作效率,并降低维护成本!

基于 Vue.js 官方文档构建的学习宝典