前端组知识库

👍欢迎大家积极投稿交流👍

如果文档内容陈旧或者链接失效,请发现后及时同步,我将尽快修改

👇微信👇

图片
Skip to content

JavaScript/TypeScript 编码规范

概述

本项目基于 UniApp + Vue3 + TypeScript + Vite 技术栈,采用现代化的JavaScript/TypeScript开发方式。本规范旨在确保代码的类型安全、可维护性和团队协作效率。

技术栈

  • 语言: TypeScript 5.8+
  • 框架: Vue 3.4+ (Composition API)
  • 构建工具: Vite 5.2+
  • 状态管理: Pinia 2.0+
  • HTTP客户端: Alova 3.3+
  • 代码格式化: Prettier
  • 代码检查: ESLint 9.31+
  • 包管理器: pnpm 10.28+

代码格式规范

Prettier 配置

javascript
// .prettierrc.cjs
module.exports = {
  printWidth: 100, // 行宽100字符
  tabWidth: 2, // 缩进2个空格
  useTabs: false, // 使用空格而非tab
  semi: false, // 不使用分号
  singleQuote: true, // 使用单引号
  quoteProps: 'as-needed', // 对象属性按需引号
  jsxSingleQuote: true, // JSX使用单引号
  trailingComma: 'es5', // ES5尾随逗号
  bracketSpacing: true, // 对象括号间空格
  arrowParens: 'avoid', // 箭头函数参数括号
  endOfLine: 'auto', // 行尾符号自动
}

基本格式示例

typescript
// ✅ 正确示例
import type { Ref } from 'vue'
import { ref, computed, watch, onMounted } from 'vue'
import { useUserStore } from '@/store/user'
import { getUserInfo } from '@/api/user'
import type { UserInfo } from '@/types/user'

interface Props {
  userId: string
  showAvatar?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  showAvatar: true,
})

const userStore = useUserStore()
const userInfo: Ref<UserInfo | null> = ref(null)
const loading = ref(false)

const displayName = computed(() => {
  return userInfo.value?.nickname || userInfo.value?.username || '未知用户'
})

const fetchUserInfo = async () => {
  try {
    loading.value = true
    const response = await getUserInfo(props.userId)
    userInfo.value = response.data
  } catch (error) {
    console.error('获取用户信息失败:', error)
  } finally {
    loading.value = false
  }
}

watch(
  () => props.userId,
  newUserId => {
    if (newUserId) {
      fetchUserInfo()
    }
  },
  { immediate: true }
)

onMounted(() => {
  console.log('组件已挂载')
})

TypeScript 规范

类型定义

基础类型定义

typescript
// ✅ 推荐:使用明确的类型定义
interface UserInfo {
  id: string
  username: string
  nickname?: string
  avatar?: string
  email: string
  phone?: string
  createdAt: string
  updatedAt: string
}

// ✅ 推荐:使用联合类型
type UserStatus = 'active' | 'inactive' | 'pending' | 'banned'

// ✅ 推荐:使用泛型
interface ApiResponse<T = any> {
  code: number
  message: string
  data: T
  success: boolean
}

// ✅ 推荐:使用工具类型
type CreateUserRequest = Omit<UserInfo, 'id' | 'createdAt' | 'updatedAt'>
type UpdateUserRequest = Partial<Pick<UserInfo, 'nickname' | 'avatar' | 'email'>>

Vue 组件类型定义

typescript
// ✅ 推荐:组件Props类型定义
interface ComponentProps {
  title: string
  visible?: boolean
  size?: 'small' | 'medium' | 'large'
  onConfirm?: (data: any) => void
  onCancel?: () => void
}

// ✅ 推荐:组件Emits类型定义
interface ComponentEmits {
  (e: 'update:visible', visible: boolean): void
  (e: 'confirm', data: any): void
  (e: 'cancel'): void
}

// ✅ 推荐:Ref类型定义
const userList: Ref<UserInfo[]> = ref([])
const currentUser: Ref<UserInfo | null> = ref(null)
const loading: Ref<boolean> = ref(false)

类型导入导出

typescript
// ✅ 推荐:明确区分类型导入和值导入
import type { UserInfo, ApiResponse } from '@/types/user'
import type { ComponentProps } from '@/types/component'
import { ref, computed } from 'vue'
import { getUserInfo } from '@/api/user'

// ✅ 推荐:导出类型
export type { UserInfo, ApiResponse }
export interface ComponentInstance {
  refresh: () => void
  reset: () => void
}

Vue 3 Composition API 规范

