MST

星途 面试题库

面试题:复杂场景下React组件卸载时的资源清理策略及性能优化

在一个大型的React应用中,某个组件管理了多种类型的资源,包括但不限于网络请求(fetch或axios等)、DOM元素的操作(如创建canvas元素并绘制图形)、动画资源(使用CSS动画或requestAnimationFrame等)。在组件卸载时,需要同时清理这些资源,并且要考虑不同资源清理的先后顺序对性能的影响,以及如何避免在清理过程中引发新的问题(如资源未加载完成就进行清理导致的异常)。请详细描述你的资源清理策略,并结合代码示例分析如何在这种复杂场景下实现高效、可靠的资源清理。
19.6万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

资源清理策略

  1. 网络请求
    • 使用 AbortController 来取消未完成的网络请求。在组件挂载时创建 AbortController 实例,在组件卸载时调用 abort 方法。这样可以避免在组件卸载后,网络请求继续执行,从而防止潜在的内存泄漏和数据更新异常。
    • 对于 fetch,可以将 signal 属性传递给 fetch 请求。对于 axios,可以使用 CancelToken
  2. DOM 元素操作
    • 在组件卸载时,移除组件所创建的 DOM 元素。可以使用 useRef 来引用 DOM 元素,然后在 useEffect 的清理函数中调用 remove 方法来移除该元素。
    • 如果涉及到 canvas 绘制,在移除 canvas 元素之前,停止相关的绘制操作(例如停止 requestAnimationFrame 动画循环,如果有的话)。
  3. 动画资源
    • 对于 CSS 动画,可以通过在组件卸载时移除相关的 CSS 类来停止动画。
    • 对于 requestAnimationFrame,在组件卸载时取消动画循环。可以使用一个变量来存储 requestAnimationFrame 返回的 ID,然后在清理函数中调用 cancelAnimationFrame

代码示例

import React, { useEffect, useRef } from'react';
import axios from 'axios';

const ComplexComponent = () => {
  const canvasRef = useRef(null);
  const animationFrameIdRef = useRef(null);
  const abortControllerRef = useRef(new AbortController());

  useEffect(() => {
    // 创建 canvas 元素
    const canvas = document.createElement('canvas');
    canvasRef.current = canvas;
    document.body.appendChild(canvas);

    // 网络请求
    const fetchData = async () => {
      try {
        const response = await axios.get('/api/data', {
          cancelToken: new axios.CancelToken((c) => {
            abortControllerRef.current.signal.addEventListener('abort', c);
          })
        });
        console.log('Data fetched:', response.data);
      } catch (error) {
        if (!axios.isCancel(error)) {
          console.error('Error fetching data:', error);
        }
      }
    };
    fetchData();

    // CSS 动画,添加类名触发动画
    document.body.classList.add('my - css - animation');

    // requestAnimationFrame 动画
    const animate = () => {
      // 这里是动画逻辑
      animationFrameIdRef.current = requestAnimationFrame(animate);
    };
    animationFrameIdRef.current = requestAnimationFrame(animate);

    return () => {
      // 清理网络请求
      abortControllerRef.current.abort();

      // 清理 DOM 元素
      if (canvasRef.current) {
        canvasRef.current.remove();
      }

      // 清理 CSS 动画
      document.body.classList.remove('my - css - animation');

      // 清理 requestAnimationFrame 动画
      if (animationFrameIdRef.current) {
        cancelAnimationFrame(animationFrameIdRef.current);
      }
    };
  }, []);

  return <div>Complex Component</div>;
};

export default ComplexComponent;

性能影响及避免新问题

  1. 性能影响
    • 网络请求的取消应优先处理,因为未完成的网络请求可能会占用网络资源,并且如果继续执行可能会导致数据更新到已经卸载的组件,从而引发性能问题和潜在的错误。
    • DOM 元素的移除和动画的停止可以并行处理。移除 DOM 元素可以释放内存,而停止动画可以避免不必要的计算。
  2. 避免新问题
    • 通过使用 AbortControllerCancelToken 确保网络请求在组件卸载时被正确取消,避免资源未加载完成就进行清理导致的异常。
    • 对于 DOM 元素和动画,在清理之前先检查相关的引用是否存在,避免在不存在的元素上执行操作,从而防止新的异常。例如,在移除 canvas 元素之前检查 canvasRef.current 是否存在,在取消 requestAnimationFrame 之前检查 animationFrameIdRef.current 是否存在。