Vue3 项目的最佳工程化实践参考

介绍一下 Vue 3 项目的最佳工程化实践。以下是推荐的项目结构和关键实践:

一、TS

src/
├── main.ts                 # 应用入口文件
├── App.vue                 # 根组件
├── env.d.ts               # 环境变量声明
├── assets/                # 静态资源
├── components/           # 全局通用组件
│   ├── common/           # 基础公共组件
│   └── business/         # 业务公共组件
├── composables/          # 组合式函数
├── configs/              # 配置文件
├── plugins/              # 插件配置
│   ├── pinia.ts         # Pinia 配置
│   ├── router.ts        # Router 配置
│   └── index.ts         # 插件统一注册
├── router/              # 路由配置
│   ├── modules/         # 路由模块
│   └── index.ts         # 路由入口
├── stores/              # Pinia 状态管理
│   ├── modules/         # 状态模块
│   └── index.ts         # 状态入口
├── services/            # API 服务
│   ├── api/            # API 接口定义
│   └── http.ts         # axios 配置
├── styles/              # 全局样式
├── types/               # TS 类型定义
├── utils/               # 工具函数
└── views/               # 页面组件

下面是各个关键部分的最佳实践示例:

1、插件配置 (src/plugins/index.ts):

import { type App } from 'vue'
import { setupPinia } from './pinia'
import { setupRouter } from './router'

// 统一注册插件
export function setupPlugins(app: App) {
  setupPinia(app)
  setupRouter(app)
  // 其他插件配置...
}

2、Pinia 配置 (src/plugins/pinia.ts):

import { type App } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

export function setupPinia(app: App) {
  const pinia = createPinia()
  pinia.use(piniaPluginPersistedstate)
  app.use(pinia)
}

3、HTTP 服务配置 (src/services/http.ts):

import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'

class HttpRequest {
  private instance: AxiosInstance

  constructor(config: AxiosRequestConfig) {
    this.instance = axios.create(config)
    this.setupInterceptors()
  }

  private setupInterceptors() {
    this.instance.interceptors.request.use(
      (config) => {
        // 请求拦截
        return config
      },
      (error) => {
        return Promise.reject(error)
      }
    )

    this.instance.interceptors.response.use(
      (response) => {
        // 响应拦截
        return response.data
      },
      (error) => {
        return Promise.reject(error)
      }
    )
  }

  request<T = any>(config: AxiosRequestConfig): Promise<T> {
    return this.instance.request(config)
  }
}

export const http = new HttpRequest({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
})

4、API 接口定义 (src/services/api/user.ts):

import { http } from '../http'

export interface UserInfo {
  id: number
  name: string
  // ...
}

export const userApi = {
  getUserInfo(id: number) {
    return http.request<UserInfo>({
      url: `/user/${id}`,
      method: 'GET'
    })
  }
}

5、Store 模块 (src/stores/modules/user.ts):

import { defineStore } from 'pinia'
import type { UserInfo } from '@/services/api/user'

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null as UserInfo | null
  }),
  actions: {
    async fetchUserInfo(id: number) {
      const { userApi } = await import('@/services/api/user')
      this.userInfo = await userApi.getUserInfo(id)
    }
  },
  persist: true
})

6、入口文件 (src/main.ts):

import { createApp } from 'vue'
import App from './App.vue'
import { setupPlugins } from './plugins'
import '@/styles/index.scss'

const app = createApp(App)
setupPlugins(app)
app.mount('#app')

7、环境变量配置 (.env.development):

VITE_API_BASE_URL=http://localhost:3000

8、组合式函数 (src/composables/useRequest.ts):

import { ref } from 'vue'

