MST

星途 面试题库

面试题:React性能优化之高级难度:虚拟DOM与重渲染优化

React利用虚拟DOM来提高性能。请详细阐述虚拟DOM的工作原理,以及在大型应用中,如何通过减少不必要的虚拟DOM diff计算和重渲染来优化性能?比如,说说 React.memo、useMemo 和 useCallback 在这方面的作用及使用场景,并且举例说明如何结合它们来优化一个频繁重渲染的组件。
22.9万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

虚拟 DOM 工作原理

  1. 创建虚拟 DOM:当 React 组件状态或 props 发生变化时,React 会根据最新的数据创建一棵新的虚拟 DOM 树。虚拟 DOM 本质上是一个轻量级的 JavaScript 对象,它描述了真实 DOM 的结构和属性。例如,对于一个简单的 <div> 元素,虚拟 DOM 可能如下:
{
  type: 'div',
  props: {
    className: 'container',
    children: [
      {
        type: 'p',
        props: {
          children: 'Hello, React!'
        }
      }
    ]
  }
}
  1. 对比虚拟 DOM 树:React 会将新创建的虚拟 DOM 树与之前的虚拟 DOM 树进行对比,这个过程称为 diff 算法。diff 算法会找出两棵树之间的差异,即哪些节点被添加、删除或更新。
  2. 更新真实 DOM:根据 diff 算法得出的差异,React 会批量对真实 DOM 进行最小化的更新,只修改那些真正需要改变的部分,从而避免了不必要的 DOM 操作,提高了性能。

减少不必要的虚拟 DOM diff 计算和重渲染的优化方法

  1. React.memo
    • 作用:React.memo 是一个高阶组件,用于对函数式组件进行浅比较优化。它会在组件接收到新的 props 时,对新旧 props 进行浅比较,如果 props 没有变化,React 会跳过该组件的渲染,从而减少不必要的重渲染。
    • 使用场景:适用于 props 变化频率较低,且组件渲染开销较大的情况。例如展示静态数据的组件,只要 props 不变,就不需要重新渲染。
    • 示例
import React from'react';

const MyComponent = React.memo((props) => {
  return <div>{props.value}</div>;
});

export default MyComponent;
  1. useMemo
    • 作用:useMemo 是一个 React Hook,用于缓存一个值,只有当依赖项发生变化时才会重新计算。它可以用来缓存函数的返回值,避免在每次渲染时都重新计算,从而减少不必要的计算开销。
    • 使用场景:当计算某个值的开销较大,且依赖项变化不频繁时使用。比如计算列表的总和等复杂操作。
    • 示例
import React, { useMemo } from'react';

const MyComponent = ({ list }) => {
  const sum = useMemo(() => {
    return list.reduce((acc, num) => acc + num, 0);
  }, [list]);

  return <div>The sum is: {sum}</div>;
};

export default MyComponent;
  1. useCallback
    • 作用:useCallback 也是一个 React Hook,它用于缓存一个函数,只有当依赖项发生变化时才会重新创建函数。这在将函数作为 props 传递给子组件时非常有用,可以避免子组件因为父组件函数引用变化而不必要的重渲染。
    • 使用场景:常用于将事件处理函数传递给子组件,且子组件依赖函数引用的稳定性。例如传递给 onClick 等事件处理函数。
    • 示例
import React, { useCallback } from'react';

const ChildComponent = ({ handleClick }) => {
  return <button onClick={handleClick}>Click me</button>;
};

const ParentComponent = () => {
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);

  return <ChildComponent handleClick={handleClick} />;
};

export default ParentComponent;

结合优化频繁重渲染的组件示例

假设我们有一个父组件 ParentComponent,它包含一个子组件 ChildComponent,并且父组件状态频繁变化,导致子组件不必要的重渲染。

import React, { useState } from'react';

const ChildComponent = React.memo((props) => {
  console.log('ChildComponent rendered');
  return <div>{props.value}</div>;
});

const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const expensiveCalculation = useMemo(() => {
    // 模拟一个开销较大的计算
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += i;
    }
    return result;
  }, []);

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <button onClick={handleClick}>Increment Count</button>
      <ChildComponent value={expensiveCalculation} />
    </div>
  );
};

export default ParentComponent;

在这个例子中,ChildComponent 使用 React.memo 进行了优化,只有当 props.value 变化时才会重新渲染。expensiveCalculation 使用 useMemo 进行缓存,避免每次渲染都重新计算。handleClick 使用 useCallback 进行缓存,确保传递给 ChildComponent 的函数引用稳定,避免因为函数引用变化导致 ChildComponent 不必要的重渲染。这样,通过结合这三个优化手段,有效地减少了不必要的重渲染和计算,提高了组件性能。