4.5 其他常用工具


文档摘要

4.5 其他常用工具 本节摘要:React 项目里还有一批「小而具体」的工具库,每个解决一个明确问题。axios 管 HTTP 请求,Lodash 管数据处理,classnames 管类名拼接,day.js 管日期,React Hook Form 管表单。本节逐个讲清「解决什么、怎么用、何时值得引入」,并给出「工具库该不该引入」的判断标准。 核心问题 阅读完本节,你应当能够: 用 axios 发起请求并处理错误,理解拦截器的价值 用 Lodash 的 filter、sortBy、debounce 处理数据与事件 用 classnames 条件拼接类名 用 day.

4.5 其他常用工具

本节摘要:React 项目里还有一批「小而具体」的工具库,每个解决一个明确问题。axios 管 HTTP 请求,Lodash 管数据处理,classnames 管类名拼接,day.js 管日期,React Hook Form 管表单。本节逐个讲清「解决什么、怎么用、何时值得引入」,并给出「工具库该不该引入」的判断标准。

核心问题

阅读完本节,你应当能够:

  1. 用 axios 发起请求并处理错误,理解拦截器的价值
  2. 用 Lodash 的 filter、sortBy、debounce 处理数据与事件
  3. 用 classnames 条件拼接类名
  4. 用 day.js 处理日期格式化与计算
  5. 判断一个工具库是否值得引入

一、问题与直觉:工具库是「锦上添花」还是「必需品」

每个工具库都对应一个「手写也能做,但很烦」的问题。写 HTTP 请求用 fetch 也能行,但每次都要处理超时、错误、请求头;拼类名用模板字符串也行,但条件一多就乱;处理日期用 Date 对象也行,但格式化要手写 padStart。

工具库的价值是「把高频但易错的操作封装好」。判断要不要引用的标准只有一条:这个操作在你的项目里出现的频率,高到值得引入一个依赖吗? 一次性的操作手写,高频的操作引库——这是工具库选型的基本原则。

本节要讲的几个库,恰好覆盖了「数据从接口到界面」这条链的每一环:请求、处理、校验、表单、展示。理解这条链,你就知道每个工具库「卡」在哪一环、解决的是哪个具体问题。

二、axios:HTTP 请求库

axios 是基于 Promise 的 HTTP 客户端,浏览器和 Node 通用。相比原生 fetch,它最常被选的理由是拦截器——在请求发出前、响应返回后统一处理。

import axios from 'axios'; import { useState, useEffect } from 'react'; function DataFetching() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { axios.get('/api/todos/1') .then(res => { setData(res.data); setLoading(false); }) .catch(err => { setError(err); setLoading(false); }); }, []); if (loading) return <p>加载中...</p>; if (error) return <p>出错:{error.message}</p>; return <p>标题:{data.title}</p>; }

axios 的常用方法:get、post、put、delete,参数传法统一。错误通过 Promise rejection 抛出,用 catch 或 try/catch 处理。

拦截器:axios 的核心优势

拦截器解决「每个请求都要做的事」——加 token、统一处理 401、打日志:

// 请求拦截:每次请求自动带 token axios.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // 响应拦截:统一处理错误 axios.interceptors.response.use( res => res, err => { if (err.response?.status === 401) { // 统一跳登录页 } return Promise.reject(err); } );

有了拦截器,「每个请求都带 token」写一次,全站生效。这是 axios 对比 fetch 最实在的理由——fetch 要做到同样的事,得自己封装一层请求函数,而 axios 把「请求生命周期」开放给你挂钩子。

维度 fetch axios
拦截器 无,要自己封装 内置
请求取消 有 AbortController 有 AbortController
超时 要手动 配置项
浏览器/Node 浏览器为主 通用
体积 小依赖

请求层封装:axios 的正确用法

axios 直接散落在组件里用,会陷入「每个组件各写一套配置」的重复。工程上更标准的做法是封装请求层——把 baseURL、超时、拦截器集中配置,导出封装好的请求函数:

// api 请求层(示意) import axios from 'axios'; const request = axios.create({ baseURL: '/api', timeout: 10000, }); // 拦截器统一挂载在 request 上 request.interceptors.response.use( res => res.data, // 直接返回 data,组件不用再取 res.data err => Promise.reject(err) ); // 按业务导出 API export const getTodo = (id) => request.get(`/todos/${id}`); export const createTodo = (data) => request.post('/todos', data);

组件里 getTodo(1).then(data => ...)——不再碰 axios 细节,也不需要知道拦截器。请求层的价值在第 5 章架构设计里会再展开,它是「业务代码不依赖具体请求库」的关键抽象。

💡 关键直觉:选 axios 还是 fetch,别只看「谁好用」,看「你要不要拦截器」。项目里每个请求都要带 token、统一处理 401——axios 的拦截器一次配置全站受益。这种「横切需求」正是 axios 存在的价值。

三、Lodash:数据处理工具箱

Lodash 提供大量数组、对象、字符串处理函数,把繁琐操作变一行。React 里高频的几个:

import _ from 'lodash'; const users = [ { id: 1, name: 'Alice', age: 25 }, { id: 2, name: 'Bob', age: 30 }, { id: 3, name: 'Charlie', age: 20 }, ]; const filtered = _.filter(users, user => user.age > 25); const sorted = _.sortBy(users, 'age');

debounce 与 throttle:事件节流

React 里 Lodash 最值钱的函数之一是 debounce——输入框搜索的防抖:

import _ from 'lodash'; import { useCallback } from 'react'; function SearchBox({ onSearch }) { // 用户停止输入 500ms 后才触发搜索 const handleChange = useCallback( _.debounce(value => { onSearch(value); }, 500), [] ); return <input onChange={e => handleChange(e.target.value)} />; }

debounce 是「停顿后再执行」(输入框搜索、窗口 resize),throttle 是「固定间隔内最多执行一次」(滚动加载)。第 3.4 节表单里手写过防抖,Lodash 把它封装成了成熟的实现——带 leading/trailing 选项、可取消,比自己写稳妥。注意这里配合 useCallback 包住 debounced 函数,防止每次渲染都重新创建导致防抖失效,和第 3.1 节的记忆化思路完全一致。

该不该引 Lodash

Lodash 常被诟病「体积大、现代 JS 原生方法够用」。诚实说,filter、map、sortBy 这些原生都有,但 debounce、throttle、deepClone、groupBy 这类,手写易错,引库省心。现代做法是按需引入单个函数——只打包用到的部分,而不是整个 Lodash。按需引入后,Lodash 的体积顾虑基本消解。

操作 原生 JS Lodash
过滤/排序 够用 语法糖
防抖/节流 手写易错 成熟封装
深拷贝 structuredClone 可用 兼容更好
groupBy 手写 一行

四、classnames:类名拼接

条件类名的写法,用模板字符串容易错:

// 易错的写法 <button className={`button ${isActive ? 'active' : ''} ${isLarge ? 'large' : ''}`}>

classnames 把条件逻辑写进对象,值真假决定类名加不加:

import classNames from 'classnames'; function Button({ isActive, isLarge }) { const classes = classNames({ 'button': true, 'button--active': isActive, 'button--large': isLarge, }); return <button className={classes}>按钮</button>; }

它也支持字符串、数组混合参数。classnames 很小(几 KB),解决的是「类名条件逻辑可读性」这个小而高频的问题——几乎每个组件都用得到,引入后基本全项目受益。

五、day.js:轻量日期处理

日期处理是 JS 的老大难——Date 对象 API 难用,格式化要手写。day.js 是 Moment.js 的轻量替代,API 几乎兼容,体积小一个量级。

import dayjs from 'dayjs'; const now = dayjs(); // 当前时间 const formatted = now.format('YYYY-MM-DD'); // 格式化 const future = now.add(7, 'days'); // 加 7 天 const diffDays = future.diff(now, 'day'); // 计算差值

day.js 的插件体系覆盖相对时间、时区等高级能力,按需加载。Moment.js 体积大且已停更,新项目一律 day.js 或原生 Intl——这是日期库选型的共识。选 day.js 而不是原生 Date,图的是它的格式化、链式计算和时区插件的组合便利。

六、React Hook Form:表单管理的现代方案

第 3.4 节已经介绍过 React Hook Form 的核心用法,这里补上它的定位:它是「受控组件手写模板」的工程化替代——字段多、校验复杂时,注册式管理比手写 state 模板高效得多。它的核心卖点之一是「减少重渲染」——用 ref 而非受控 state,输入时组件不频繁重渲染,性能好。

import { useForm } from 'react-hook-form'; function ProfileForm() { const { register, handleSubmit, formState: { errors } } = useForm(); const onSubmit = (data) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('name', { required: '姓名必填' })} /> {errors.name && <p>{errors.name.message}</p>} <button type="submit">提交</button> </form> ); }

对比第 3.4 节手写的 ValidatedForm——React Hook Form 把「每个字段的 value state、onChange、错误 state」的模板全部收进 register + errors 里。字段从 2 个涨到 10 个时,手写模板的代码量线性增长,React Hook Form 几乎不变。这个「模板收敛」是引它的核心收益。

其他值得认识的数据工具

除了主打的几个,React 生态还有几类高频工具,认识它们能少造轮子:

TanStack Query / SWR:服务器状态管理库,内置缓存、重试、竞态处理、失效刷新。第 4.2 节提过它与状态库的分工——接口数据归它管。它是「数据请求」方向最值得引入的库。

Immer:不可变更新的便捷语法。produce 让你「看似直接改」地更新嵌套对象,底层生成新引用。Redux Toolkit 内部就用它,单独用则配合 useState/useReducer 管理复杂对象状态。

Zod:运行时校验 + 类型推导。接口返回的数据、表单输入,用 Zod 的 schema 校验,还能从 schema 推出 TypeScript 类型。它是「前端信任边界」的守门员——数据进到应用前先过一遍 schema。