export function useRequest<T, P extends any[]>(
  api: (...args: P) => Promise<T>
) {
  const data = ref<T | null>(null)
  const loading = ref(false)
  const error = ref<Error | null>(null)

  const execute = async (...args: P) => {
    loading.value = true
    error.value = null
    try {
      data.value = await api(...args)
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  }

  return {
    data,
    loading,
    error,
    execute
  }
}

这样的工程化实践有以下优点:

  1. 目录结构清晰,职责分明
  2. 模块化设计,便于维护和扩展
  3. 类型安全,使用 TypeScript
  4. 统一的错误处理和状态管理
  5. 可复用的组合式函数
  6. 环境配置分离
  7. API 接口统一管理
  8. 插件配置集中管理

此外,还建议:

  1. 使用 ESLint + Prettier 统一代码风格
  2. 使用 Husky + lint-staged 进行提交前检查
  3. 使用 Conventional Commits 规范提交信息
  4. 编写单元测试确保代码质量
  5. 使用 CI/CD 实现自动化部署

这样的工程化结构可以帮助团队更好地协作,提高代码质量和开发效率。

二、JS

对于 JavaScript 项目,我们同样可以实现良好的工程化。以下是基于 JavaScript 的最佳实践:

src/
├── main.js                 # 应用入口文件
├── App.vue                 # 根组件
├── assets/                # 静态资源
├── components/           # 全局通用组件
│   ├── common/          # 基础公共组件
│   └── business/        # 业务公共组件
├── hooks/               # 组合式函数 (composables)
├── configs/             # 配置文件
├── plugins/             # 插件配置
├── router/              # 路由配置
├── stores/              # Pinia 状态管理
├── services/            # API 服务
├── styles/              # 全局样式
└── utils/               # 工具函数

下面是各个核心文件的示例代码:

1、插件配置 (src/plugins/index.js):

import { setupPinia } from './pinia'
import { setupRouter } from './router'

export function setupPlugins(app) {
  setupPinia(app)
  setupRouter(app)
}

2、Pinia 配置 (src/plugins/pinia.js):

import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

export function setupPinia(app) {
  const pinia = createPinia()
  pinia.use(piniaPluginPersistedstate)
  app.use(pinia)
}

3、HTTP 服务配置 (src/services/http.js):

import axios from 'axios'

class HttpRequest {
  constructor(config) {
    this.instance = axios.create(config)
    this.setupInterceptors()
  }

  setupInterceptors() {
    // 请求拦截器
    this.instance.interceptors.request.use(
      config => {
        // 在发送请求之前做些什么
        const token = localStorage.getItem('token')
        if (token) {
          config.headers.Authorization = `Bearer ${token}`
        }
        return config
      },
      error => {
        return Promise.reject(error)
      }
    )

    // 响应拦截器
    this.instance.interceptors.response.use(
      response => {
        // 对响应数据做点什么
        const { code, data, message } = response.data
        
        if (code === 200) {
          return data
        } else {
          // 统一错误处理
          this.handleError(code, message)
          return Promise.reject(new Error(message))
        }
      },
      error => {
        // 对响应错误做点什么
        const { status } = error.response
        this.handleError(status)
        return Promise.reject(error)
      }
    )
  }

  handleError(code, message) {
    switch (code) {
      case 401:
        // 未登录或 token 过期
        break
      case 403:
        // 无权限
        break
      case 404:
        // 请求的资源不存在
        break
      case 500:
        // 服务器错误
        break
      default:
        // 其他错误
        break
    }
  }

  request(config) {
    return this.instance.request(config)
  }
}

export const http = new HttpRequest({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
})

4、API 接口定义 (src/services/api/user.js):

import { http } from '../http'

export const userApi = {
  // 获取用户信息
  getUserInfo(id) {
    return http.request({
      url: `/user/${id}`,
      method: 'GET'
    })
  },
  
  // 用户登录
  login(data) {
    return http.request({
      url: '/user/login',
      method: 'POST',
      data
    })
  }
}

5、Store 模块 (src/stores/modules/user.js):

import { defineStore } from 'pinia'
import { userApi } from '@/services/api/user'

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null,
    token: localStorage.getItem('token') || ''
  }),

  getters: {
    isLogin: state => !!state.token
  },

  actions: {
    async login(credentials) {
      try {
        const { token, userInfo } = await userApi.login(credentials)
        this.token = token
        this.userInfo = userInfo
        localStorage.setItem('token', token)
        return true
      } catch (error) {
        return false
      }
    },

    async getUserInfo(id) {
      try {
        this.userInfo = await userApi.getUserInfo(id)
      } catch (error) {
        console.error('获取用户信息失败:', error)
      }
    },

    logout() {
      this.token = ''
      this.userInfo = null
      localStorage.removeItem('token')
    }
  },

  persist: {
    key: 'user-store',
    paths: ['token']
  }
})

6、组合式函数 (src/hooks/useRequest.js):

import { ref } from 'vue'

export function useRequest(api) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)

  const execute = async (...args) => {
    loading.value = true
    error.value = null
    try {
      data.value = await api(...args)
    } catch (err) {
      error.value = err
      console.error('请求错误:', err)
    } finally {
      loading.value = false
    }
  }

  return {
    data,
    loading,
    error,
    execute
  }
}

7、入口文件 (src/main.js):

import { createApp } from 'vue'
import App from './App.vue'
import { setupPlugins } from './plugins'
import './styles/index.scss'

const app = createApp(App)
setupPlugins(app)
app.mount('#app')

8、工具函数 (src/utils/validate.js):

// 表单验证工具函数
export const validators = {
  isEmail(value) {
    return /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(value)
  },

  isPhone(value) {
    return /^1[3-9]\d{9}$/.test(value)
  },

  isPassword(value) {
    return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/.test(value)
  }
}

推荐的工程化实践:

1、使用 ESLint 和 Prettier:

// .eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
    browser: true,
    es2021: true
  },
  extends: [
    'plugin:vue/vue3-recommended',
    'eslint:recommended'
  ],
  rules: {
    // 自定义规则
  }
}

2、使用 husky 和 lint-staged 进行代码检查:

{
  "scripts": {
    "lint": "eslint --ext .js,.vue src",
    "lint:fix": "eslint --ext .js,.vue src --fix"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,vue}": [
      "eslint --fix",
      "git add"
    ]
  }
}

4、统一的错误处理:

// src/utils/error.js
export class BusinessError extends Error {
  constructor(code, message) {
    super(message)
    this.code = code
  }
}

export const errorHandler = (error) => {
  if (error instanceof BusinessError) {
    // 处理业务错误
  } else {
    // 处理其他错误
  }
}

主要优势:

  1. 代码组织清晰,易于维护
  2. 统一的错误处理机制
  3. 可重用的工具函数和 hooks
  4. 规范的代码风格
  5. API 接口统一管理
  6. 状态管理清晰
  7. 自动化的代码检查和格式化

最后,建议:

  1. 合理使用注释,特别是复杂逻辑部分
  2. 遵循一致的命名规范
  3. 适当做性能优化
  4. 定期进行代码审查
  5. 保持依赖包的更新
posted @ 2025-01-14 16:09  非法关键字  阅读(787)  评论(0)    收藏  举报