组件结构

vue
<script setup lang="ts">
// 1. 类型导入
import type { Ref } from 'vue'
import type { UserInfo } from '@/types/user'

// 2. 依赖导入
import { ref, computed, watch, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/store/user'

// 3. Props 定义
interface Props {
  userId: string
  showActions?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  showActions: true,
})

// 4. Emits 定义
interface Emits {
  (e: 'user-loaded', user: UserInfo): void
  (e: 'error', error: Error): void
}

const emit = defineEmits<Emits>()

// 5. 响应式数据
const userInfo: Ref<UserInfo | null> = ref(null)
const loading = ref(false)
const error: Ref<Error | null> = ref(null)

// 6. 计算属性
const displayName = computed(() => {
  return userInfo.value?.nickname || userInfo.value?.username || '未知用户'
})

const isVip = computed(() => {
  return userInfo.value?.vipLevel && userInfo.value.vipLevel > 0
})

// 7. 方法定义
const fetchUserInfo = async () => {
  try {
    loading.value = true
    error.value = null

    const response = await getUserInfo(props.userId)
    userInfo.value = response.data

    emit('user-loaded', response.data)
  } catch (err) {
    error.value = err as Error
    emit('error', err as Error)
    console.error('获取用户信息失败:', err)
  } finally {
    loading.value = false
  }
}

const handleRefresh = () => {
  fetchUserInfo()
}

// 8. 监听器
watch(
  () => props.userId,
  (newUserId, oldUserId) => {
    if (newUserId && newUserId !== oldUserId) {
      fetchUserInfo()
    }
  },
  { immediate: true }
)

// 9. 生命周期
onMounted(() => {
  console.log('用户信息组件已挂载')
})

// 10. 暴露给父组件
defineExpose({
  refresh: handleRefresh,
  userInfo: readonly(userInfo),
  loading: readonly(loading),
})
</script>

<template>
  <view class="user-info-container">
    <!-- 模板内容 -->
  </view>
</template>

Composables 规范

typescript
// composables/useUserInfo.ts
import type { Ref } from 'vue'
import { ref, computed } from 'vue'
import type { UserInfo } from '@/types/user'
import { getUserInfo } from '@/api/user'

interface UseUserInfoOptions {
  immediate?: boolean
  onSuccess?: (user: UserInfo) => void
  onError?: (error: Error) => void
}

export function useUserInfo(userId: Ref<string> | string, options: UseUserInfoOptions = {}) {
  const { immediate = false, onSuccess, onError } = options

  const userInfo: Ref<UserInfo | null> = ref(null)
  const loading = ref(false)
  const error: Ref<Error | null> = ref(null)

  const displayName = computed(() => {
    return userInfo.value?.nickname || userInfo.value?.username || '未知用户'
  })

  const fetchUserInfo = async () => {
    const id = unref(userId)
    if (!id) return

    try {
      loading.value = true
      error.value = null

      const response = await getUserInfo(id)
      userInfo.value = response.data

      onSuccess?.(response.data)
    } catch (err) {
      error.value = err as Error
      onError?.(err as Error)
    } finally {
      loading.value = false
    }
  }

  const refresh = () => fetchUserInfo()

  const reset = () => {
    userInfo.value = null
    error.value = null
    loading.value = false
  }

  // 监听userId变化
  watch(
    () => unref(userId),
    newId => {
      if (newId) {
        fetchUserInfo()
      } else {
        reset()
      }
    },
    { immediate }
  )

  return {
    userInfo: readonly(userInfo),
    loading: readonly(loading),
    error: readonly(error),
    displayName,
    fetchUserInfo,
    refresh,
    reset,
  }
}

状态管理 (Pinia) 规范

Store 定义

typescript
// store/user.ts
import { defineStore } from 'pinia'
import type { UserInfo, LoginRequest, LoginResponse } from '@/types/user'
import { login, getUserProfile, logout } from '@/api/auth'
import { removeToken, setToken } from '@/utils/auth'

