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则在这些方面提供了开箱即用的能力。下面的对比图从请求发起、响应处理、错误处理、取消机制和扩展能力五个维度,直观呈现两者的差异与选型依据。

Axios 与 Fetch 能力对比与选型 五个维度评估:请求配置 · 响应解析 · 错误处理 · 取消机制 · 扩展能力 对比维度 Axios Fetch 请求配置 baseURL / timeout / headers create() 实例统一配置 超时、基地址开箱即用 需手动封装 Request 函数 超时需 AbortController 模拟 响应解析 JSON 自动转换 自动解析 JSON res.data 直接可用 需手动 res.json() 多一步异步解析 错误处理 非 2xx 状态码 自动 reject catch 统一捕获 需检查 response.ok 否则 404 也走 then 取消机制 AbortController signal 参数直接支持 isCancel 判断取消 同样支持 signal 但需自行封装取消逻辑 扩展能力 拦截器 / 进度 拦截器 + 上传进度 生态成熟,插件丰富 无拦截器 进度需 ReadableStream

选型建议可以归纳为三条:第一,如果项目只有零星几个简单请求,且团队希望零依赖,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之间做出权衡。掌握这四点,就能在网络层建立起清晰、可扩展的基础设施。