如何在 React 项目中优化列表渲染性能,避免不必要的重绘?

大白话如何在 React 项目中优化列表渲染性能,避免不必要的重绘?

在 React 项目里,要是列表数据量很大,每次数据变化都重新渲染列表,会严重影响性能。

1. 使用 key 属性

key 属性能帮助 React 识别哪些元素发生了变化,这样在更新列表时,React 就只更新那些真正改变的元素,而不是重新渲染整个列表。

import React from 'react';

// 假设这是我们的数据列表
const data = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' },
];

const ListComponent = () => {
  return (
    <ul>
      {/* 遍历数据列表,为每个列表项添加唯一的 key */}
      {data.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
};

export default ListComponent;

在这个例子中,key 属性被设置为 item.id,因为 id 是唯一的,这样 React 就能准确知道哪些元素改变了。

2. 使用 React.memo 进行组件缓存

React.memo 是一个高阶组件,它能对组件进行浅比较,如果组件的 props 没有变化,就不会重新渲染。

import React from 'react';

// 定义一个简单的列表项组件
const ListItem = React.memo(({ item }) => {
  // 这里返回列表项的 JSX
  return <li>{item.name}</li>;
});

// 假设这是我们的数据列表
const data = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' },
];

const ListComponent = () => {
  return (
    <ul>
      {/* 遍历数据列表,使用 ListItem 组件 */}
      {data.map(item => (
        <ListItem key={item.id} item={item} />
      ))}
    </ul>
  );
};

export default ListComponent;

在这个例子中,ListItem 组件被 React.memo 包裹,当 item props 没有变化时,这个组件就不会重新渲染。

3. 使用 shouldComponentUpdate(类组件)

在类组件中,可以使用 shouldComponentUpdate 生命周期方法来控制组件是否需要重新渲染。

import React from 'react';

// 定义一个类组件作为列表项
class ListItem extends React.Component {
  // shouldComponentUpdate 方法,用于控制组件是否重新渲染
  shouldComponentUpdate(nextProps) {
    // 比较当前 props 和下一个 props 的 item 是否相同
    return this.props.item.name !== nextProps.item.name;
  }

  render() {
    // 这里返回列表项的 JSX
    return <li>{this.props.item.name}</li>;
  }
}

// 假设这是我们的数据列表
const data = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' },
];

const ListComponent = () => {
  return (
    <ul>
      {/* 遍历数据列表,使用 ListItem 组件 */}
      {data.map(item => (
        <ListItem key={item.id} item={item} />
      ))}
    </ul>
  );
};

export default ListComponent;

在这个例子中,shouldComponentUpdate 方法会比较当前的 item props 和下一个 item props 是否相同,如果相同就不重新渲染组件。

4. 虚拟列表

当列表数据量非常大时,可以使用虚拟列表只渲染用户可见区域的列表项,从而提高性能。下面是一个简单的虚拟列表示例:

import React, { useState } from 'react';

// 假设这是我们的大数据列表
const bigData = Array.from({ length: 1000 }, (_, i) => ({ id: i, name: `Item ${i}` }));

const VirtualList = () => {
  // 定义每个列表项的高度
  const itemHeight = 30;
  // 定义容器的高度
  const containerHeight = 300;
  // 定义显示的列表项数量
  const visibleItemCount = Math.floor(containerHeight / itemHeight);
  // 使用 useState 来管理滚动偏移量
  const [scrollOffset, setScrollOffset] = useState(0);

  // 计算开始索引
  const startIndex = Math.floor(scrollOffset / itemHeight);
  // 计算结束索引
  const endIndex = startIndex + visibleItemCount;
  // 截取可见区域的数据
  const visibleData = bigData.slice(startIndex, endIndex);

  return (
    <div
      style={{ height: containerHeight, overflowY: 'auto' }}
      onScroll={(e) => setScrollOffset(e.target.scrollTop)}
    >
      {/* 为不可见区域预留高度 */}
      <div style={{ height: startIndex * itemHeight }} />
      {/* 渲染可见区域的列表项 */}
      {visibleData.map(item => (
        <div key={item.id} style={{ height: itemHeight }}>
          {item.name}
        </div>
      ))}
      {/* 为不可见区域预留高度 */}
      <div style={{ height: (bigData.length - endIndex) * itemHeight }} />
    </div>
  );
};

export default VirtualList;

在这个例子中,VirtualList 组件只渲染用户可见区域的列表项,当用户滚动时,动态更新可见区域的数据,这样可以显著提高性能。

通过以上这些方法,就能在 React 项目中优化列表渲染性能,避免不必要的重绘。

