MST

星途 面试题库

面试题:React性能优化之useCallback与useMemo

在React函数式组件中,useCallback和useMemo是常用的性能优化钩子。请详细说明它们的作用、区别以及如何正确使用它们来避免不必要的重新渲染。假设你有一个包含复杂计算函数和频繁渲染子组件的场景,如何利用这两个钩子进行优化?
40.2万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

useCallback作用

useCallback 用于返回一个记忆化的回调函数。它会记住回调函数的引用,只有当依赖项发生变化时,才会返回新的回调函数。这样可以避免在每次渲染时都创建新的函数实例,从而防止因函数引用变化导致的子组件不必要的重新渲染。

useMemo作用

useMemo 用于返回一个记忆化的值。它会记住计算的结果,只有当依赖项发生变化时,才会重新计算并返回新的值。这对于复杂计算很有用,可以避免在每次渲染时都进行重复的计算。

两者区别

  1. 返回值类型
    • useCallback 返回记忆化的函数。
    • useMemo 返回记忆化的值。
  2. 用途侧重
    • useCallback 主要用于防止因函数引用变化导致子组件不必要的重新渲染。
    • useMemo 主要用于避免复杂计算的重复执行。

正确使用以避免不必要重新渲染

  1. useCallback使用
    • 当子组件依赖于父组件传递的回调函数,且该回调函数在父组件每次渲染时不应该变化时,使用 useCallback
    • 例如:
    import React, { useCallback } from'react';
    
    const ParentComponent = () => {
      const handleClick = useCallback(() => {
        // 处理点击逻辑
        console.log('Button clicked');
      }, []);
    
      return (
        <ChildComponent onClick={handleClick} />
      );
    };
    
    const ChildComponent = ({ onClick }) => {
      return <button onClick={onClick}>Click me</button>;
    };
    
    • 这里 handleClick 函数只有在依赖项(这里为空数组,表示永远不会变化)发生变化时才会更新,这样 ChildComponent 只有在 handleClick 引用变化时才会重新渲染。
  2. useMemo使用
    • 当有复杂计算,且希望避免每次渲染都重新计算时,使用 useMemo
    • 例如:
    import React, { useMemo } from'react';
    
    const ParentComponent = () => {
      const complexValue = useMemo(() => {
        // 复杂计算逻辑
        let result = 0;
        for (let i = 0; i < 1000000; i++) {
          result += i;
        }
        return result;
      }, []);
    
      return (
        <ChildComponent value={complexValue} />
      );
    };
    
    const ChildComponent = ({ value }) => {
      return <div>{value}</div>;
    };
    
    • 这里 complexValue 只有在依赖项(这里为空数组,表示永远不会变化)发生变化时才会重新计算,避免了每次渲染都进行复杂的计算。

复杂计算函数和频繁渲染子组件场景优化

  1. 对于复杂计算函数
    • 使用 useMemo 来记忆化复杂计算的结果。例如,如果有一个计算两个数组乘积之和的复杂函数:
    import React, { useMemo } from'react';
    
    const complexCalculation = (arr1, arr2) => {
      let sum = 0;
      for (let i = 0; i < arr1.length; i++) {
        sum += arr1[i] * arr2[i];
      }
      return sum;
    };
    
    const ParentComponent = () => {
      const arr1 = [1, 2, 3];
      const arr2 = [4, 5, 6];
      const result = useMemo(() => complexCalculation(arr1, arr2), [arr1, arr2]);
    
      return (
        <ChildComponent result={result} />
      );
    };
    
    const ChildComponent = ({ result }) => {
      return <div>{`The result of complex calculation is: ${result}`}</div>;
    };
    
    • 这里只有 arr1arr2 变化时,才会重新计算 result
  2. 对于频繁渲染子组件
    • 如果子组件依赖于父组件传递的回调函数,使用 useCallback 来确保回调函数引用稳定。例如,子组件是一个列表项,点击列表项触发一个函数:
    import React, { useCallback } from'react';
    
    const ParentComponent = () => {
      const handleListItemClick = useCallback((item) => {
        console.log(`Clicked item: ${item}`);
      }, []);
    
      const items = ['item1', 'item2', 'item3'];
    
      return (
        <ul>
          {items.map((item) => (
            <ChildComponent key={item} item={item} onClick={handleListItemClick} />
          ))}
        </ul>
      );
    };
    
    const ChildComponent = ({ item, onClick }) => {
      return <li onClick={() => onClick(item)}>{item}</li>;
    };
    
    • 这样 ChildComponent 不会因为父组件每次渲染导致 handleListItemClick 引用变化而不必要地重新渲染。