这四类工具(请求、不可变、校验、表单)覆盖了「数据从接口到界面」的大部分痛点,是现代 React 项目里的「高性价比依赖」。

常见疑问快答

「fetch 加上自己封装的拦截器,是不是就不用 axios 了?」 可以,但要算清账。自己封装一层 request 函数,确实能实现「统一带 token、统一处理 401」,这是很多团队的做法,也完全可行。axios 的差异在于它把「请求生命周期」做成了标准接口——拦截器、取消、超时、并发控制都有成熟实现,你写的封装是「一次性私有实现」,出问题要自己修。判断标准回到那句话:项目里请求多不多、错误处理复杂不复杂。请求多、横切逻辑多,用 axios 省心;请求少,原生 fetch 包一层完全够。别为了「少一个依赖」硬写,也别为了「多一个依赖」硬引。

「Lodash 是不是已经过时了?」 看怎么用。它的很多函数(filter、map、sortBy)原生 JS 已经覆盖,这部分确实可以不用。但它有几个「手写易错、原生没有或不好用」的能力至今有存在价值:deepClone(深拷贝处理循环引用和 Date/Map 等特殊类型)、debounce/throttle 的成熟实现、groupBy 这类聚合函数、以及针对 null/undefined 的安全访问链。现代项目普遍按需引入单个函数,体积顾虑基本消失。所以结论不是「Lodash 过时了」,而是「别整个引,按需取」。

「day.js 和浏览器原生 Intl 怎么选?」 纯格式化、纯排序,Intl.DateTimeFormat 完全够,零依赖。day.js 的价值在「链式 API」和「插件体系」——格式化、加减、差值、相对时间、时区可以串成一串调用,写起来是一行,而原生 Intl 是零散的静态方法。项目里如果只是「偶尔显示个日期」,用 Intl;如果日期操作遍布代码(报表、日程、多时区),day.js 的组合便利性值得引。这又是一次「高频才引库」的实践。

请求层的完整职责清单

前面说请求层封装,把它的完整职责列一下,方便对照检查自己的封装缺了哪块:

职责 说明
baseURL 与超时 全局配置,一处修改全站生效
请求拦截 统一加 token、签名、日志
响应拦截 统一解包 data、处理业务错误码
错误规范化 把网络错误、HTTP 错误、业务错误统一成一种格式
取消机制 组件卸载时取消未完成的请求
重试策略 网络抖动时有限次重试

这些不是每个项目都要全做——小项目有个 baseURL 和超时就够。但「请求层的职责边界」值得知道:它是业务代码和请求库之间的稳定接口,业务不碰 axios 细节,请求库换成 fetch 也不影响业务代码。这个「稳定中间层」的思想在第 5 章架构设计里会反复出现。

七、工具库引入的判断框架

最后给一个统一的判断框架,回答「要不要引这个工具库」:

判断维度 该引 别引
使用频率 项目里高频用 偶尔用一次
手写成本 手写易错、逻辑复杂 手写十行内搞定
维护状态 活跃、有社区 停更、无维护
体积 按需引入可控制 引入即暴涨
替代方案 原生没好的替代 原生已够用

⚠️ 常见坑:看到别人用就引,引完发现只用其中一个函数。引入工具库前先做「一页纸评估」——在哪个模块用、用几个函数、多久用一次。答不上来的,先别引。

依赖管理的工程纪律

工具库的引入还牵扯一个工程问题:依赖膨胀。每个依赖都要被维护、升级、审计,依赖越多,风险面越大。三条纪律值得坚持:

锁版本。package.json 里锁定依赖版本或用锁文件,别让「换个环境版本飘了」成为灵异 bug 的来源。

小依赖警惕。装一个只解决极小问题的库,不如自己写十行。每个依赖都是「未来的维护债」。

定期清理。半年审查一次依赖清单,把没用到的删掉。依赖不是越新越好,是越少越好——够用的依赖数量,才是健康项目的标志。

温故知新

  • axios:HTTP 请求库,拦截器解决「每个请求都要做的事」
  • 请求层封装:baseURL/拦截器集中配置,业务代码不碰 axios 细节
  • Lodash:数据处理工具箱,debounce/throttle 是高频价值
  • classnames:条件类名拼接,小而高频
  • day.js:轻量日期处理,替代停更的 Moment.js
  • React Hook Form:注册式表单管理,模板收敛、减少重渲染
  • 高性价比四类:TanStack Query 请求、Immer 不可变、Zod 校验、React Hook Form 表单
  • 判断框架:频率高、手写易错、维护活跃、体积可控才引
  • 按需引入:别整个库打包,按需引单个函数
  • 依赖纪律:锁版本、警惕小依赖、定期清理

下一章进入实践与最佳实践——工具都齐了,怎么把它们组装成一个能上线的项目。第 5 章先讲大型应用架构设计,再走一遍完整项目实战,前面四章的所有知识在这里合流,变成一套可落地的工程方法。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U