面试官让我解释React的key,我反手掏出一沓身份证…

51 阅读3分钟

🎙️ 面试暴击现场

面试官:(转笔)看你项目里列表渲染都写了 key,说说这玩意儿是干啥的?不写会咋样?

:(OS:来送!)Key 就像给组件发的身份证,React 用它来认人。不写的话...(压低声音)React 可能会把你的组件认成失足青年,直接送去火葬场(销毁重建)!

面试官:(拍桌)说人话!

:好的!不写 key 会导致列表更新时错乱的血案现场

  • 输入框内容跟着错位
  • 组件状态混乱
  • 性能疯狂掉帧

🆔 一、Key 的前世今生

1. 史前时代:React 的认亲难题

// React 0.13时代的痛
<ul>
  {items.map(item => <li>{item.text}</li>)} 
</ul>
  • 问题:增删改操作后,React像脸盲症患者,分不清谁是谁
  • 解法:2015年 React 0.14 正式引入 key 机制,开启精准匹配时代

2. 现代文明:Diff算法的灵魂伴侣

// React的虚拟DOM对比算法简写
function reconcileChildren(oldChildren, newChildren) {
  const matchedChildren = [];
  
  newChildren.forEach(newChild => {
    const oldChild = oldChildren.find(child => 
      child.key === newChild.key // 核心匹配逻辑
    );
    if (oldChild) reuseOrUpdate(oldChild, newChild);
    else mountNew(newChild);
  });
}

面试官:(推眼镜)那 key 在不同版本有啥变化?

:React 18新增了 StrictMode 双重渲染检测,乱写 key 更容易翻车!比如:

// 作死写法:用随机数当key
<Item key={Math.random()} /> 

控制台会疯狂警告,仿佛在说:"你不对劲!"


🔑 二、Key的选妃指南

1. 安全牌:数据库 ID

// 正宫娘娘:稳定唯一
{todos.map(todo => (
  <TodoItem key={todo.id} {...todo} />
))}

适用场景:有后端返回的唯一 ID 时,闭眼用!

2. 替身文学:索引 index

// 危险操作:仅限静态列表
{fixedList.map((item, index) => (
  <StaticItem key={index} {...item} />
))}

死亡案例

// 动态删除中间项时...
原列表:A(index0)-B(index1)-C(index2)
删除B后:A(index0)-C(index1) 
// React以为还是A-C,但C其实已经是原来的index2!

3. 黑科技:复合键

// 当没有唯一ID时
{posts.map(post => (
  <Post 
    key={`${post.authorId}-${post.createTime}`} 
  />
))}

秘籍:组合多个稳定字段,降低碰撞概率


💥 三、Key的七宗罪与救赎

罪状1:随机数导致核爆级渲染

// 灾难现场
<ChatMessage 
  key={Date.now()} // 每次渲染都变
  message={text} 
/>

症状:每次输入都导致全部消息重新渲染
救赎:用消息 ID 或哈希值

罪状2:索引导致状态穿越

// 惊悚片场
{todos.map((todo, index) => (
  <Todo 
    key={index}
    // 删除中间项后后面项的index全变
    onDelete={() => deleteTodo(index)} 
  />
))}

灵异现象:点击删除第2项,结果删了第3项
解法:用ID闭包或传递原始数据

罪状3:服务端渲染Hydration错乱

// 水合失败警告!
// 服务端生成的key和客户端不同
{items.map(item => (
  <Item key={isServer ? item.ssrId : item.clientId} />
))}

核弹级后果:页面直接白屏
防御:保证SSR/CSR的key生成一致性


🚀 四、Key的高级兵法

1. 强制刷新组件

// 江湖秘术:修改key触发卸载+重载
<PaymentForm key={selectedPaymentMethod} />

2. 列表动画过渡

// 搭配React-Transition-Group
<CSSTransition
  key={item.id}
  timeout={300}
  classNames="fade"
>
  <div>{item.text}</div>
</CSSTransition>

3. 跨列表穿梭

// 跨列表保持状态
const archivedItems = items.filter(item => item.archived);

return (
  <div>
    <ActiveList items={items} />
    <ArchiveList 
      items={archivedItems}
      // 即使移动到其他列表保持相同key仍复用组件
    />
  </div>
);

🎯 面试官の灵魂拷问

面试官:(突然合上Mac)如果让你设计一个比 key 更牛的方案,会怎么做?

:(战术沉默)可能...给每个组件植入 DNA 识别芯片

// 幻想中的未来API
<List>
  <Item geneticCode={getComponentDNA(item)} />
</List>

但现实是 —— key 方案已经足够优雅,React团队甚至用它登上了 《算法导论》 的 Diff 优化案例!