3. Axios在React Native中的集成与封装
在React Native中,网络请求是绝大多数应用的核心能力。虽然框架内置了Fetch API,但在真实项目中,开发者往往更倾向于使用Axios。原因并不复杂:Axios提供了更友好的JSON自动解析、请求/响应拦截器、超时控制、请求取消、统一的错误处理以及更简洁的配置方式。本节将围绕Axios的安装、实例创建、拦截器、请求取消以及与Fetch的对比选型,给出一套可直接落地的封装方案。
安装与基础配置
Axios是纯JavaScript库,不依赖原生模块,因此在React Native中安装非常简单。在项目根目录执行:
npm install axios
# 或
yarn add axios
安装完成后无需额外链接或原生配置,直接导入即可使用。一个最基础的GET请求如下:
import axios from 'axios';
const res = await axios.get('https://api.example.com/users', {
params: { page: 1, size: 20 },
timeout: 8000,
});
console.log(res.data);
与Fetch不同,Axios会自动将响应体解析为JSON对象,无需手动调用res.json();同时,当HTTP状态码不在2xx范围内时,Axios会直接抛出错误,而不是像Fetch那样需要手动检查response.ok。这两点差异在业务代码中能显著减少样板逻辑。
创建实例:统一基地址与超时
在真实项目中,不建议到处直接使用全局的axios对象。更好的做法是创建一个带默认配置的实例,把基地址、超时、公共请求头集中管理。例如:
// src/api/client.js
import axios from 'axios';
const client = axios.create({
baseURL: 'https://api.example.com/v1',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
'X-Client': 'react-native',
},
});
export default client;
这样后续所有请求只需写相对路径,例如client.get('/users'),既减少了重复代码,也方便在切换环境(开发、测试、生产)时只改一处配置。实例的另一个好处是:拦截器、取消令牌等都可以绑定在实例上,不会污染全局Axios。
拦截器:请求与响应的统一处理
拦截器是Axios最实用的能力之一。请求拦截器可以在请求发出前统一注入Token、设备信息或语言标识;响应拦截器可以在数据返回后统一处理业务错误、刷新Token或做日志上报。下面是一个典型封装:
client.interceptors.request.use(
(config) => {
const token = getToken(); // 从AsyncStorage或内存读取
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
client.interceptors.response.use(
(response) => {
const { code, data, message } = response.data;
if (code === 0) return data; // 业务成功,直接返回data
return Promise.reject(new Error(message || '业务错误'));
},
(error) => {
if (error.response?.status === 401) {
// 触发登出或刷新Token
}
return Promise.reject(error);
}
);
需要注意:响应拦截器返回data后,调用方拿到的就不再是完整的Axios响应对象,而是业务数据本身。这种“剥壳”设计能让业务代码更干净,但要在团队内约定清楚,避免有人误以为还能访问response.status。
请求取消:避免组件卸载后的无效更新
在React Native中,用户快速切换页面时,未完成的请求可能在组件卸载后才返回,导致“setState on unmounted component”警告或内存泄漏。Axios从0.22版本开始推荐使用AbortController来取消请求:
const controller = new AbortController();
client.get('/users', { signal: controller.signal })
.then((data) => setUsers(data))
.catch((err) => {
if (axios.isCancel(err)) {
console.log('请求已取消');
}
});
// 组件卸载或重新请求时
controller.abort();
在React函数组件中,通常配合useEffect的清理函数使用:在effect内创建controller,在return中调用controller.abort()。这样既能取消网络请求,也能避免过期的响应覆盖新状态。
与Fetch的对比及选型建议
Fetch是浏览器和React Native内置的标准API,体积为零,适合极简场景。但它的短板也很明显:不支持超时、不会自动拒绝非2xx状态、没有拦截器、取消请求需要手动组合AbortController、上传进度难以监听。Axios则在这些方面提供了开箱即用的能力。下面的对比图从请求发起、响应处理、错误处理、取消机制和扩展能力五个维度,直观呈现两者的差异与选型依据。
选型建议可以归纳为三条:第一,如果项目只有零星几个简单请求,且团队希望零依赖,Fetch足够;第二,如果项目需要统一鉴权、统一错误码处理、超时控制和请求取消,Axios的拦截器与实例机制能节省大量重复代码;第三,如果涉及文件上传进度、请求重试或复杂并发控制,Axios的生态和API设计更成熟。对于本课程后续的实战项目,我们统一采用Axios实例加拦截器的封装方式,以便把鉴权、错误提示和加载状态集中管理。
client.js、request.js(业务请求函数)和errorHandler.js分层放置。拦截器只做通用逻辑,具体业务错误提示交给调用方或统一的错误处理模块,避免拦截器承担过多职责导致难以调试。
一个可直接使用的封装结构
综合以上内容,下面给出一个精简但完整的目录与代码结构,可直接作为项目起点:
src/api/
├── client.js // Axios 实例 + 拦截器
├── user.js // 用户相关接口
└── index.js // 统一导出
// client.js
import axios from 'axios';
import { getToken } from '../utils/storage';
const client = axios.create({
baseURL: 'https://api.example.com/v1',
timeout: 10000,
});
client.interceptors.request.use((config) => {
const token = getToken();
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
client.interceptors.response.use(
(res) => res.data,
(err) => Promise.reject(err)
);
export default client;
// user.js
import client from './client';
export const fetchProfile = (signal) =>
client.get('/user/profile', { signal });
export const updateProfile = (payload) =>
client.put('/user/profile', payload);
调用时只需const profile = await fetchProfile(controller.signal),拿到的就是业务数据。若请求被取消,会进入catch并可通过axios.isCancel识别。这套结构把配置、鉴权、错误传递和取消能力都收敛到了少数几个文件中,后续新增接口只需在user.js这类模块中追加函数即可。
res.data后,如果某个接口需要读取响应头(例如分页总数放在X-Total-Count中),就不能再通过返回值获取。此时可以在拦截器中把headers挂到data上,或对该接口单独使用原始client并跳过剥壳逻辑。封装要留出这种例外通道。
总结来说,Axios在React Native中的集成成本很低,但封装质量决定了后续维护效率。创建实例解决配置复用,拦截器解决横切逻辑,AbortController解决取消与内存安全,而选型则应根据项目复杂度在Fetch与Axios之间做出权衡。掌握这四点,就能在网络层建立起清晰、可扩展的基础设施。