除了使用 key 属性,还有哪些方法可以优化 React 列表渲染性能?

在 React 里,除了使用 key 属性,还有不少方法可以优化列表渲染性能,下面详细介绍:

1. 使用 React.memo 进行组件缓存

React.memo 是一个高阶组件,它可以对函数组件进行浅比较,若组件的 props 没有变化,就不会重新渲染该组件。

import React from 'react';

// 使用 React.memo 包裹组件,对传入的 props 进行浅比较
const ListItem = React.memo(({ item }) => {
    // 渲染列表项
    return <li>{item.name}</li>;
});

const data = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Charlie' }
];

const ListComponent = () => {
    return (
        <ul>
            {/* 遍历数据数组,渲染列表项 */}
            {data.map(item => (
                <ListItem key={item.id} item={item} />
            ))}
        </ul>
    );
};

export default ListComponent;

在上述代码中,ListItem 组件被 React.memo 包裹。当 item 属性没有改变时,ListItem 组件不会重新渲染,以此减少不必要的渲染。

2. 类组件使用 shouldComponentUpdate

在类组件里,shouldComponentUpdate 生命周期方法能够控制组件是否重新渲染。你可以在这个方法里比较 propsstate,依据比较结果决定是否重新渲染。

import React from 'react';

class ListItem extends React.Component {
    // shouldComponentUpdate 方法,用于决定组件是否重新渲染
    shouldComponentUpdate(nextProps) {
        // 比较当前和下一个 props 的 item 属性
        return this.props.item.name!== nextProps.item.name;
    }

    render() {
        // 渲染列表项
        return <li>{this.props.item.name}</li>;
    }
}

const data = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Charlie' }
];

const ListComponent = () => {
    return (
        <ul>
            {/* 遍历数据数组,渲染列表项 */}
            {data.map(item => (
                <ListItem key={item.id} item={item} />
            ))}
        </ul>
    );
};

export default ListComponent;

在这个例子中,shouldComponentUpdate 方法会比较当前 props 和下一个 propsitem.name 是否相同,若相同则不重新渲染组件。

3. 使用 PureComponent

React.PureComponentReact.Component 类似,但它自动实现了 shouldComponentUpdate 方法,会对 propsstate 进行浅比较。

import React from 'react';

// 继承 React.PureComponent
class ListItem extends React.PureComponent {
    render() {
        // 渲染列表项
        return <li>{this.props.item.name}</li>;
    }
}

const data = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Charlie' }
];

const ListComponent = () => {
    return (
        <ul>
            {/* 遍历数据数组,渲染列表项 */}
            {data.map(item => (
                <ListItem key={item.id} item={item} />
            ))}
        </ul>
    );
};

export default ListComponent;

在上述代码中,ListItem 组件继承自 React.PureComponent,当 propsstate 发生浅层次的变化时,组件才会重新渲染。

4. 虚拟列表

当列表数据量非常大时,可采用虚拟列表技术,只渲染用户可见区域的列表项。

import React, { useState } from 'react';

// 模拟大量数据
const bigData = Array.from({ length: 1000 }, (_, i) => ({ id: i, name: `Item ${i}` }));

const VirtualList = () => {
    // 每个列表项的高度
    const itemHeight = 30;
    // 容器的高度
    const containerHeight = 300;
    // 可见列表项的数量
    const visibleItemCount = Math.floor(containerHeight / itemHeight);
    // 滚动偏移量状态
    const [scrollOffset, setScrollOffset] = useState(0);

    // 计算开始索引
    const startIndex = Math.floor(scrollOffset / itemHeight);
    // 计算结束索引
    const endIndex = startIndex + visibleItemCount;
    // 获取可见区域的数据
    const visibleData = bigData.slice(startIndex, endIndex);

    return (
        <div
            style={{ height: containerHeight, overflowY: 'auto' }}
            onScroll={(e) => setScrollOffset(e.target.scrollTop)}
        >
            {/* 为不可见区域预留高度 */}
            <div style={{ height: startIndex * itemHeight }} />
            {/* 渲染可见区域的列表项 */}
            {visibleData.map(item => (
                <div key={item.id} style={{ height: itemHeight }}>
                    {item.name}
                </div>
            ))}
            {/* 为不可见区域预留高度 */}
            <div style={{ height: (bigData.length - endIndex) * itemHeight }} />
        </div>
    );
};

export default VirtualList;    

在这个例子中,VirtualList 组件仅渲染用户可见区域的列表项,当用户滚动时,动态更新可见区域的数据,避免了渲染大量不可见的列表项,从而提升了性能。

你可能感兴趣的:(大白话前端八股,react.js,前端,前端框架)