在这里插入图片描述

列表渲染与 Key

一、什么是列表渲染?

在 React 中,列表渲染是指使用 JavaScript 的数组方法来生成一组元素。最常见的是使用 map() 方法遍历数组,返回 JSX 元素。

核心概念

  • 使用 map()filter()reduce() 等数组方法处理数据
  • 每个列表项都需要一个唯一的 key 属性
  • React 使用 key 来识别哪些元素发生了变化

二、基础列表渲染

2.1 使用 map() 渲染列表

function NumberList({ numbers }) {
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  return <ul>{listItems}</ul>;
}

// 使用
<NumberList numbers={[1, 2, 3, 4, 5]} />

2.2 直接在 JSX 中使用 map

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

2.3 使用 filter() 过滤数据

function CompletedTodos({ todos }) {
  const completedTodos = todos.filter(todo => todo.completed);
  return (
    <ul>
      {completedTodos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

三、Key 的深入理解

3.1 什么是 Key?

Key 是 React 用来识别列表项的唯一标识符。它帮助 React 确定哪些元素被添加、更改或删除。

3.2 Key 的作用

没有 key 时:
[1,2,3] → [1,2,3,4]  React 会重新渲染整个列表

有 key 时:
[1,2,3] → [1,2,3,4]  React 只添加新的第 4 项

3.3 选择合适的 Key

// ✅ 好的 Key:使用数据中的唯一 ID
<li key={todo.id}>{todo.text}</li>

// ✅ 可以:使用索引(仅在列表静态且不会重新排序时)
<li key={index}>{todo.text}</li>

// ❌ 坏的 Key:使用随机数或 Math.random()
<li key={Math.random()}>{todo.text}</li>

3.4 Key 的最佳实践

场景 推荐 Key 说明
数据库数据 数据库 ID 最稳定
本地生成数据 uuid / nanoid 保证唯一性
静态列表 索引(谨慎) 列表不变时可接受
动态列表 唯一标识符 必须稳定且唯一

四、列表渲染示例

4.1 渲染对象数组

const users = [
  { id: 1, name: '张三', age: 25 },
  { id: 2, name: '李四', age: 30 },
  { id: 3, name: '王五', age: 28 }
];

function UserList() {
  return (
    <table>
      <thead>
        <tr><th>姓名</th><th>年龄</th></tr>
      </thead>
      <tbody>
        {users.map(user => (
          <tr key={user.id}>
            <td>{user.name}</td>
            <td>{user.age}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

4.2 带删除功能的列表

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: '学习 React' },
    { id: 2, text: '写代码' },
    { id: 3, text: '休息' }
  ]);

  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
          <button onClick={() => deleteTodo(todo.id)}>删除</button>
        </li>
      ))}
    </ul>
  );
}

4.3 可编辑列表

function EditableList() {
  const [items, setItems] = useState(['项目 1', '项目 2', '项目 3']);
  const [newItem, setNewItem] = useState('');

  const addItem = () => {
    if (newItem.trim()) {
      setItems([...items, newItem]);
      setNewItem('');
    }
  };

  return (
    <div>
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
      <input
        value={newItem}
        onChange={(e) => setNewItem(e.target.value)}
        placeholder="添加新项目"
      />
      <button onClick={addItem}>添加</button>
    </div>
  );
}

4.4 分组列表

const groupedData = {
  '已完成': [
    { id: 1, text: '任务 1' },
    { id: 2, text: '任务 2' }
  ],
  '未完成': [
    { id: 3, text: '任务 3' },
    { id: 4, text: '任务 4' }
  ]
};