interface UserState {
  userInfo: UserInfo | null
  token: string | null
  isLoggedIn: boolean
  permissions: string[]
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    userInfo: null,
    token: null,
    isLoggedIn: false,
    permissions: [],
  }),

  getters: {
    // ✅ 推荐:使用箭头函数定义getter
    displayName: (state): string => {
      return state.userInfo?.nickname || state.userInfo?.username || '未知用户'
    },

    isVip: (state): boolean => {
      return Boolean(state.userInfo?.vipLevel && state.userInfo.vipLevel > 0)
    },

    // ✅ 推荐:带参数的getter
    hasPermission: state => {
      return (permission: string): boolean => {
        return state.permissions.includes(permission)
      }
    },
  },

  actions: {
    // ✅ 推荐:异步action使用async/await
    async login(loginData: LoginRequest): Promise<LoginResponse> {
      try {
        const response = await login(loginData)
        const { token, userInfo } = response.data

        this.token = token
        this.userInfo = userInfo
        this.isLoggedIn = true

        setToken(token)

        return response
      } catch (error) {
        console.error('登录失败:', error)
        throw error
      }
    },

    async fetchUserProfile(): Promise<void> {
      try {
        const response = await getUserProfile()
        this.userInfo = response.data
      } catch (error) {
        console.error('获取用户信息失败:', error)
        throw error
      }
    },

    async logout(): Promise<void> {
      try {
        await logout()
      } catch (error) {
        console.error('退出登录失败:', error)
      } finally {
        this.resetUserState()
      }
    },

    resetUserState(): void {
      this.userInfo = null
      this.token = null
      this.isLoggedIn = false
      this.permissions = []
      removeToken()
    },

    updateUserInfo(userInfo: Partial<UserInfo>): void {
      if (this.userInfo) {
        this.userInfo = { ...this.userInfo, ...userInfo }
      }
    },
  },

  // ✅ 推荐:使用持久化插件
  persist: {
    key: 'user-store',
    storage: uni.getStorageSync
      ? {
          getItem: (key: string) => uni.getStorageSync(key),
          setItem: (key: string, value: string) => uni.setStorageSync(key, value),
          removeItem: (key: string) => uni.removeStorageSync(key),
        }
      : localStorage,
    paths: ['userInfo', 'token', 'isLoggedIn'],
  },
})

API 请求规范

Alova 配置

typescript
// http/alova.ts
import { createAlova } from 'alova'
import adapterUniapp from '@alova/adapter-uniapp'
import type { AlovaGenerics } from 'alova'

// ✅ 推荐:定义通用的API响应类型
export interface ApiResponse<T = any> {
  code: number
  message: string
  data: T
  success: boolean
  timestamp: number
}

// ✅ 推荐:创建Alova实例
export const alovaInstance = createAlova({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  statesHook: adapterUniapp.VueHook,
  requestAdapter: adapterUniapp.uniappRequestAdapter(),

  // 全局请求拦截器
  beforeRequest(method) {
    const token = uni.getStorageSync('token')
    if (token) {
      method.config.headers = {
        ...method.config.headers,
        Authorization: `Bearer ${token}`,
      }
    }
  },

  // 全局响应拦截器
  responded: {
    onSuccess: async response => {
      const data = response.data as ApiResponse

      if (data.success) {
        return data
      } else {
        // 处理业务错误
        throw new Error(data.message || '请求失败')
      }
    },

    onError: error => {
      console.error('请求错误:', error)

      // 处理HTTP错误
      if (error.status === 401) {
        // 处理未授权
        uni.navigateTo({ url: '/pages/login/login' })
      } else if (error.status === 403) {
        // 处理权限不足
        uni.showToast({ title: '权限不足', icon: 'none' })
      }

      throw error
    },
  },
})

API 定义

typescript
// api/user.ts
import type { Method } from 'alova'
import { alovaInstance } from '@/http/alova'
import type { ApiResponse } from '@/http/alova'
import type { UserInfo, LoginRequest, LoginResponse, UserListParams } from '@/types/user'

// ✅ 推荐:用户登录
export const login = (data: LoginRequest): Method<AlovaGenerics<any, any, LoginResponse>> => {
  return alovaInstance.Post<ApiResponse<LoginResponse>>('/auth/login', data)
}

// ✅ 推荐:获取用户信息
export const getUserInfo = (userId: string): Method<AlovaGenerics<any, any, UserInfo>> => {
  return alovaInstance.Get<ApiResponse<UserInfo>>(`/users/${userId}`)
}

// ✅ 推荐:获取用户列表(分页)
export const getUserList = (
  params: UserListParams
): Method<AlovaGenerics<any, any, { list: UserInfo[]; total: number }>> => {
  return alovaInstance.Get<ApiResponse<{ list: UserInfo[]; total: number }>>('/users', {
    params,
  })
}

// ✅ 推荐:更新用户信息
export const updateUserInfo = (
  userId: string,
  data: Partial<UserInfo>
): Method<AlovaGenerics<any, any, UserInfo>> => {
  return alovaInstance.Put<ApiResponse<UserInfo>>(`/users/${userId}`, data)
}

