jsx map key !

178 阅读4分钟

一、前言

在 React 开发中,key 是一个非常关键但又常常被误解的属性。它出现在我们使用 .map() 渲染列表的时候,虽然不是 DOM 属性,但它的作用却非常核心:帮助 React 高效地识别列表中每个元素的身份

本文将带你从零开始理解 key 的作用、为什么不能使用 index 作为 key、以及如何正确使用 key 来优化你的 React 应用。


二、什么是 key

在 React 中,当你使用 .map() 方法渲染一个列表时,你通常会看到类似这样的代码:

{items.map((item) => (
  <div key={item.id}>{item.name}</div>
))}

其中的 key 属性是 React 的一个特殊属性,它不是传递给组件的 props,而是 React 内部用来:

  • 识别哪些元素发生了变化、被添加了或被删除了。
  • 在虚拟 DOM 的 diff 算法中用于优化更新效率。

key 是 React 用来识别列表中每个元素身份的“身份证”。


三、如果不写 key,React 会怎么做?

如果你没有手动指定 key,React 默认会使用数组的索引(index)作为 key

例如:

{items.map((item) => (
  <div>{item.name}</div>
))}

React 会自动处理默认索引,等价于:

{items.map((item, index) => (
  <div key={index}>{item.name}</div>
))}

默认索引,虽然看起来没问题,但这种写法在某些情况下会导致性能问题和状态混乱。


四、默认索引'index'会导致什么问题呢?

❌ 问题 1:列表顺序变化时,key 也会变化

假设你有如下数据:

[
  { id: 1, name: '张三' },
  { id: 2, name: '李四' },
  { id: 3, name: '王五' }
]

初始渲染时,每个元素的 key 是:

  • 张三 → key=0
  • 李四 → key=1
  • 王五 → key=2

如果你将顺序改为:

[
  { id: 3, name: '王五' },
  { id: 1, name: '张三' },
  { id: 2, name: '李四' }
]

React 会认为:

  • 王五 → key=0(新组件)
  • 张三 → key=1(新组件)
  • 李四 → key=2(新组件)

于是 React 会认为所有组件都变了,导致整个列表重新渲染!

但实际上,这些组件只是顺序变了而已,内容并没有变。

没看懂?没事,下文有实战演示


❌ 问题 2:状态丢失

如果你的列表项中包含状态(比如输入框、展开/收起状态),使用 index 作为 key 会导致:

  • React 无法正确识别哪个状态对应哪个组件。
  • 结果:输入框内容错乱、折叠面板状态混乱、动画异常等

五、什么时候可以用 默认索引index 作为 key

虽然不推荐,但在以下情况可以使用 index

  • ✅ 列表是静态的(不会排序、增删)。
  • ✅ 列表项不需要维护状态(比如没有输入框、动画、展开收起等)。
  • ✅ 列表只是展示内容,不涉及交互。
const list = ['首页', '关于', '联系'];
{list.map((item) => (
  <div key={index}>{item}</div> // 可以接受
))}

六、正确的 key 应该满足什么条件?

  • 唯一:不能重复。
  • 稳定:即使顺序变化,key 也不应该变。
  • 可预测:比如使用数据库中的 id
{items.map((item) => (
  <div key={item.id}>{item.name}</div> // 推荐写法
))}

七、实战演示:插入新元素到列表(注意:插入到表头和表尾有区别)

代码


function App() {
  const [todos, setTodos] = useState([
    {
      id: 1,
      title: 'todo1',
    }, {
      id: 2,
      title: 'todo2',
    }, {
      id: 3,
      title: 'todo3',
    }
  ])
    
  useEffect(() => {
    setTimeout(() => {
      setTodos(prev=>[...prev,{
        id:4,
        title:'标题-改二'
      }])
      setTodos(prev => [{
        id: 4,
        title: '标题4'
      },
      ...prev
      ])
    }, 5000)
  }, [])
  return (
    <>
      {
        todos.map((todo) => (
          <li key={todo}>
            {todo.title}
          </li>
        ))
      }
    </>
  )
}

插入到表尾:

  setTodos(prev=>[
  ...prev,{
        id:4,
        title:'标题-改二'
      }])

表尾.gif

并不影响,不会全部渲染,只渲染第四个。

插入到表头:

setTodos(prev => [{
  id: 4,
  title: '标题4'
}, ...prev])

表头.gif

这段代码在 5 秒后将一个新的 todo 项目插入到列表的最前面。 然后默认索引颠倒了,全部重新渲染,影响了性能

✅ 使用 todo.id 作为 key 的好处:

  • 即使顺序变化,每个 todo 的 key 保持不变。
  • React 能准确识别哪些元素是新增的,哪些是已有的。
  • 保证最小的重渲染,提升性能。

❌ 如果默认索引 作为 key

  • 可能会导致所有后续元素的 key 都会改变。
  • React 会误认为这些元素都发生了变化,导致不必要的重渲染。
  • 如果列表项中有状态,还会导致状态丢失。

回顾:

key 是 React 用来识别列表中每个元素身份的“身份证”。如果你不写 key,React 会默认使用 index,但这在列表顺序变化或需要维护状态时可能会导致问题。所以,推荐始终使用唯一且稳定的值(如 id)作为 key