function GroupedList() {
  return (
    <div>
      {Object.entries(groupedData).map(([group, items]) => (
        <div key={group}>
          <h3>{group}</h3>
          <ul>
            {items.map(item => (
              <li key={item.id}>{item.text}</li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
}

五、性能优化

5.1 使用 React.memo 优化列表项

const TodoItem = React.memo(({ todo, onToggle }) => {
  console.log('渲染:', todo.id);
  return (
    <li>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      {todo.text}
    </li>
  );
});

function TodoList({ todos, onToggle }) {
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={onToggle}
        />
      ))}
    </ul>
  );
}

5.2 虚拟列表(大数据量)

对于包含大量数据的列表(>1000项),建议使用虚拟列表库:

npm install react-window
import { FixedSizeList as List } from 'react-window';

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

function VirtualList({ items }) {
  return (
    <List
      height={400}
      itemCount={items.length}
      itemSize={35}
      itemData={items}
      width={300}
    >
      {Row}
    </List>
  );
}

六、常见问题与陷阱

6.1 使用索引作为 Key 的问题

// ❌ 当列表顺序变化时会有问题
{todos.map((todo, index) => (
  <TodoItem key={index} todo={todo} />
))}

// 问题:
// 1. 删除第一项时,所有后续项的 key 都改变了
// 2. React 会重新渲染所有项,而不是只删除一项
// 3. 可能导致组件状态错乱

6.2 Key 不唯一的问题

// ❌ Key 必须唯一
{items.map(item => (
  <div key={item.type}>  {/* 如果有相同 type 会报错 */}
    {item.value}
  </div>
))}

// ✅ 使用组合 key
{items.map(item => (
  <div key={`${item.type}-${item.id}`}>
    {item.value}
  </div>
))}

6.3 Key 不会传递给组件

// ❌ 不能通过 props 获取 key
function MyComponent(props) {
  console.log(props.key); // undefined
  return <div>{props.value}</div>;
}

// ✅ 显式传递
{items.map(item => (
  <MyComponent key={item.id} id={item.id} value={item.value} />
))}

七、高级技巧

7.1 使用 key 重置组件状态

function Profile({ userId }) {
  // 当 userId 改变时,key 改变,组件会重新创建
  return (
    <UserProfile
      key={userId}  // 强制重新挂载
      userId={userId}
    />
  );
}

7.2 列表动画

import { TransitionGroup, CSSTransition } from 'react-transition-group';

function AnimatedList({ items }) {
  return (
    <TransitionGroup component="ul">
      {items.map(item => (
        <CSSTransition
          key={item.id}
          timeout={500}
          classNames="item"
        >
          <li>{item.text}</li>
        </CSSTransition>
      ))}
    </TransitionGroup>
  );
}

八、练习题

基础题

  1. 渲染一个水果列表,每个水果旁边显示一个"购买"按钮
  2. 实现一个待办事项列表,可以添加新事项

进阶题

  1. 实现一个可拖拽排序的列表
  2. 实现一个带有搜索过滤功能的列表

参考答案

// 1. 水果列表
function FruitList() {
  const fruits = ['苹果', '香蕉', '橙子', '葡萄'];
  return (
    <ul>
      {fruits.map((fruit, index) => (
        <li key={index}>
          {fruit} <button>购买</button>
        </li>
      ))}
    </ul>
  );
}

// 2. 带搜索的列表
function SearchableList() {
  const [searchTerm, setSearchTerm] = useState('');
  const items = ['React', 'Vue', 'Angular', 'Svelte'];
  
  const filteredItems = items.filter(item =>
    item.toLowerCase().includes(searchTerm.toLowerCase())
  );
  
  return (
    <div>
      <input
        type="text"
        placeholder="搜索..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      <ul>
        {filteredItems.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

九、小结

要点 说明
map() 最常用的列表渲染方法
key 必须唯一且稳定,帮助 React 识别变化
索引作为 key 仅在列表静态且不重新排序时使用
性能优化 使用 React.memo 和虚拟列表
常见陷阱 索引 key、key 不唯一、key 不传递

核心要点

  • 始终为列表项提供稳定的 key
  • 优先使用数据中的唯一 ID
  • 避免使用随机数作为 key
  • 大数据量考虑虚拟化

更多推荐