Vue 3 JavaScript 最佳实践指南
1. 组件结构规范
1.1 文件命名
- 使用 PascalCase 命名组件文件:UserProfile.vue
- 使用 kebab-case 命名非组件文件:user-profile.js
1.2 组件模板结构
            
            
              xml
              
              
            
          
          <template>
  <!-- 单根元素 -->
  <div class="component-name">
    <!-- 语义化 HTML 标签 -->
    <header class="component-header">
      <h1>{{ title }}</h1>
    </header>
    <main class="component-content">
      <!-- 内容区域 -->
    </main>
    <footer class="component-footer">
      <!-- 底部区域 -->
    </footer>
  </div>
</template>2. Script Setup 最佳实践
2.1 导入顺序
            
            
              xml
              
              
            
          
          <script setup>
// 1. Vue 核心导入
import { ref, computed, watch, onMounted } from 'vue'
// 2. 第三方库导入
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
// 3. 组件导入
import UserCard from './UserCard.vue'
import Button from './Button.vue'
// 4. 工具函数导入
import { formatDate } from '@/utils/date'
import { validateEmail } from '@/utils/validation'
// 5. 类型定义导入
import type { User } from '@/types/user'
</script>2.2 响应式数据定义
            
            
              xml
              
              
            
          
          <script setup>
// 基本类型使用 ref
const count = ref(0)
const name = ref('')
const isLoading = ref(false)
// 对象类型使用 reactive
const user = reactive({
  id: 1,
  name: 'John',
  email: 'john@example.com'
})
// 数组使用 ref
const items = ref([])
// 计算属性
const fullName = computed(() => `${user.firstName} ${user.lastName}`)
const itemCount = computed(() => items.value.length)
// 方法定义
const increment = () => {
  count.value++
}
const fetchData = async () => {
  isLoading.value = true
  try {
    const response = await api.get('/data')
    items.value = response.data
  } catch (error) {
    console.error('Failed to fetch data:', error)
  } finally {
    isLoading.value = false
  }
}
// 生命周期
onMounted(() => {
  fetchData()
})
</script>3. Props 和 Emits 规范
3.1 Props 定义
            
            
              typescript
              
              
            
          
          <script setup>
// 运行时声明
defineProps({
  // 必填属性
  title: {
    type: String,
    required: true
  },
  // 可选属性
  description: {
    type: String,
    default: ''
  },
  // 数字类型
  count: {
    type: Number,
    default: 0
  },
  // 数组类型
  items: {
    type: Array,
    default: () => []
  },
  // 对象类型
  config: {
    type: Object,
    default: () => ({})
  },
  // 函数类型
  onSuccess: {
    type: Function,
    default: () => {}
  }
})
// 或者使用 TypeScript 类型
defineProps<{
  title: string
  description?: string
  count?: number
  items?: any[]
  config?: Record<string, any>
  onSuccess?: () => void
}>()
</script>3.2 Emits 定义
            
            
              xml
              
              
            
          
          <script setup>
// 定义 emits
const emit = defineEmits<{
  // 无参数事件
  (e: 'close'): void
  // 带参数事件
  (e: 'update:modelValue', value: string): void
  (e: 'submit', data: FormData): void
  // 可选参数事件
  (e: 'error', message?: string): void
}>()
// 使用 emits
const handleClose = () => {
  emit('close')
}
const handleSubmit = (data) => {
  emit('submit', data)
}
</script>4. 响应式数据处理
4.1 避免直接修改响应式对象
            
            
              javascript
              
              
            
          
          // ❌ 不推荐 - 直接修改
user.name = 'New Name'
// ✅ 推荐 - 使用 Object.assign 或展开运算符
Object.assign(user, { name: 'New Name' })
// 或者
user.name = 'New Name'4.2 数组操作
            
            
              scss
              
              
            
          
          const list = ref([])
// ✅ 推荐的操作方式
list.value.push(newItem)           // 添加
list.value.splice(index, 1)        // 删除
list.value[index] = newValue       // 修改
list.value = [...list.value, item] // 不可变操作4.3 解构响应式对象
            
            
              php
              
              
            
          
          const user = reactive({
  name: 'John',
  age: 30,
  address: {
    city: 'New York',
    country: 'USA'
  }
})
// ❌ 失去响应性
const { name, age } = user
// ✅ 保持响应性
const name = toRef(user, 'name')
const age = toRef(user, 'age')
// ✅ 或者使用 computed
const userName = computed(() => user.name)
const userAge = computed(() => user.age)5. 组合式函数 (Composables)
5.1 创建组合式函数
            
            
              javascript
              
              
            
          
          // composables/useCounter.js
