项目结构与最佳实践 #

一、项目目录结构 #

1.1 推荐目录结构 #

text
src/
├── assets/              # 静态资源
│   ├── images/         # 图片
│   ├── styles/         # 全局样式
│   └── fonts/          # 字体
├── components/          # 公共组件
│   ├── common/         # 通用组件
│   ├── layout/         # 布局组件
│   └── ui/             # UI组件
├── composables/         # 组合式函数
├── directives/          # 自定义指令
├── layouts/             # 布局模板
├── router/              # 路由配置
│   ├── index.js
│   └── routes.js
├── stores/              # 状态管理
│   ├── index.js
│   ├── user.js
│   └── product.js
├── utils/               # 工具函数
│   ├── request.js      # 请求封装
│   ├── storage.js      # 存储封装
│   └── helpers.js      # 辅助函数
├── views/               # 页面组件
│   ├── home/
│   ├── user/
│   └── product/
├── App.vue
└── main.js

1.2 按功能模块组织 #

text
src/
├── modules/             # 功能模块
│   ├── user/
│   │   ├── components/
│   │   ├── composables/
│   │   ├── stores/
│   │   ├── views/
│   │   └── router.js
│   ├── product/
│   │   ├── components/
│   │   ├── composables/
│   │   ├── stores/
│   │   ├── views/
│   │   └── router.js
│   └── order/
├── shared/              # 共享资源
│   ├── components/
│   ├── composables/
│   ├── directives/
│   └── utils/
└── App.vue

二、组件设计原则 #

2.1 单一职责 #

vue
<!-- ❌ 不推荐:组件职责过多 -->
<template>
  <div>
    <header>...</header>
    <nav>...</nav>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

<!-- ✅ 推荐:职责单一 -->
<template>
  <div>
    <AppHeader />
    <AppNav />
    <AppMain />
    <AppFooter />
  </div>
</template>

2.2 组件分类 #

text
components/
├── common/              # 通用组件
│   ├── Button.vue
│   ├── Input.vue
│   └── Modal.vue
├── layout/              # 布局组件
│   ├── Header.vue
│   ├── Sidebar.vue
│   └── Footer.vue
├── business/            # 业务组件
│   ├── UserCard.vue
│   └── ProductList.vue
└── form/                # 表单组件
    ├── FormInput.vue
    └── FormSelect.vue

2.3 组件命名 #

vue
<!-- 组件名使用PascalCase -->
<UserCard />
<ProductList />

<!-- 多词命名避免冲突 -->
<BaseButton />
<AppHeader />

<!-- 组件文件命名 -->
components/
├── BaseButton.vue
├── UserCard.vue
└── ProductList.vue

三、组合式函数设计 #

3.1 目录结构 #

text
composables/
├── useAuth.js           # 认证相关
├── useFetch.js          # 数据请求
├── useStorage.js        # 本地存储
├── useDebounce.js       # 防抖
└── useThrottle.js       # 节流

3.2 组合式函数模板 #

javascript
// composables/useFeature.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useFeature(options = {}) {
  // 响应式状态
  const state = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  // 方法
  function doSomething() {
    // ...
  }
  
  // 生命周期
  onMounted(() => {
    // 初始化
  })
  
  onUnmounted(() => {
    // 清理
  })
  
  // 返回
  return {
    state,
    loading,
    error,
    doSomething
  }
}

3.3 常用组合式函数 #

javascript
// composables/useFetch.js
import { ref, toValue, watchEffect } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)
  
  async function fetchData() {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(toValue(url))
      data.value = await response.json()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }
  
  watchEffect(() => {
    fetchData()
  })
  
  return { data, error, loading, refetch: fetchData }
}

// composables/useLocalStorage.js
import { ref, watch } from 'vue'

export function useLocalStorage(key, defaultValue) {
  const stored = localStorage.getItem(key)
  const data = ref(stored ? JSON.parse(stored) : defaultValue)
  
  watch(data, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })
  
  return data
}

四、代码规范 #

