在React项目里,数据请求层通常封装成独立模块,Axios依靠完整的拦截器体系和取消能力成为许多团队的默认选择。但Axios在浏览器端底层使用XMLHttpRequest,每次请求都要经过它自己的一套适配逻辑,体积和心智负担并不低。Ky则是直接构建在Fetch API之上的现代封装,它用很小的体积补齐了重试、超时、JSON解析和请求钩子,能够和React的函数式风格更好地配合。从Axios迁移到Ky并不意味着重写全部业务,而是把请求实例、拦截逻辑和取消策略换成更贴近浏览器原生的方案。本文会从底层差异、基础用法、拦截器迁移和高级特性四个角度展开,提供可以直接运行的React改造示例。

需要注意的是,Ky只支持现代浏览器和较新的Node.js环境。如果你的项目还需要兼容IE11或老版本WebView,迁移前必须先引入Fetch的polyfill,并确认部分高级能力是否满足需求。下面先看两者最核心的机制差异。
架构与体积:Axios重在哪,Ky轻在哪
严格来说,Axios在浏览器端并不直接使用Fetch,而是基于XMLHttpRequest做了一层Promise化包装,在Node环境则切换到http模块。这种设计让Axios可以在不同运行时提供一致的API,但也意味着它需要在内部维护请求配置、响应转换、拦截器队列和取消令牌等复杂结构。浏览器端打包后,Axios的核心体积大约在14KB左右,而Ky由于直接复用原生Fetch,只需要处理配置合并、重试、超时和钩子,压缩后通常只有3KB到4KB。对于在意首屏加载的React单页应用,这个差距会随着代码分割和网络环境变慢而被放大。
另一个关键区别是Promise的行为。原生的fetch只有在网络错误或请求无法完成时才会reject,对于HTTP 404、500等状态码,fetch会正常resolve,需要开发者手动判断response.ok。Axios则默认把非2xx状态码都当作错误并reject。Ky选择站在Fetch这一边,但也提供了httpErrors选项来自动将非成功状态码转成HTTPError对象。理解这个差异很重要,否则迁移后会出现请求进入成功回调但取不到数据的情况。
基础用法迁移:从axios.create到ky.create
在React项目中,请求模块一般会导出一个配置好的客户端实例。Axios使用axios.create,通过baseURL和timeout设置基础路径与超时;Ky使用ky.create,对应的是prefixUrl和timeout。两者在语义上略有不同:baseURL允许路径中包含变量,而prefixUrl更像是一个纯前缀,例如Ky会自动处理末尾斜杠。代码示例如下。
// Axios 实例
import axios from 'axios';
const axiosClient = axios.create({
baseURL: 'https://api.ipipp.com',
timeout: 10000,
});
迁移到Ky后,实例创建方式如下。注意prefixUrl必须填写完整域名,不能省略协议,而且Ky会自动处理末尾斜杠。如果需要携带查询参数,Axios使用params配置,Ky则使用searchParams,并支持普通对象、URLSearchParams和字符串多种形式。
// Ky 实例
import ky from 'ky';
const kyClient = ky.create({
prefixUrl: 'https://api.ipipp.com',
timeout: 10000,
retry: 2,
});
发送GET请求时,两边的差异会进一步体现。Axios会自动解析JSON,返回值直接就是数据对象;Ky遵循Fetch规范,get方法返回的是Response对象,需要手动调用.json()或.text()。这种设计让Ky在流式响应和二进制数据处理上更灵活,但对于简单的JSON接口来说多了一步。错误处理上,Axios通过error.response.status读取状态码,Ky的HTTPError同样带有response属性,但错误类型名称从AxiosError变成了HTTPError。
// Axios 发起 GET 请求
const users = await axiosClient.get('/users', {
params: { page: 1 }
});
// Ky 发起 GET 请求
const users = await kyClient.get('users', {
searchParams: { page: 1 }
}).json();
在React组件中使用时,建议把请求逻辑放在useEffect中,并维护loading、data和error三个状态。Axios的迁移工作量主要集中在参数写法和响应解析上,请求触发方式没有本质变化。
拦截器迁移:用hooks替代interceptors
Axios的拦截器采用use方法注册,请求拦截器可以修改config,响应拦截器可以处理数据或错误。这种命令式风格很直观,但拦截器是全局注册的,容易在大型项目中形成难以追踪的链式修改。Ky没有名为拦截器的概念,它提供hooks选项,分为beforeRequest、beforeRetry、afterResponse和beforeError等。每个钩子都是一个函数或函数数组,可以直接修改Request和Response对象,或者返回新的对象来替换。
// Axios 请求拦截器添加 Authorization
axiosClient.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
return config;
});
// Ky beforeRequest 实现同样功能
const kyClient = ky.create({
hooks: {
beforeRequest: [
request => {
request.headers.set('Authorization', `Bearer ${localStorage.getItem('token')}`);
}
]
}
});
响应拦截的迁移需要格外小心。Axios可以在响应拦截器里直接返回response.data,也可以在错误拦截器里统一跳转登录页。Ky的afterResponse在每次响应后执行,但如果需要在钩子里读取响应体,无论是克隆还是解析,都必须返回一个新的Response对象,否则后续的.json()调用会因body已经被消费而失败。下面这个例子只根据状态码做跳转,不读取body,所以直接返回response即可。
// Ky afterResponse 处理 401
const kyClient = ky.create({
hooks: {
afterResponse: [
(request, options, response) => {
if (response.status === 401) {
window.location.href = '/login';
}
return response;
}
]
}
});
如果确实需要在afterResponse中读取数据做监控,可以这样写:先调用response.clone()或读取text后重建Response。推荐使用clone方式,因为不会破坏原始响应体,但会占用额外内存;如果响应非常大,更好的做法是把读取逻辑放到业务代码里。另外,beforeRetry钩子可以拿到重试次数和错误,适合在重试前刷新token或记录日志。与Axios相比,Ky把钩子收口到options中,每个实例的职责更清晰,也更方便单元测试。
取消、重试与上传进度:迁移前必须知道的差异
请求取消是React组件卸载时避免内存泄漏的关键。Axios早期使用CancelToken,后来也支持通过signal传入AbortController。Ky则完全依赖原生AbortController,使用方式与fetch一致。在useEffect中创建一个controller,把signal传给请求,然后在清理函数中调用abort即可。如果请求已经被取消,Ky会抛出一个名称包含AbortError的错误,可以在catch中判断。
// React 组件卸载时取消请求
useEffect(() => {
const controller = new AbortController();
kyClient.get('users', { signal: controller.signal })
.json()
.then(data => setUsers(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求已被取消');
}
});
return () => controller.abort();
}, []);
重试策略是两者容易被忽略的地方。Axios默认不会自动重试,需要借助axios-retry等插件;Ky则默认对GET、PUT、HEAD、DELETE和OPTIONS请求重试两次,触发条件包括网络错误、408、429和5xx状态码。如果后端接口不是幂等的,这个默认行为可能导致重复提交。可以通过retry选项调整次数,甚至设置retry.delay来计算重试间隔。超时方面,Axios的timeout默认值为0,表示不限制;Ky默认timeout为10000毫秒,如果接口响应超过10秒就会直接失败,迁移后要检查长耗时请求。
上传进度是本次迁移最大的限制之一。因为Fetch API本身没有提供上传进度事件,Ky也没有实现onUploadProgress。Axios在浏览器环境中可以通过XMLHttpRequest的upload事件拿到上传百分比,常用于文件上传进度条。如果你的业务强依赖上传进度展示,直接迁移到Ky会导致进度条失效。目前可行的替代方案包括:对上传请求仍然保留Axios客户端,或者使用XMLHttpRequest单独封装上传模块,也可以期待Fetch规范未来加入上传流。此外,Ky不支持Axios的取消令牌API,但AbortController已经足够覆盖绝大多数场景。
综合来看,如果React项目新启动、不要求上传进度、运行在较新的浏览器,用Ky替换Axios可以明显减少依赖体积,让请求层更贴近标准API。存量项目中如果已经深度依赖Axios的拦截器、上传进度或生态插件,建议保留Axios用于上传,其余请求逐步迁移到Ky,两者可以共存,只是需要分别维护两份实例配置。