export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  return {
    count,
    increment,
    decrement,
    reset
  }
}
// composables/useApi.js
export function useApi(url) {
  const data = ref(null)
  const error = ref(null)
  const isLoading = ref(false)
  const fetchData = async () => {
    isLoading.value = true
    error.value = null
    try {
      const response = await fetch(url)
      data.value = await response.json()
    } catch (err) {
      error.value = err
    } finally {
      isLoading.value = false
    }
  }
  return {
    data,
    error,
    isLoading,
    fetchData
  }
}5.2 使用组合式函数
            
            
              xml
              
              
            
          
          <script setup>
import { useCounter, useApi } from '@/composables'
// 使用计数器
const { count, increment } = useCounter(0)
// 使用 API
const { data: users, isLoading, fetchData } = useApi('/api/users')
onMounted(() => {
  fetchData()
})
</script>6. 事件处理
6.1 事件命名规范
            
            
              yaml
              
              
            
          
          // ✅ 推荐的事件命名
const emit = defineEmits<{
  'update:modelValue': [value: string]
  'input-change': [value: string, event: Event]
  'form-submit': [data: FormData]
  'item-selected': [item: Item, index: number]
}>()6.2 事件处理函数
            
            
              csharp
              
              
            
          
          <script setup>
const handleClick = (event) => {
  // 阻止默认行为
  event.preventDefault()
  // 停止事件冒泡
  event.stopPropagation()
  // 业务逻辑
  emit('click', event)
}
const handleInput = (event) => {
  const value = event.target.value
  emit('update:modelValue', value)
}
// 防抖处理
const debouncedSearch = useDebounceFn((query) => {
  search(query)
}, 300)
</script>7. 样式和 CSS 最佳实践
7.1 作用域样式
            
            
              xml
              
              
            
          
          <style scoped>
/* 使用 BEM 命名规范 */
.component {
  &__header {
    padding: 1rem;
  }
  &__content {
    margin: 1rem 0;
  }
  &--disabled {
    opacity: 0.5;
    pointer-events: none;
  }
}
/* 使用 CSS 变量 */
:root {
  --primary-color: #42b883;
  --secondary-color: #35495e;
}
.component {
  color: var(--primary-color);
  background-color: var(--secondary-color);
}
</style>8. 性能优化
8.1 条件渲染优化
            
            
              xml
              
              
            
          
          <template>
  <!-- 使用 v-show 频繁切换 -->
  <div v-show="isVisible">频繁切换的内容</div>
  <!-- 使用 v-if 一次性渲染 -->
  <div v-if="shouldRender">一次性渲染的内容</div>
  <!-- 列表渲染使用 key -->
  <div
    v-for="item in items"
    :key="item.id"
    class="item"
  >
    {{ item.name }}
  </div>
</template>8.2 计算属性缓存
            
            
              ini
              
              
            
          
          // ✅ 推荐 - 使用计算属性
const filteredItems = computed(() => {
  return items.value.filter(item =>
    item.name.includes(searchTerm.value)
  )
})
// ❌ 不推荐 - 在模板中直接计算
// <div v-for="item in items.filter(i => i.name.includes(searchTerm))">9. 错误处理
9.1 异步错误处理
            
            
              javascript
              
              
            
          
          const fetchUser = async (userId) => {
  try {
    const user = await api.getUser(userId)
    return user
  } catch (error) {
    console.error('Failed to fetch user:', error)
    // 显示错误信息给用户
    showError('加载用户信息失败')
    throw error // 重新抛出错误
  }
}9.2 组件错误边界
            
            
              xml
              
              
            
          
          // ErrorBoundary.vue
<script setup>
import { ref, onErrorCaptured } from 'vue'
const error = ref(null)
onErrorCaptured((err, instance, info) => {
  error.value = err
  console.error('Error captured:', err, info)
  // 阻止错误继续传播
  return false
})
</script>
<template>
  <div v-if="error" class="error-boundary">
    <h3>Something went wrong</h3>
    <p>{{ error.message }}</p>
    <button @click="error = null">Try Again</button>
  </div>
  <slot v-else />
</template>10. 代码组织原则
10.1 单一职责原则
- 每个组件只负责一个特定的功能
- 复杂的组件拆分为多个小组件
- 业务逻辑提取到组合式函数中
10.2 可复用性原则
- 创建通用的基础组件
- 使用组合式函数封装可复用逻辑
- 通过 props 和 slots 提供灵活性
10.3 可测试性原则
- 组件逻辑与 UI 分离
- 使用依赖注入而不是硬编码
- 编写单元测试和组件测试
通过遵循这些最佳实践,可以创建出可维护、可测试、高性能的 Vue 3 应用程序。