前端常用工具库

前端常用工具库

轮子别自己造, 这是我整理的前端常用库地图: 每个库解决什么痛、什么时候用、什么时候别用。Java 世界里有 Hutool 和 Commons, 前端对应的就是这一批。写熟了 05 篇那条 CRUD 链路再看这篇, 会知道每个库都往哪个位置装。

一张地图先对号:

代码块PLAINTEXT · 5 行收起展开
数据处理 → Lodash-es        日期处理 → Day.js
HTTP 请求 → Axios           组合式工具 → VueUse
UI 组件 → Element Plus      图表 → ECharts
表单校验 → VeeValidate      拖拽排序 → SortableJS
虚拟滚动 → vue-virtual-scroller    富文本 → TipTap

Lodash-es: 工具函数百宝箱

解决的痛: 深拷贝、防抖节流、分组排序这些高频操作, 原生 JS 要么没有要么写着啰嗦。装 lodash-es 版本 (支持 tree-shaking, 打包时只带用到的函数, 别装老的 lodash):

代码块JAVASCRIPT · 15 行收起展开
import {
  cloneDeep,    // 深拷贝, 前端的 BeanUtils 加强版
  debounce,     // 防抖: 停止触发 N 毫秒后才执行 (搜索框标配)
  throttle,     // 节流: N 毫秒内最多执行一次 (滚动事件标配)
  groupBy,      // 分组, 相当于 Stream 的 Collectors.groupingBy
  orderBy,      // 多字段排序
  uniq,         // 去重
  pick, omit    // 挑选/排除对象属性
} from 'lodash-es'

const search = debounce(keyword => fetchList(keyword), 500)   // 用户停手 500ms 才真发请求

const grouped = groupBy(users, 'role')          // { admin: [...], user: [...] }
const sorted = orderBy(users, ['age', 'name'], ['desc', 'asc'])
const safe = omit(user, ['password'])           // 传给日志前把敏感字段摘掉

什么时候别用: 原生已经够用的场景 (arr.map?. 可选链、structuredClone), 别为一个函数引一个库。

Day.js: 日期处理

原生 Date 的 API 是出了名的难用, Day.js 用 2KB 的体积给了一套人话 API (前端的 java.time 平替):

代码块JAVASCRIPT · 12 行收起展开
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'   // 插件机制: 核心极小, 功能按需挂
import 'dayjs/locale/zh-cn'

dayjs.extend(relativeTime)
dayjs.locale('zh-cn')

dayjs().format('YYYY-MM-DD HH:mm:ss')       // 格式化
dayjs('2026-01-15').add(7, 'day')           // 日期运算
dayjs().startOf('day')                      // 今天 00:00:00
dayjs('2026-01-20').diff('2026-01-15', 'day')  // 差值: 5
dayjs(post.createdAt).fromNow()             // "3 小时前", 评论区标配

一个用它练手的真实场景, 倒计时组件 (注意 01 篇讲过的定时器清理):

代码块VUE · 31 行收起展开
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import dayjs from 'dayjs'

const props = defineProps({ endTime: String })
const countdown = ref('')
let timer = null

function update() {
  const diff = dayjs(props.endTime).diff(dayjs(), 'second')
  if (diff <= 0) {
    countdown.value = '已结束'
    clearInterval(timer)
    return
  }
  const d = Math.floor(diff / 86400)
  const h = Math.floor((diff % 86400) / 3600)
  const m = Math.floor((diff % 3600) / 60)
  countdown.value = `${d}天 ${h}小时 ${m}分 ${diff % 60}秒`
}

onMounted(() => {
  update()
  timer = setInterval(update, 1000)
})
onUnmounted(() => clearInterval(timer))   // 忘了这行就是内存泄漏
</script>

<template>
  <div>{{ countdown }}</div>
</template>

Axios: HTTP 请求

fetch 的进化版: 自动 JSON 转换、超时、拦截器、请求取消都是开箱即有。它在项目里的正确姿势是封装成统一请求层 (拦截器统一挂 token、统一处理 401、统一错误提示), 完整封装和分层思路在 05 篇写透了, 这里不重复。只记一条使用侧的样子:

