鲁斯前端布鲁斯前端

文章中英模式

布鲁斯前端React面试题目 - 解释 React Key 的用途

深入解析React中key属性的作用、如何正确使用key、常见错误以及对性能的影响。了解为何不应使用索引作为key,key如何协助React识别元素变化,以及React协调算法与key的关系。

影片縮圖

懒得看文章?那就来看视频吧

React Key 的作用是什么?

在React中,key 是一个特殊的props,主要用于帮助React识别列表中的元素。它就像是每个元素的「身份证」,让React能快速找出哪些元素有变化。

Key的主要作用:

  • 1. 提升DOM更新效率
  • 2. 保持组件状态不丢失
  • 3. 避免不必要的重新渲染
  • 4. 确保列表元素的唯一性

Key 与 React 渲染列表的关系

当React渲染列表时,它需要知道哪些元素需要更新、新增或删除。

没有key:React只能按位置比较

原列表:

位置0: 苹果

位置1: 香蕉

位置2: 橘子

新增「芒果」到开头:

位置0: 苹果→芒果 (更新)

位置1: 香蕉→苹果 (更新)

位置2: 橘子→香蕉 (更新)

位置3: 新增橘子

有key:React可按身份比较

原列表:

key="a": 苹果

key="b": 香蕉

key="c": 橘子

新增「芒果」到开头:

key="m": 新增芒果

key="a": 苹果 (不变)

key="b": 香蕉 (不变)

key="c": 橘子 (不变)

使用索引作为key的问题

⚠️ 当列表顺序会有所改变时,索引key会导致不必要的DOM更新:

// 如果列表会更新,不好的做法
{items.map((item, index) => (
  <Item key={index} data={item} />
))}

// 好的做法
{items.map(item => (
  <Item key={item.id} data={item} />
))}

Key 也可被用于强制重新创建组件

组件的实例、状态和DOM与其key密切相关。当key改变时,即使组件类型相同,React也会:

  • 1. 完全卸载旧组件实例(触发useEffect的清理函数)
  • 2. 创建新组件实例(重新触发useEffect
  • 3. 重置组件的所有内部状态
  • 4. 重建DOM节点

这种行为可以被有意利用来强制重置组件状态:

// 使用key重置表单组件
function ResetableForm({ formData, resetKey }) {
  return (
    <Form 
      key={resetKey}  // 当resetKey改变时,整个Form组件会重置
      initialValues={formData}
    >
      {/* 表单内容 */}
    </Form>
  );
}

// 使用案例
function ProfileEditor() {
  const [user, setUser] = useState(null);
  const [resetCounter, setResetCounter] = useState(0);
  
  // 当用户切换时,我们可以通过改变key来重置表单
  return (
    <div>
      <UserSelector onSelect={setUser} />
      <ResetableForm 
        formData={user} 
        resetKey={user?.id || resetCounter} 
      />
      <button onClick={() => setResetCounter(c => c + 1)}>
        重置表单
      </button>
    </div>
  );
}

Key 的最佳实践

选择正确的key

  • 优先使用稳定、唯一的ID: 数据通常有固有的ID,如数据库主键、UUID等
  • 其次考虑内容哈希: 如果没有ID,可基于内容生成哈希值
  • 避免使用随机值: 每次渲染产生的随机key会导致所有组件重新渲染
  • 最后才考虑索引: 只在项目稳定不变、没有重新排序时使用索引

常见错误

// ❌ 错误:使用索引作为可变列表的key
{items.map((item, index) => (
  <TodoItem key={index} item={item} />
))}

// ❌ 错误:使用随机值或时间戳作为key
{items.map(item => (
  <TodoItem key={Math.random()} item={item} />
))}

// ✅ 正确:使用固有ID
{items.map(item => (
  <TodoItem key={item.id} item={item} />
))}

// ✅ 替代方案:结合索引和稳定识别属性
{items.map((item, index) => (
  <TodoItem key={`${item.name}-${index}`} item={item} />
))}

🔥 常见面试题目

(一) React中为什么需要key?不提供key会有什么后果?

解答: React用key来识别列表中的元素,就像每个人的身份证号码。有了key,React才能:

  • 1. 知道哪些元素变了、新增了或删除了
  • 2. 决定能否重用元素而不是重建
  • 3. 正确保留元素状态

没有key的后果:

原始列表

项目A (输入中)
项目B

删除A后

项目B (输入丢失)

没提供key时,React默认用索引,这在列表变动时会导致:

  • 1. 性能下降(过度重建DOM)
  • 2. UI混乱(如错误的选中状态)
  • 3. 状态错乱(如输入框内容跑错位置)

(二) 为什么不建议使用索引作为key?什么情况下可以使用?

解答: 索引只是位置编号,不代表项目本身。当列表变动时,同一索引可能对应不同项目:

// 原列表:索引与项目的对应
[0: 苹果, 1: 香蕉, 2: 橘子]

// 删除苹果后:同样的索引现在对应不同项目!
[0: 香蕉, 1: 橘子]

这会导致:

  • 1. 组件被不必要地重建
  • 2. 表单输入值跑位
  • 3. 状态混乱

只有在这些条件全部满足时才能用索引:

  • 1. 列表永远不会重排序
  • 2. 不会添加/删除项目
  • 3. 项目没有ID
  • 4. 组件没有状态

(三) 如何正确生成key值?有哪些常见策略?

解答: 好的key应该是稳定且唯一的,按优先级:

  1. 数据ID:最理想的选择
    <Item key={user.id} />
  2. 内容哈希:当没有ID时
    <Item key={hash(item.content)} />
  3. 生成ID:初始化时创建
    // 初始化时添加ID
    const data = items.map(i => ({...i, id: uuid()}))