// ✅ 推荐:删除用户
export const deleteUser = (userId: string): Method<AlovaGenerics<any, any, boolean>> => {
  return alovaInstance.Delete<ApiResponse<boolean>>(`/users/${userId}`)
}

工具函数规范

通用工具函数

typescript
// utils/index.ts
import type { PageMetaDatum } from '@uni-helper/vite-plugin-uni-pages'

// ✅ 推荐:类型安全的工具函数
export function parseUrlToObj(url: string): { path: string; query: Record<string, string> } {
  const [path, queryStr] = url.split('?')

  if (!queryStr) {
    return { path, query: {} }
  }

  const query: Record<string, string> = {}
  queryStr.split('&').forEach(item => {
    const [key, value] = item.split('=')
    if (key && value !== undefined) {
      query[key] = ensureDecodeURIComponent(value)
    }
  })

  return { path, query }
}

export function ensureDecodeURIComponent(url: string): string {
  if (url.startsWith('%')) {
    return ensureDecodeURIComponent(decodeURIComponent(url))
  }
  return url
}

// ✅ 推荐:泛型工具函数
export function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: NodeJS.Timeout | null = null

  return function executedFunction(...args: Parameters<T>) {
    const later = () => {
      timeout = null
      func(...args)
    }

    if (timeout) {
      clearTimeout(timeout)
    }
    timeout = setTimeout(later, wait)
  }
}

export function throttle<T extends (...args: any[]) => any>(
  func: T,
  limit: number
): (...args: Parameters<T>) => void {
  let inThrottle: boolean = false

  return function executedFunction(...args: Parameters<T>) {
    if (!inThrottle) {
      func(...args)
      inThrottle = true
      setTimeout(() => (inThrottle = false), limit)
    }
  }
}

// ✅ 推荐:类型守卫函数
export function isString(value: unknown): value is string {
  return typeof value === 'string'
}

export function isNumber(value: unknown): value is number {
  return typeof value === 'number' && !isNaN(value)
}

export function isObject(value: unknown): value is Record<string, any> {
  return value !== null && typeof value === 'object' && !Array.isArray(value)
}

错误处理规范

统一错误处理

typescript
// utils/error.ts
export class AppError extends Error {
  public readonly code: string
  public readonly statusCode: number

  constructor(message: string, code: string = 'UNKNOWN_ERROR', statusCode: number = 500) {
    super(message)
    this.name = 'AppError'
    this.code = code
    this.statusCode = statusCode
  }
}

export class ValidationError extends AppError {
  constructor(message: string, field?: string) {
    super(message, 'VALIDATION_ERROR', 400)
    this.name = 'ValidationError'
  }
}

export class NetworkError extends AppError {
  constructor(message: string = '网络连接失败') {
    super(message, 'NETWORK_ERROR', 0)
    this.name = 'NetworkError'
  }
}

// ✅ 推荐:错误处理函数
export function handleError(error: unknown): void {
  console.error('应用错误:', error)

  if (error instanceof AppError) {
    uni.showToast({
      title: error.message,
      icon: 'none',
      duration: 2000,
    })
  } else if (error instanceof Error) {
    uni.showToast({
      title: error.message || '操作失败',
      icon: 'none',
      duration: 2000,
    })
  } else {
    uni.showToast({
      title: '未知错误',
      icon: 'none',
      duration: 2000,
    })
  }
}

// ✅ 推荐:异步错误包装
export async function safeAsync<T>(
  asyncFn: () => Promise<T>,
  fallback?: T
): Promise<[T | null, Error | null]> {
  try {
    const result = await asyncFn()
    return [result, null]
  } catch (error) {
    return [fallback || null, error as Error]
  }
}

代码注释规范

JSDoc 注释

typescript
/**
 * 获取用户信息
 * @param userId - 用户ID
 * @param options - 可选配置
 * @param options.includeProfile - 是否包含详细资料
 * @param options.cache - 是否使用缓存
 * @returns Promise<UserInfo> 用户信息
 * @throws {ValidationError} 当用户ID无效时抛出
 * @throws {NetworkError} 当网络请求失败时抛出
 * @example
 * ```typescript
 * const user = await getUserInfo('123', { includeProfile: true })
 * console.log(user.nickname)
 * ```
 */
export async function getUserInfo(
  userId: string,
  options: {
    includeProfile?: boolean
    cache?: boolean
  } = {}
): Promise<UserInfo> {
  // 实现代码
}