代码块JAVASCRIPT · 4 行收起展开
// 业务代码里永远只碰封装后的 api 模块, 不直接 import axios
import { userApi } from '@/api/user'

const users = await userApi.getList({ page: 1, size: 10 })

VueUse: 组合式函数军火库

Vue 生态最值得装的库。浏览器各种能力 (窗口尺寸、剪贴板、网络状态、localStorage) 的响应式封装, 全是 02 篇讲的 useXxx 组合式函数形态:

代码块VUE · 13 行收起展开
<script setup>
import { useClipboard, useLocalStorage, useOnline, useWindowSize } from '@vueuse/core'

const token = useLocalStorage('token', '')   // 响应式 localStorage: 赋值自动存, 多标签页还同步
const { width } = useWindowSize()            // 窗口宽度, 变了自动更新
const online = useOnline()                   // 断网检测
const { copy, copied } = useClipboard()      // 一键复制 + 复制成功状态
</script>

<template>
  <button @click="copy('要复制的内容')">{{ copied ? '已复制' : '复制' }}</button>
  <p v-if="!online">网络断了</p>
</template>

自己动手写 useXxx 之前先翻它的文档, 大概率已经有了, 而且人家处理好了清理和边界。

Element Plus: 按需导入

快速上手篇用的全量引入省事但打包全带上了。正式项目配自动按需导入, 用到哪个组件打包哪个:

npm install -D unplugin-vue-components unplugin-auto-import
// vite.config.js
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default {
  plugins: [
    AutoImport({ resolvers: [ElementPlusResolver()] }),     // ElMessage 这类函数自动引入
    Components({ resolvers: [ElementPlusResolver()] })      // 模板里的 el-xxx 自动引入
  ]
}

配完之后模板里直接写 <el-button>, 连 import 都不用写, 构建时插件自动补。第一次见到”没 import 也能用”别慌, 是这两个插件在背后干活。

ECharts: 图表

数据可视化的国民库。要点就一个模式: 拿个 div 当画布, init 后 setOption:

代码块VUE · 26 行收起展开
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import * as echarts from 'echarts'

const chartRef = ref(null)
let chart = null

onMounted(() => {
  chart = echarts.init(chartRef.value)     // 必须等 DOM 挂载后才能 init
  chart.setOption({
    xAxis: { data: ['1月', '2月', '3月'] },
    yAxis: {},
    series: [{ type: 'bar', data: [5, 20, 36] }]
  })
  window.addEventListener('resize', chart.resize)
})

onUnmounted(() => {
  window.removeEventListener('resize', chart.resize)
  chart?.dispose()                          // 图表实例不销毁也是内存泄漏
})
</script>

<template>
  <div ref="chartRef" style="width: 600px; height: 400px;"></div>
</template>

ECharts 很重, 一定走路由懒加载或 defineAsyncComponent (02 篇), 别打进首屏。

VeeValidate + Yup: 脱离组件库的表单校验

用了 Element Plus 就用它自带的 rules (05 篇)。但不用组件库的场景 (自定义 UI、移动端), VeeValidate 配 Yup 声明式校验很舒服:

代码块JAVASCRIPT · 6 行收起展开
import * as yup from 'yup'

const schema = yup.object({
  email: yup.string().required('邮箱不能为空').email('邮箱格式不正确'),
  age: yup.number().min(18, '未满 18')
})

schema 定义和后端的 Bean Validation 注解 (@NotNull/@Email) 思路完全一致, 声明规则, 框架负责执行。

挑库的原则

装库之前过四关: 最近还在维护吗 (GitHub 最后提交时间); 体积多大 (bundlephobia.com 查, 前端每 KB 都算流量); 支不支持按需引入; 原生是不是已经能做了。库解决的是效率, 不能解决理解, 每个引进来的库至少要知道它替我干了什么脏活, 不然出了 bug 连排查的入口都找不到。

延伸阅读