前端怎么封装工具类

前端封装工具类,本质是把某一类通用逻辑(如日期、存储、请求、校验、DOM、URL、设备判断等)集中到一个独立模块里,对外暴露清晰、稳定、可复用的 API。

和"公共方法"相比,公共方法 更泛指所有可复用的函数;工具类 通常指按功能领域组织的一组工具函数或静态方法集合,比如 DateUtils、StorageUtils、RequestUtils。

现代前端更推荐:模块化 + 命名导出 + 纯函数优先 ,而不是传统 OOP 的 class 一把梭。下面以 Vue3 + TypeScript 项目为例,讲清楚怎么封装工具类。


一、先选封装形式

形式 写法 适用场景 推荐度
模块函数 export function formatDate() {} 绝大多数工具类 最高
对象字面量 export const storage = { get, set } 一组相关方法,如存储 高
类 + 静态方法 class DateUtils { static format() {} } 需要命名空间、链式调用 中
组合式函数 export function usePagination() {} 带响应式状态,Vue3 高
高阶函数 debounce(fn, delay) 防抖、节流、缓存 高

建议 :优先用模块函数和对象字面量,class 只在确实需要时用。


二、目录怎么组织

推荐按功能领域拆文件,统一放 src/utils:

text 复制代码
src/
├── utils/
│   ├── index.ts          # 统一出口
│   ├── date.ts           # 日期工具
│   ├── string.ts         # 字符串工具
│   ├── array.ts          # 数组工具
│   ├── object.ts         # 对象工具
│   ├── storage.ts        # 本地存储
│   ├── request.ts        # 请求封装
│   ├── validate.ts       # 校验工具
│   ├── url.ts            # URL 工具
│   ├── dom.ts            # DOM 工具
│   ├── env.ts            # 环境判断
│   ├── debounce.ts       # 防抖
│   └── throttle.ts       # 节流
├── hooks/                # Vue3 组合式函数
│   └── usePagination.ts
└── services/             # 接口服务

每个文件只负责一个领域,避免一个 utils.ts 塞几百个函数。


三、封装原则

  1. 单一职责:一个文件或一个函数只做一件事。
  2. 纯函数优先:相同输入得到相同输出,不依赖外部状态。
  3. 无副作用:除非是存储、请求、DOM 操作等本身就有副作用的工具。
  4. 输入输出明确:参数和返回值清晰,有 TypeScript 类型。
  5. 默认参数:降低调用成本。
  6. 错误处理:内部处理或抛出明确错误。
  7. 不污染全局 :不要挂到 window。
  8. 按需导出:支持 Tree-shaking。
  9. 可测试:容易写单元测试。
  10. 不耦合业务:工具类不应依赖具体页面或 store。

四、具体示例

1. 日期工具类

ts 复制代码
// utils/date.ts
export function formatDate(
  date: Date | string | number,
  format = 'YYYY-MM-DD HH:mm:ss'
): string {
  const d = new Date(date)
  const map: Record<string, number> = {
    YYYY: d.getFullYear(),
    MM: d.getMonth() + 1,
    DD: d.getDate(),
    HH: d.getHours(),
    mm: d.getMinutes(),
    ss: d.getSeconds()
  }

  return format.replace(/YYYY|MM|DD|HH|mm|ss/g, key => {
    return String(map[key]).padStart(2, '0')
  })
}

export function isToday(date: Date | string | number): boolean {
  const d = new Date(date)
  const now = new Date()
  return (
    d.getFullYear() === now.getFullYear() &&
    d.getMonth() === now.getMonth() &&
    d.getDate() === now.getDate()
  )
}

使用:

ts 复制代码
import { formatDate, isToday } from '@/utils/date'
formatDate(new Date(), 'YYYY-MM-DD') // 2026-10-15

2. 存储工具类

ts 复制代码
// utils/storage.ts
const PREFIX = 'app_'

export const storage = {
  set(key: string, value: any) {
    localStorage.setItem(PREFIX + key, JSON.stringify(value))
  },
  get<T = any>(key: string): T | null {
    const raw = localStorage.getItem(PREFIX + key)
    if (!raw) return null
    try {
      return JSON.parse(raw) as T
    } catch {
      return null
    }
  },
  remove(key: string) {
    localStorage.removeItem(PREFIX + key)
  },
  clear() {
    Object.keys(localStorage)
      .filter(k => k.startsWith(PREFIX))
      .forEach(k => localStorage.removeItem(k))
  }
}

使用:

ts 复制代码
import { storage } from '@/utils/storage'
storage.set('token', 'abc')
const token = storage.get<string>('token')

3. 请求工具类

ts 复制代码
// utils/request.ts
import axios from 'axios'
import { storage } from './storage'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
})

request.interceptors.request.use(config => {
  const token = storage.get<string>('token')
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})

request.interceptors.response.use(
  res => res.data,
  err => {
    // 统一错误处理
    return Promise.reject(err)
  }
)

export default request

4. 校验工具类

ts 复制代码
// utils/validate.ts
export const isPhone = (value: string): boolean => /^1[3-9]\d{9}$/.test(value)

export const isEmail = (value: string): boolean =>
  /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)

export const isIdCard = (value: string): boolean =>
  /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(value)

5. 防抖与节流

