MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React 列表渲染基础入门

2024-04-104.8k 阅读

React 列表渲染基础入门

在前端开发中,列表渲染是一个非常常见的需求。React 提供了简洁且强大的方式来处理列表渲染。接下来,我们将逐步深入了解 React 中列表渲染的基础知识。

1. 什么是列表渲染

列表渲染,简单来说,就是将一组数据以列表的形式展示在页面上。例如,展示一个用户列表,每个用户的信息(如姓名、年龄等)作为列表的一项。在 React 中,我们通常会使用数组来存储这组数据,然后通过特定的语法将数组中的每一项渲染为页面上的一个元素。

2. 基本的列表渲染

假设我们有一个包含一些数字的数组,我们想要在页面上把这些数字以列表项的形式展示出来。

首先,创建一个 React 组件,如下:

import React from 'react';

function NumberList() {
  const numbers = [1, 2, 3, 4, 5];
  const listItems = numbers.map((number) =>
    <li>{number}</li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

export default NumberList;

在上述代码中:

  • 我们定义了一个 numbers 数组,包含数字 15
  • 使用数组的 map 方法遍历 numbers 数组。map 方法会为数组中的每一个元素调用传入的回调函数,并返回一个新的数组。这里的回调函数接收数组中的每一个 number,并返回一个 <li> 元素,其内容为对应的数字。
  • 最后,将生成的包含 <li> 元素的新数组 listItems 放在 <ul> 元素中进行渲染。这样,页面上就会显示一个包含数字 15 的无序列表。

3. 列表项的 key 属性

细心的开发者可能会注意到,当在 React 中渲染列表时,如果不指定 key 属性,React 会发出警告。那么 key 到底是什么,为什么它如此重要呢?

key 是 React 用来识别列表中每一项的“身份标识”。当列表中的项发生变化(如添加、删除、重新排序)时,React 可以通过 key 更高效地确定哪些项发生了变化,从而只更新必要的 DOM 元素,提高渲染性能。

继续以上面的 NumberList 组件为例,为 <li> 元素添加 key 属性:

import React from 'react';

function NumberList() {
  const numbers = [1, 2, 3, 4, 5];
  const listItems = numbers.map((number) =>
    <li key={number}>{number}</li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

export default NumberList;

在这个例子中,我们使用数组中的数字本身作为 key。通常,在实际应用中,key 应该是列表项中一个稳定且唯一的标识符。例如,如果我们渲染一个用户列表,每个用户有一个唯一的 id,那么就应该使用 user.id 作为 key

注意事项

  • key 必须在其兄弟元素之间保持唯一,但不必在整个应用程序中唯一。
  • 不要使用数组的索引作为 key,除非列表项是静态的,不会发生重新排序、添加或删除操作。因为索引是不稳定的,当列表发生变化时,基于索引的 key 可能会导致 React 做出错误的 DOM 更新,降低性能并产生难以调试的问题。

4. 渲染复杂列表

实际应用中,列表项往往不仅仅是简单的文本,可能包含多个元素和复杂的样式。例如,我们要渲染一个任务列表,每个任务包含任务名称和完成状态。

首先,定义任务数据结构:

const tasks = [
  { id: 1, name: '学习 React', completed: false },
  { id: 2, name: '完成项目文档', completed: true },
  { id: 3, name: '测试代码', completed: false }
];

然后,创建一个 React 组件来渲染这个任务列表:

import React from 'react';

function TaskList() {
  const tasks = [
    { id: 1, name: '学习 React', completed: false },
    { id: 2, name: '完成项目文档', completed: true },
    { id: 3, name: '测试代码', completed: false }
  ];
  const taskItems = tasks.map((task) =>
    <li key={task.id}>
      <input type="checkbox" checked={task.completed} />
      {task.name}
    </li>
  );
  return (
    <ul>{taskItems}</ul>
  );
}

export default TaskList;

在上述代码中:

  • 我们定义了一个 tasks 数组,每个元素是一个包含 idnamecompleted 属性的对象。
  • 使用 map 方法遍历 tasks 数组,为每个任务生成一个 <li> 元素。每个 <li> 元素包含一个复选框和任务名称,复选框的 checked 属性根据任务的 completed 状态来设置。
  • 通过 key={task.id} 为每个列表项设置唯一的 key

5. 动态更新列表

列表通常不是静态的,可能需要动态添加、删除或更新列表项。接下来我们看如何在 React 中实现这些操作。

添加列表项: 假设我们有一个输入框和一个按钮,用户可以在输入框中输入新任务名称,点击按钮将新任务添加到任务列表中。

import React, { useState } from'react';

function TaskList() {
  const [tasks, setTasks] = useState([
    { id: 1, name: '学习 React', completed: false },
    { id: 2, name: '完成项目文档', completed: true },
    { id: 3, name: '测试代码', completed: false }
  ]);
  const [newTaskName, setNewTaskName] = useState('');

  const handleInputChange = (e) => {
    setNewTaskName(e.target.value);
  };

  const handleAddTask = () => {
    if (newTaskName.trim()!== '') {
      const newTask = {
        id: tasks.length + 1,
        name: newTaskName,
        completed: false
      };
      setTasks([...tasks, newTask]);
      setNewTaskName('');
    }
  };

  const taskItems = tasks.map((task) =>
    <li key={task.id}>
      <input type="checkbox" checked={task.completed} />
      {task.name}
    </li>
  );

  return (
    <div>
      <input
        type="text"
        placeholder="添加新任务"
        value={newTaskName}
        onChange={handleInputChange}
      />
      <button onClick={handleAddTask}>添加任务</button>
      <ul>{taskItems}</ul>
    </div>
  );
}

export default TaskList;

在上述代码中:

  • 使用 useState 钩子来管理任务列表 tasks 和新任务名称 newTaskName 的状态。
  • handleInputChange 函数用于更新 newTaskName 的状态,当输入框的值发生变化时调用。
  • handleAddTask 函数在点击“添加任务”按钮时被调用。首先检查输入框的值是否为空,不为空则创建一个新的任务对象,其 id 为当前任务列表长度加 1,名称为输入框的值,完成状态为 false。然后使用展开运算符 ... 将新任务添加到现有任务列表中,并清空输入框。

删除列表项: 在任务列表中添加删除功能,允许用户删除特定的任务。

import React, { useState } from'react';

function TaskList() {
  const [tasks, setTasks] = useState([
    { id: 1, name: '学习 React', completed: false },
    { id: 2, name: '完成项目文档', completed: true },
    { id: 3, name: '测试代码', completed: false }
  ]);

  const handleDeleteTask = (taskId) => {
    const newTasks = tasks.filter((task) => task.id!== taskId);
    setTasks(newTasks);
  };

  const taskItems = tasks.map((task) =>
    <li key={task.id}>
      <input type="checkbox" checked={task.completed} />
      {task.name}
      <button onClick={() => handleDeleteTask(task.id)}>删除</button>
    </li>
  );

  return (
    <ul>{taskItems}</ul>
  );
}

export default TaskList;

在这段代码中:

  • handleDeleteTask 函数接收要删除任务的 id。使用数组的 filter 方法创建一个新的任务列表,该列表不包含要删除的任务。然后通过 setTasks 更新任务列表状态。
  • 在每个任务的 <li> 元素中添加了一个“删除”按钮,点击按钮时调用 handleDeleteTask 并传入当前任务的 id

更新列表项: 例如,我们要实现用户点击复选框时更新任务的完成状态。

import React, { useState } from'react';

function TaskList() {
  const [tasks, setTasks] = useState([
    { id: 1, name: '学习 React', completed: false },
    { id: 2, name: '完成项目文档', completed: true },
    { id: 3, name: '测试代码', completed: false }
  ]);

  const handleTaskCompletionChange = (taskId) => {
    setTasks(tasks.map((task) =>
      task.id === taskId? { ...task, completed:!task.completed } : task
    ));
  };

  const taskItems = tasks.map((task) =>
    <li key={task.id}>
      <input
        type="checkbox"
        checked={task.completed}
        onChange={() => handleTaskCompletionChange(task.id)}
      />
      {task.name}
    </li>
  );

  return (
    <ul>{taskItems}</ul>
  );
}

export default TaskList;

这里:

  • handleTaskCompletionChange 函数接收任务的 id。使用 map 方法遍历任务列表,对于点击复选框的任务,通过展开运算符 ... 创建一个新对象,将其 completed 状态取反,其他任务保持不变。最后更新任务列表状态。
  • 在复选框的 onChange 事件中调用 handleTaskCompletionChange 并传入当前任务的 id

6. 嵌套列表渲染

有时候,我们需要渲染嵌套的列表结构,比如一个树形菜单。假设我们有一个部门列表,每个部门又包含多个员工。

首先,定义数据结构:

const departments = [
  {
    id: 1,
    name: '研发部',
    employees: [
      { id: 11, name: '张三' },
      { id: 12, name: '李四' }
    ]
  },
  {
    id: 2,
    name: '市场部',
    employees: [
      { id: 21, name: '王五' },
      { id: 22, name: '赵六' }
    ]
  }
];

然后,创建 React 组件来渲染这个嵌套列表:

import React from'react';

function DepartmentList() {
  const departments = [
    {
      id: 1,
      name: '研发部',
      employees: [
        { id: 11, name: '张三' },
        { id: 12, name: '李四' }
      ]
    },
    {
      id: 2,
      name: '市场部',
      employees: [
        { id: 21, name: '王五' },
        { id: 22, name: '赵六' }
      ]
    }
  ];

  const departmentItems = departments.map((department) =>
    <li key={department.id}>
      {department.name}
      <ul>
        {department.employees.map((employee) =>
          <li key={employee.id}>{employee.name}</li>
        )}
      </ul>
    </li>
  );

  return (
    <ul>{departmentItems}</ul>
  );
}

export default DepartmentList;

在上述代码中:

  • 外层 map 方法遍历 departments 数组,为每个部门生成一个 <li> 元素,包含部门名称。
  • 内层 map 方法在每个部门的 <li> 元素内部,遍历该部门的 employees 数组,为每个员工生成一个 <li> 元素,包含员工名称。这样就实现了嵌套列表的渲染。

7. 列表渲染与性能优化

虽然 React 已经在列表渲染方面做了很多优化,但在处理大量数据时,仍有一些方法可以进一步提升性能。

虚拟列表: 当列表项数量非常大时,一次性渲染所有项可能会导致性能问题。虚拟列表技术只渲染当前可见区域的列表项,当用户滚动时,动态加载新的可见项。在 React 中,有一些第三方库(如 react - virtualizedreact - window)可以帮助实现虚拟列表。

react - virtualized 为例,安装并使用它来渲染一个大数据量的列表:

npm install react - virtualized
import React from'react';
import { List } from'react - virtualized';

const data = Array.from({ length: 10000 }, (_, i) => i + 1);

const rowRenderer = ({ index, key, style }) => (
  <div key={key} style={style}>
    Item {data[index]}
  </div>
);

function BigList() {
  return (
    <List
      height={400}
      rowCount={data.length}
      rowHeight={50}
      rowRenderer={rowRenderer}
      width={300}
    />
  );
}

export default BigList;

在上述代码中:

  • 我们使用 react - virtualizedList 组件。heightwidth 属性定义了列表的可视区域大小,rowCount 表示列表项的总数,rowHeight 表示每个列表项的高度。
  • rowRenderer 函数定义了如何渲染每一行,它接收 index(当前行的索引)、key(用于唯一标识当前行)和 style(React - Virtualized 自动计算的用于定位当前行的样式)。

shouldComponentUpdate 优化: 对于列表中的组件,可以通过 shouldComponentUpdate 方法来控制组件何时重新渲染。如果列表项中的数据没有发生变化,就可以阻止组件不必要的重新渲染。

import React from'react';

class TaskItem extends React.Component {
  shouldComponentUpdate(nextProps) {
    return this.props.task!== nextProps.task;
  }

  render() {
    const { task } = this.props;
    return (
      <li>
        <input type="checkbox" checked={task.completed} />
        {task.name}
      </li>
    );
  }
}

function TaskList() {
  const tasks = [
    { id: 1, name: '学习 React', completed: false },
    { id: 2, name: '完成项目文档', completed: true },
    { id: 3, name: '测试代码', completed: false }
  ];

  const taskItems = tasks.map((task) =>
    <TaskItem key={task.id} task={task} />
  );

  return (
    <ul>{taskItems}</ul>
  );
}

export default TaskList;

在上述代码中:

  • TaskItem 组件通过 shouldComponentUpdate 方法,只有当 task 属性发生变化时才重新渲染,避免了不必要的更新。

通过这些方法,可以在处理列表渲染时,特别是处理大量数据时,显著提升应用程序的性能。

通过以上内容,我们全面地了解了 React 列表渲染的基础知识,包括基本渲染、key 属性的使用、动态更新列表、嵌套列表渲染以及性能优化等方面。掌握这些知识将有助于开发者在 React 应用中高效地处理各种列表相关的需求。