MST

星途 面试题库

面试题:React 列表组件封装时如何处理动态更新与性能优化

在 React 列表组件封装过程中,列表数据会频繁动态更新,如添加、删除、修改某一项。请描述你会采取哪些策略进行性能优化,比如如何避免不必要的重渲染,并结合 React.memo、useCallback、useMemo 等 API 给出具体实现思路。
31.6万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

避免不必要重渲染的策略

  1. 使用 React.memo

    • 原理:React.memo 是一个高阶组件,它通过浅比较 props 来决定组件是否需要重新渲染。如果 props 没有变化,组件将复用之前的渲染结果,从而避免不必要的重渲染。
    • 应用于列表项组件:假设我们有一个 ListItem 组件来展示列表中的每一项数据。
    const ListItem = React.memo(({ item }) => {
      return <div>{item.text}</div>;
    });
    
    • 这样,只有当 ListItemitem prop 发生变化时,该组件才会重新渲染。如果 item 是一个对象,浅比较只会检查对象的引用是否改变,所以要确保传递给 ListItemitem 引用变化是有意义的,否则可能出现数据更新但组件未重渲染的情况。
  2. useCallback 和 useMemo

    • useCallback
      • 原理useCallback 用于缓存函数,返回一个 memoized 回调函数。它接收两个参数,第一个是要缓存的函数,第二个是依赖数组。只有当依赖数组中的值发生变化时,才会返回新的函数。
      • 应用场景:在列表组件中,如果有传递给子组件(如 ListItem)的回调函数,使用 useCallback 可以避免这些回调函数在每次渲染时都重新创建,从而防止因函数引用变化导致子组件不必要的重渲染。例如,假设 ListItem 有一个点击删除按钮的功能:
      import React, { useCallback } from'react';
      
      const List = ({ list, onDelete }) => {
        const handleDelete = useCallback((index) => {
          onDelete(index);
        }, [onDelete]);
      
        return (
          <ul>
            {list.map((item, index) => (
              <ListItem key={index} item={item} onDelete={handleDelete} />
            ))}
          </ul>
        );
      };
      
    • useMemo
      • 原理useMemo 用于缓存计算结果,它接收两个参数,第一个是一个函数,该函数返回要缓存的值,第二个是依赖数组。只有当依赖数组中的值发生变化时,才会重新计算并返回新的值。
      • 应用场景:如果列表组件中有一些需要计算的属性,并且计算成本较高,可以使用 useMemo 来缓存计算结果,避免每次渲染都重新计算。例如,计算列表中所有项的总和:
      import React, { useMemo } from'react';
      
      const List = ({ list }) => {
        const total = useMemo(() => {
          return list.reduce((acc, item) => acc + item.value, 0);
        }, [list]);
      
        return (
          <div>
            <p>Total: {total}</p>
            <ul>
              {list.map((item, index) => (
                <ListItem key={index} item={item} />
              ))}
            </ul>
          </div>
        );
      };
      
  3. 虚拟列表

    • 原理:当列表数据量非常大时,一次性渲染所有列表项会导致性能问题。虚拟列表只渲染当前视口可见的列表项,当用户滚动时,动态地加载和卸载可见区域外的列表项。
    • 实现方式:可以使用第三方库如 react - virtualizedreact - window 来实现虚拟列表。例如,使用 react - virtualizedList 组件:
    import React from'react';
    import { List } from'react - virtualized';
    
    const rowRenderer = ({ index, key, style, data }) => {
      const item = data[index];
      return (
        <div key={key} style={style}>
          {item.text}
        </div>
      );
    };
    
    const MyList = ({ list }) => {
      return (
        <List
          height={400}
          rowCount={list.length}
          rowHeight={50}
          rowRenderer={rowRenderer.bind(null, { data: list })}
          width={300}
        />
      );
    };
    
    • 这样,无论列表数据量多大,始终只渲染视口内的列表项,大大提高了性能。
  4. 减少状态提升

    • 原理:如果列表项的状态只影响该项本身,尽量将状态放在该项组件内部,而不是提升到父列表组件。这样可以避免因父组件状态变化导致整个列表重新渲染。
    • 示例:如果 ListItem 有一个展开/收起的功能,该状态应该在 ListItem 组件内部管理,而不是在父 List 组件管理。
    const ListItem = React.memo(({ item }) => {
      const [isExpanded, setIsExpanded] = React.useState(false);
      return (
        <div>
          <button onClick={() => setIsExpanded(!isExpanded)}>
            {isExpanded? 'Collapse' : 'Expand'}
          </button>
          {isExpanded && <div>{item.extraInfo}</div>}
        </div>
      );
    });