ts 复制代码
// utils/debounce.ts
export function debounce<T extends (...args: any[]) => void>(
  fn: T,
  delay = 300
) {
  let timer: ReturnType<typeof setTimeout> | null = null
  return function (this: any, ...args: Parameters<T>) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}
ts 复制代码
// utils/throttle.ts
export function throttle<T extends (...args: any[]) => void>(
  fn: T,
  interval = 300
) {
  let last = 0
  return function (this: any, ...args: Parameters<T>) {
    const now = Date.now()
    if (now - last >= interval) {
      last = now
      fn.apply(this, args)
    }
  }
}

6. 环境判断工具类

ts 复制代码
// utils/env.ts
export const isIOS = () => /iPhone|iPad|iPod/i.test(navigator.userAgent)
export const isAndroid = () => /Android/i.test(navigator.userAgent)
export const isWechat = () => /MicroMessenger/i.test(navigator.userAgent)
export const isMobile = () => isIOS() || isAndroid()

7. URL 工具类

ts 复制代码
// utils/url.ts
export function getQuery(name: string): string | null {
  return new URLSearchParams(location.search).get(name)
}

export function buildQuery(params: Record<string, any>): string {
  return Object.entries(params)
    .filter(([, v]) => v !== undefined && v !== null)
    .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
    .join('&')
}

五、统一出口与按需导入

在 utils/index.ts 统一导出:

ts 复制代码
export * from './date'
export * from './storage'
export * from './validate'
export * from './debounce'
export * from './throttle'
export * from './url'
export * from './env'

使用:

ts 复制代码
import { formatDate, storage, isPhone } from '@/utils'

也可以按需导入:

ts 复制代码
import { formatDate } from '@/utils/date'

建议:大型项目优先按需导入,减少打包体积;小型项目可以用统一出口。


六、TypeScript 类型支持

工具类一定要写类型:

ts 复制代码
export function formatDate(
  date: Date | string | number,
  format?: string
): string

泛型:

ts 复制代码
export function deepClone<T>(value: T): T

返回类型明确,调用方有提示,减少错误。


七、带响应式的工具:组合式函数

如果工具类需要响应式状态,应封装成 Vue3 的 composables 或 hooks:

ts 复制代码
// hooks/usePagination.ts
import { ref } from 'vue'

export function usePagination(fetchFn: (params: any) => Promise<any>) {
  const page = ref(1)
  const pageSize = ref(10)
  const total = ref(0)
  const list = ref([])
  const loading = ref(false)

  async function load() {
    loading.value = true
    try {
      const res = await fetchFn({ page: page.value, pageSize: pageSize.value })
      list.value = res.data.list
      total.value = res.data.total
    } finally {
      loading.value = false
    }
  }

  return { page, pageSize, total, list, loading, load }
}

使用:

ts 复制代码
const { list, loading, load } = usePagination(api.getUserList)

八、测试与文档

  • 单元测试:用 Vitest 测试纯函数。
  • JSDoc 注释:说明参数、返回值、示例。
  • README :在 utils 目录下写说明。
  • 类型提示:TypeScript 本身就是最好的文档。

示例注释:

ts 复制代码
/**
 * 格式化日期
 * @param date 日期对象、时间戳或字符串
 * @param format 格式,默认 YYYY-MM-DD HH:mm:ss
 * @returns 格式化后的字符串
 * @example
 * formatDate(new Date(), 'YYYY-MM-DD') // '2026-10-15'
 */

九、常见坑

  • 把业务逻辑塞进工具类,导致工具类依赖具体业务。
  • 直接挂到 window,污染全局。
  • 不处理异常,调用方容易崩溃。
  • 参数过多,没有默认值。
  • 不写类型,调用方不知道传什么。
  • 循环依赖:a.ts 引 b.ts,b.ts 又引 a.ts。
  • 过度封装,简单逻辑也包一层。
  • 不写测试,改一处崩多处。
  • 命名混乱,utils1、common、helper 混用。

十、总结

前端封装工具类的核心:

按领域拆模块,纯函数优先,类型明确,按需导出,写好测试和文档。

一个合格的工具类应该:

  • 别人不看源码就会用
  • 输入输出清晰
  • 不依赖具体页面
  • 容易测试
  • 不会互相影响
  • 能按需加载

从项目中最常重复的逻辑开始封装,逐步沉淀成自己的工具库。

如果逻辑带响应式,就封装成 Vue3 的组合式函数;如果纯逻辑,就放 utils 里按模块导出。

相关推荐
__sjfzllv___1 小时前
在职前端Leader学习/转行 AI Agent -DAY78
前端
deli0071 小时前
PDF 合并别再用在线工具:12 份文件浏览器里合完,实测不上传
前端
shuoshuohaohao1 小时前
SpringBoot3-web开发-静态资源-内容协商
前端
kiros_wang1 小时前
RDB关系数据库实战:增删改查、事务、分页、版本迁移
前端
用户1733598075371 小时前
纯前端 PDF 双面打印避坑指南:翻页顺序错了整个白印?
前端·javascript·vue.js
庖丁解牛1 小时前
第四课:循环,让电脑重复做事(4)
前端
数据掘金1 小时前
用户路径分析的会话切分与节点聚合实现
前端
代码猎人1 小时前
前端怎么搭建项目基础架构
前端
lichenyang4531 小时前
Promise 基础与进阶:状态规则、链式错误处理和异步执行顺序
前端