如何在 React 中使用 AbortController 中止 fetch 请求,并避免内存泄漏

55 阅读3分钟

背景

在React中,如何在组件卸载时清除异步操作(如fetch请求)以避免内存泄漏,通常是前端面试中的常见题目。尤其是当你在组件中发起了fetch请求时,如果组件在请求返回之前卸载,那么这个异步请求会试图更新卸载后的组件状态,这就会导致内存泄漏或报错。

如何安全地中止一个fetch请求,确保在组件卸载时清理异步操作,是我们要解决的核心问题。

1. fetch 是否能中止?

fetch本身并不支持直接的取消机制。但我们可以利用AbortController来实现这一功能。AbortController是浏览器提供的一个API,可以用来控制fetch请求的中止。

2. AbortControllersignal 的使用

AbortController是通过传递一个signalfetch来中止请求。signal允许我们在fetch请求中监控并中止请求操作。

代码示例:
import React, { useState, useEffect } from 'react';
import './App.css';

let controller = new AbortController();

function App() {
  useEffect(() => {
    // 发起 fetch 请求,并传入 signal
    fetch('http://localhost:5173/api/banners', {
      signal: controller.signal
    })
      .then(res => res.json())
      .then(data => {
        console.log(data);
      })
      .catch(err => {
        if (err.name === 'AbortError') {
          console.log('请求已中止');
        }
      });

    // 清理函数,组件卸载时取消请求
    return () => {
      controller.abort();
    };
  }, []);

  // 停止请求
  const stop = () => {
    controller.abort();
  };

  return (
    <>
      <button onClick={stop}>暂停</button>
    </>
  );
}

export default App;

在上述代码中,我们在useEffect中发起了fetch请求并使用AbortControllersignal来监听该请求。如果用户点击了“暂停”按钮,我们会通过controller.abort()来中止请求。此外,在组件卸载时,useEffect的返回清理函数会被调用,确保请求在组件卸载时被中止,避免内存泄漏。

3. AbortController 的实现细节

  • AbortController是一个控制器对象,用于通过signal与异步任务(如fetch请求)通信。
  • 在异步操作(如fetch请求)中传递signal,如果调用了abort()方法,signal会发出中止信号,fetch请求会被取消。

4. 使用AbortSignal.timeout 中止请求

AbortSignal还提供了AbortSignal.timeout(ms)方法,用于在指定的时间后自动中止请求。

fetch('http://localhost:5173/api/banners', {
  signal: AbortSignal.timeout(5000)  // 5秒后自动中止请求
})
  .then(res => res.json())
  .then(data => {
    console.log(data);
  })
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('请求超时,已中止');
    }
  });

在上述代码中,如果请求超时5秒钟,AbortSignal.timeout会自动中止请求。

5. 服务端模拟数据

为了模拟一个真实的环境,我们可以使用vite-plugin-mock来模拟后端数据。在开发过程中,前端开发者通常会使用假数据接口,进行前端页面展示。

vite-plugin-mock可以帮助我们在本地模拟API请求,快速构建接口。以下是一个简单的mock配置示例,在 vite.config.js 中进行配置::

import { defineConfig } from 'vite';
import { viteMockServe } from 'vite-plugin-mock';
import react from '@vitejs/plugin-react';

// vite.config.js 配置
export default defineConfig({
  plugins: [
    react(),
    viteMockServe({
      mockPath: 'mock',
      localEnabled: true
    })
  ],
});

这里,我们在mock文件夹中配置模拟数据接口,模拟一个获取广告数据的API:

export default [
  {
    url: '/api/banners',
    method: 'get',
    timeout: 3000,
    response: (req, res) => {
      const banners = [
        {
          id: 1,
          pic: 'https://images4.c-ctrip.com/target/1mc3y12000bhw655k6679_D_280_280_R5.jpg',
          title: '美豪雅致酒店(杭州桐庐银泰城店)1晚',
        },
        {
          id: 2,
          pic: 'https://images4.c-ctrip.com/target/1mc6m12000balvt6q6461_D_280_280_R5.jpg',
          title: '宁波象山海景皇冠假日酒店2晚',
        },
        {
          id: 3,
          pic: 'https://images4.c-ctrip.com/target/1mc0512000bljvsbd5D53_D_280_280_R5.jpg',
          title: '千岛湖开元度假村2晚',
        },
      ];
      return {
        code: 0,
        msg: 'success',
        data: banners,
      };
    },
  },
];

6. 总结

  • fetch请求可以通过AbortControllersignal进行中止,这对于防止内存泄漏非常重要。
  • 当组件卸载或用户中止请求时,我们可以调用controller.abort()来取消请求,避免不必要的资源浪费。
  • 通过vite-plugin-mock,我们可以轻松地模拟API请求,进行前端开发。

这些都是面试中常见的异步请求管理技巧,掌握这些内容可以帮助你更好地处理React中的异步操作,提高代码质量和性能。