4.1 组件结构 #

vue
<template>
  <!-- 模板内容 -->
</template>

<script setup>
// 1. 导入
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import MyComponent from './MyComponent.vue'

// 2. Props和Emits
const props = defineProps({
  title: String
})

const emit = defineEmits(['update', 'delete'])

// 3. 响应式状态
const count = ref(0)
const loading = ref(false)

// 4. 计算属性
const doubled = computed(() => count.value * 2)

// 5. 方法
function increment() {
  count.value++
}

// 6. 生命周期
onMounted(() => {
  fetchData()
})

// 7. 暴露给模板引用
defineExpose({
  count,
  increment
})
</script>

<style scoped>
/* 样式 */
</style>

4.2 命名规范 #

javascript
// 组件名:PascalCase
MyComponent.vue
UserCard.vue

// 组合式函数:use前缀
useAuth.js
useFetch.js

// 事件名:kebab-case
emit('update-user')
emit('delete-item')

// Props:kebab-case在模板中
<UserCard user-name="张三" />

// 变量名:camelCase
const userName = ref('')
const isLoading = ref(false)

// 常量:UPPER_SNAKE_CASE
const API_BASE_URL = '/api'
const MAX_RETRY_COUNT = 3

4.3 注释规范 #

vue
<script setup>
/**
 * 用户卡片组件
 * @description 显示用户信息卡片
 * @author Your Name
 */
import { ref } from 'vue'

// 用户数据
const user = ref({
  name: '',
  email: ''
})

/**
 * 获取用户数据
 * @param {number} id - 用户ID
 * @returns {Promise<void>}
 */
async function fetchUser(id) {
  // 实现逻辑
}
</script>

五、性能优化 #

5.1 懒加载组件 #

javascript
// 路由懒加载
const routes = [
  {
    path: '/about',
    component: () => import('./views/About.vue')
  }
]

// 组件懒加载
const AsyncComponent = defineAsyncComponent(() =>
  import('./components/HeavyComponent.vue')
)

5.2 使用v-memo #

vue
<template>
  <!-- 跳过不必要的更新 -->
  <div v-memo="[selected]">
    <p>{{ item.name }}</p>
    <p>{{ selected ? '已选中' : '未选中' }}</p>
  </div>
</template>

5.3 虚拟列表 #

vue
<template>
  <VirtualList
    :items="largeList"
    :item-height="50"
  >
    <template #default="{ item }">
      <div class="item">{{ item.name }}</div>
    </template>
  </VirtualList>
</template>

5.4 避免不必要的响应式 #

vue
<script setup>
import { ref, shallowRef, markRaw } from 'vue'

// 大型不可变数据使用shallowRef
const largeData = shallowRef({ /* ... */ })

// 不需要响应式的对象使用markRaw
const staticConfig = markRaw({
  apiUrl: '/api',
  timeout: 5000
})
</script>

六、错误处理 #

6.1 全局错误处理 #

javascript
// main.js
const app = createApp(App)

app.config.errorHandler = (err, instance, info) => {
  console.error('全局错误:', err)
  // 上报错误
  reportError(err)
}

app.config.warnHandler = (msg, instance, trace) => {
  console.warn('警告:', msg)
}

6.2 组件错误边界 #

vue
<template>
  <slot v-if="!error" />
  <div v-else class="error">
    {{ error.message }}
    <button @click="resetError">重试</button>
  </div>
</template>

<script setup>
import { ref, onErrorCaptured } from 'vue'

const error = ref(null)

onErrorCaptured((err) => {
  error.value = err
  return false  // 阻止错误传播
})

function resetError() {
  error.value = null
}
</script>

七、总结 #

项目结构要点 #

  • 按功能模块组织代码
  • 组件分类存放
  • 组合式函数独立目录
  • 工具函数统一管理

最佳实践 #

  • 组件单一职责
  • 命名规范统一
  • 合理使用懒加载
  • 做好错误处理
  • 注释清晰完整
最后更新:2026-03-26