首页 / React 19 入门教程 / 列表渲染

React 19 入门教程

列表渲染

本教程共 50 篇 · 第 11 篇 · 更新于 2026-07-29 · 约 7 分钟阅读

React列表渲染mapfilterkey数组

11. 列表渲染

本节目标:掌握用 map() 渲染列表、用 filter() 筛选数据,理解 key 的重要性以及为什么不能用数组索引。

用 map() 渲染列表

map() 把一个数组转换成另一个数组。在 React里,就是把数据数组转成 JSX 数组:

const fruits = ['苹果', '香蕉', '橙子'];

const listItems = fruits.map(fruit => <li>{fruit}</li>);

// 结果:[<li>苹果</li>, <li>香蕉</li>, <li>橙子</li>]

然后在 JSX 里嵌入:

function FruitList() {
  const fruits = ['苹果', '香蕉', '橙子'];
  const listItems = fruits.map(fruit => <li>{fruit}</li>);

  return <ul>{listItems}</ul>;
}
Note

实际开发中,map() 通常直接写在 JSX 里:

<ul>
  {fruits.map(fruit => <li>{fruit}</li>)}
</ul>

key:列表项的身份证

渲染列表时,React 会报警告:Each child in a list should have a unique "key" prop

这是因为 React 需要唯一标识每个列表项。当列表增删改时,React 靠 key 判断哪些项变了、哪些没变。

const fruits = [
  { id: 1, name: '苹果' },
  { id: 2, name: '香蕉' },
  { id: 3, name: '橙子' },
];

const listItems = fruits.map(fruit => (
  <li key={fruit.id}>{fruit.name}</li>
));

key 的要求:

  • 在兄弟节点之间唯一——不要求全局唯一
  • 不能变——不要用 Math.random() 或时间戳
  • 来自数据——比如数据库 ID

为什么不能用 index 做 key

你可能会这样写:

// 不推荐
{items.map((item, index) => (
  <li key={index}>{item.name}</li>
))}

React 不报错,但这是反模式

问题在于:index 代表的是位置,不是数据本身

假设列表是 ['A', 'B', 'C'],key 是 0, 1, 2。现在删除第一项:

  • 删除前:A(0), B(1), C(2)
  • 删除后:B(0), C(1)

React 看到 key 0 还在,key 1 还在,只是值变了。它会更新这两个 DOM 节点,而不是移动它们。如果列表项有状态(比如输入框里的文字),就会错乱。

Warning

只有当列表永远不会增删排序时,才能用 index 做 key。比如固定的导航菜单。其他情况一律用数据 ID。

用 filter() 筛选数据

filter() 返回满足条件的元素组成的新数组。配合 map() 可以筛选后渲染:

const people = [
  { id: 1, name: '张三', role: '前端' },
  { id: 2, name: '李四', role: '后端' },
  { id: 3, name: '王五', role: '前端' },
];

function FrontendTeam() {
  const frontends = people.filter(p => p.role === '前端');

  return (
    <ul>
      {frontends.map(p => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
}

也可以链式调用:

const listItems = people
  .filter(p => p.role === '前端')
  .map(p => <li key={p.id}>{p.name}</li>);

key 放在哪里

key 要放在 map() 返回的最外层元素上:

// 正确
items.map(item => (
  <li key={item.id}>{item.name}</li>
));

// 也正确(Fragment 需要完整写法)
items.map(item => (
  <Fragment key={item.id}>
    <dt>{item.name}</dt>
    <dd>{item.desc}</dd>
  </Fragment>
));
Note

<>...</> 简写形式不能带 key。如果你需要给 Fragment 加 key,必须用 <Fragment key={...}>

列表项是组件时

如果列表项提取成了组件,key 放在组件标签上,不要放在组件内部的根元素上:

// 正确
{items.map(item => (
  <TodoItem key={item.id} item={item} />
))}

// 错误:key 放在了组件内部的 <li> 上
function TodoItem({ item }) {
  return <li key={item.id}>{item.name}</li>;  // 不行
}

因为 React 需要 key 在数组上下文中就近可用。组件内部的 key 对 React 来说”看不见”。

实战示例:动态列表

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: '学 React', done: false },
    { id: 2, text: '写代码', done: false },
    { id: 3, text: '吃午饭', done: true },
  ]);

  return (
    <ul>
      {todos
        .filter(todo => !todo.done)  // 只显示未完成的
        .map(todo => (
          <li key={todo.id}>
            {todo.text}
          </li>
        ))}
    </ul>
  );
}

常见错误

错误 1:忘记加 key

// 警告
{items.map(item => <li>{item.name}</li>)}

// 修复
{items.map(item => <li key={item.id}>{item.name}</li>)}

错误 2:key 不唯一

// 错误:多个项可能有相同的名字
{items.map(item => <li key={item.name}>{item.name}</li>)}

// 修复:用唯一 ID
{items.map(item => <li key={item.id}>{item.name}</li>)}

错误 3:在 map 里用 if

// 不推荐:map 里用 if 逻辑混乱
{items.map(item => {
  if (item.visible) {
    return <li key={item.id}>{item.name}</li>;
  }
})}

// 推荐:先 filter 再 map
{items
  .filter(item => item.visible)
  .map(item => <li key={item.id}>{item.name}</li>)
}

本节小结

  • map() 把数据数组转成 JSX 数组
  • key 是列表项的唯一标识,来自数据 ID
  • 不要用 index 做 key(除非列表永远不变)
  • filter() + map() 实现筛选渲染
  • key 放在 map 返回的最外层元素上

下一节学样式方案。