MST
星途 面试题库

面试题:React自定义Hooks中的依赖数组问题及优化

在自定义Hooks中使用useEffect时,依赖数组的设置很关键。例如,有一个自定义Hook用于获取某个商品的详情数据,该Hook接收商品ID作为参数。请解释依赖数组应该如何设置才能避免不必要的重复请求,同时处理好商品ID变化时重新获取数据的情况。如果在实际开发中遇到由于依赖数组设置不当导致性能问题,你会如何排查和优化?
39.1万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

依赖数组设置

  1. 仅依赖商品ID
    • 由于该自定义Hook的功能是根据商品ID获取商品详情数据,那么依赖数组应只包含商品ID。例如:
    import { useEffect, useState } from'react';
    
    const useProductDetails = (productId) => {
      const [product, setProduct] = useState(null);
      useEffect(() => {
        const fetchProduct = async () => {
          // 这里模拟异步请求获取商品详情
          const response = await fetch(`/api/products/${productId}`);
          const data = await response.json();
          setProduct(data);
        };
        fetchProduct();
      }, [productId]);
      return product;
    };
    
    • 这样设置,只有当productId发生变化时,useEffect内的回调函数才会重新执行,避免了不必要的重复请求。如果依赖数组为空[],那么只会在组件挂载时执行一次,商品ID变化时不会重新获取数据;如果依赖数组包含了其他不必要的变量,可能会导致在这些变量变化时也触发不必要的请求。

排查和优化因依赖数组设置不当导致的性能问题

  1. 排查
    • 控制台日志法:在useEffect回调函数内部添加console.log语句,输出依赖数组中的变量值以及useEffect的执行次数。例如:
    useEffect(() => {
      console.log('useEffect执行,productId:', productId);
      const fetchProduct = async () => {
        const response = await fetch(`/api/products/${productId}`);
        const data = await response.json();
        setProduct(data);
      };
      fetchProduct();
    }, [productId]);
    
    • 通过观察控制台输出,可以判断useEffect是否按照预期的频率执行,以及依赖数组中的变量变化是否导致了意外的执行。
    • React DevTools:使用React DevTools的性能面板,记录组件的渲染情况。可以看到组件的重新渲染次数以及每个渲染阶段的耗时。如果useEffect执行过于频繁,会导致组件重新渲染次数增加,通过性能面板可以直观地发现这种异常情况,并定位到对应的组件和useEffect
  2. 优化
    • 正确调整依赖数组:根据排查结果,确保依赖数组只包含真正影响useEffect回调函数逻辑的变量。如果发现某些不必要的变量导致了useEffect频繁执行,将其从依赖数组中移除。
    • 使用Memoization:对于传递给自定义Hook的复杂数据结构(如对象或数组),如果它们在组件更新过程中没有实质性变化,但却导致了useEffect不必要的执行,可以使用React.memo(对于函数组件)或shouldComponentUpdate(对于类组件)来避免不必要的重新渲染,进而减少useEffect的不必要执行。例如,如果商品ID是通过一个对象传递的,而对象中的其他属性不影响商品详情的获取,可以对包含商品ID的对象进行React.memo处理:
    const ProductIdContainer = React.memo(({ productId }) => {
      const product = useProductDetails(productId);
      return (
        // 组件渲染逻辑
      );
    });
    
    • 节流或防抖:如果productId变化非常频繁,例如在一个搜索输入框中实时输入商品ID进行搜索,可以使用节流(throttle)或防抖(debounce)技术。节流限制useEffect在一定时间间隔内只能执行一次,防抖则是在productId停止变化后的一定时间再执行useEffect。可以通过自定义节流或防抖函数,或者使用lodash等库中的throttledebounce函数来实现。例如,使用lodashdebounce
    import { useEffect, useState } from'react';
    import debounce from 'lodash/debounce';
    
    const useProductDetails = (productId) => {
      const [product, setProduct] = useState(null);
      const fetchProductDebounced = debounce(async () => {
        const response = await fetch(`/api/products/${productId}`);
        const data = await response.json();
        setProduct(data);
      }, 300);
      useEffect(() => {
        fetchProductDebounced();
        return () => {
          fetchProductDebounced.cancel();
        };
      }, [productId]);
      return product;
    };
    
    • 这样在productId变化时,不会立即发起请求,而是等待300毫秒(这里的时间可以根据实际需求调整),如果在这300毫秒内productId又发生了变化,之前的请求会被取消,避免了过多不必要的请求。