/**
 * 用户信息接口
 * @interface UserInfo
 */
export interface UserInfo {
  /** 用户ID */
  id: string
  /** 用户名 */
  username: string
  /** 昵称 */
  nickname?: string
  /** 头像URL */
  avatar?: string
  /** 邮箱地址 */
  email: string
  /** 手机号码 */
  phone?: string
  /** VIP等级 */
  vipLevel: number
  /** 创建时间 */
  createdAt: string
  /** 更新时间 */
  updatedAt: string
}

性能优化

代码分割和懒加载

typescript
// ✅ 推荐:路由懒加载
const routes = [
  {
    path: '/user',
    component: () => import('@/pages/user/index.vue'),
  },
  {
    path: '/profile',
    component: () => import('@/pages/profile/index.vue'),
  },
]

// ✅ 推荐:组件懒加载
const LazyComponent = defineAsyncComponent(() => import('@/components/heavy-component.vue'))

// ✅ 推荐:条件导入
const loadChartLibrary = async () => {
  if (process.env.NODE_ENV === 'development') {
    const { Chart } = await import('chart.js')
    return Chart
  }
  return null
}

响应式优化

typescript
// ✅ 推荐:使用shallowRef优化大对象
const largeData = shallowRef<LargeDataType>({})

// ✅ 推荐:使用markRaw标记不需要响应式的对象
const staticConfig = markRaw({
  apiEndpoints: {
    user: '/api/users',
    order: '/api/orders',
  },
})

// ✅ 推荐:使用computed缓存计算结果
const expensiveComputed = computed(() => {
  return heavyCalculation(props.data)
})

// ✅ 推荐:使用watchEffect优化副作用
watchEffect(() => {
  if (userStore.isLoggedIn) {
    fetchUserData()
  }
})

测试规范

单元测试示例

typescript
// __tests__/utils/index.test.ts
import { describe, it, expect } from 'vitest'
import { parseUrlToObj, debounce } from '@/utils/index'

describe('parseUrlToObj', () => {
  it('should parse URL with query parameters', () => {
    const result = parseUrlToObj('/pages/user?id=123&name=test')
    expect(result).toEqual({
      path: '/pages/user',
      query: { id: '123', name: 'test' },
    })
  })

  it('should handle URL without query parameters', () => {
    const result = parseUrlToObj('/pages/user')
    expect(result).toEqual({
      path: '/pages/user',
      query: {},
    })
  })
})

describe('debounce', () => {
  it('should debounce function calls', async () => {
    let callCount = 0
    const debouncedFn = debounce(() => {
      callCount++
    }, 100)

    debouncedFn()
    debouncedFn()
    debouncedFn()

    expect(callCount).toBe(0)

    await new Promise(resolve => setTimeout(resolve, 150))
    expect(callCount).toBe(1)
  })
})

ESLint 配置

项目使用的主要ESLint规则:

javascript
// eslint.config.mjs
export default uniHelper({
  unocss: true,
  vue: true,
  rules: {
    'no-console': 'off', // 允许console
    'no-unused-vars': 'off', // 关闭未使用变量检查
    'vue/no-unused-refs': 'off', // 关闭未使用ref检查
    'unused-imports/no-unused-vars': 'off', // 关闭未使用导入检查
    'ts/no-empty-object-type': 'off', // 允许空对象类型
    'vue/block-order': [
      'error',
      {
        // Vue SFC块顺序
        order: [['script', 'template'], 'style'],
      },
    ],
  },
})

最佳实践

  1. 类型安全: 充分利用TypeScript的类型系统,避免使用any
  2. 组合式API: 优先使用Composition API,合理组织代码逻辑
  3. 响应式优化: 合理使用refreactivecomputed等响应式API
  4. 错误处理: 统一错误处理机制,提供良好的用户体验
  5. 代码复用: 通过composables和工具函数提高代码复用性
  6. 性能优化: 注意避免不必要的响应式转换和计算
  7. 类型定义: 为API响应、组件Props等定义明确的类型
  8. 代码注释: 为复杂逻辑和公共函数添加详细注释

工具推荐

  • VS Code插件:
    • TypeScript Vue Plugin (Volar)
    • ESLint
    • Prettier
    • Auto Rename Tag
    • Path Intellisense
  • 开发工具:
    • Vue DevTools
    • UniApp开发者工具
    • Postman/Apifox (API测试)
  • 类型工具:
    • type-fest (实用类型工具)
    • utility-types (类型工具库)

上次更新于: