iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0
佛心分享-IT 人職涯歷練

從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始系列 第 18 篇

Day 18|key 到底是幹嘛的?為什麼不能每次都直接用 index?

  • 分享至 

  • xImage
  •  

上一篇講到 React State 更新時,我們會盡量建立新的 Array / Object。

但只要開始 Render List,很快又會看到另一個幾乎無所不在的東西:

key

例如:

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

第一次看到時,我很容易把 key 理解成:

React 規定列表一定要放的東西。

但真正重要的不是「有沒有寫 key」。

而是:

React 到底要怎麼知道這一筆資料是誰?


先從沒有 key 的列表開始

假設:

const items = [
  { id: 1, name: "Apple" },
  { id: 2, name: "Banana" },
  { id: 3, name: "Orange" },
];

Render:

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

畫面:

Apple
Banana
Orange

看起來完全沒問題。

但 React 會需要知道:

上一輪 Render 的 Apple
↓
是不是這一輪的 Apple?

上一輪的 Banana
↓
是不是這一輪的 Banana?

因為列表不一定永遠不變。

可能會:

新增
刪除
排序
更新

所以 React 需要一個可以辨認每一筆資料的方式。

這就是:

key

key 可以先理解成「這一筆資料的身分證」

例如:

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

這裡:

key={item.id}

等於告訴 React:

id = 1
→ 這是同一筆 Apple

id = 2
→ 這是同一筆 Banana

id = 3
→ 這是同一筆 Orange

所以就算順序改了:

Orange
Apple
Banana

React 還是可以透過:

1
2
3

知道誰是誰。


為什麼很多教學會直接用 index?

例如:

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

這樣通常不會馬上報錯。

而且畫面也看起來正常。

所以初學時很容易覺得:

那全部 key={index} 不就好了?

問題在於:

index 代表的是位置

不是:

這筆資料真正的身份

假設刪掉第一筆

原本:

index 0 → Apple
index 1 → Banana
index 2 → Orange

如果刪掉 Apple:

Banana
Orange

新的 index 變成:

index 0 → Banana
index 1 → Orange

也就是:

原本 index 0 是 Apple
現在 index 0 變 Banana

原本 index 1 是 Banana
現在 index 1 變 Orange

如果 React 只看 index:

0
1

它可能會認為:

原本第 0 個還是第 0 個。

但實際資料已經不是同一筆了。


問題不一定只表現在文字上

如果列表只是:

Apple
Banana
Orange

可能很難看出問題。

但如果每一列裡面還有自己的 UI 狀態,例如:

<input />

或:

checkbox
expanded state
editing state
animation

就可能出現很奇怪的結果。

例如:

Apple 的 input
Banana 的 input
Orange 的 input

使用者正在編輯 Banana。

這時候刪掉 Apple。

如果用 index 當 key,

React 可能會把原本某個位置的 UI 狀態重用到另一筆資料上。

最後就可能看到:

「我明明編輯 Banana,為什麼刪掉上一筆後狀態跑掉了?」


穩定的 ID 才比較像真正的 Identity

所以比較理想的是:

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

因為:

id = 2

不管 Banana 排在:

第 1 個
第 2 個
第 10 個

它還是:

id = 2

所以:

key 最重要的不是唯一而已,還要穩定。


key 要符合兩件事

可以先記:

① 同一個列表裡要唯一
② 同一筆資料跨 Render 要穩定

例如:

key={item.id}

通常很好。

因為:

唯一
+
穩定

什麼叫「不穩定」?

例如:

key={Math.random()}

這看起來每一筆都一定不同。

但問題是:

每次 Render 都會重新產生新的值。

所以:

第一次 Render
Apple → 0.123

第二次 Render
Apple → 0.987

React 會覺得:

這不是原本那一筆,是新的。

這樣反而失去 key 的意義。


index 什麼時候不是完全不能用?

這裡也不是要說:

index 永遠不能當 key。

如果列表:

完全不會重新排序
不會刪除
不會插入
每一筆也沒有複雜的內部狀態

那用 index 的風險比較低。

但只要列表可能:

新增
刪除
排序
Filter
Drag & Drop

就應該優先找真正穩定的 ID。


Ant Design Table 裡為什麼又出現 rowKey?

實際專案裡,如果使用:

<Table />

或 ProTable,

常看到:

rowKey="id"

這其實跟 React 的 key 概念非常接近。

Table 需要知道:

每一列資料到底是誰。

例如:

[
  {
    id: 1,
    name: "Apple",
  },
  {
    id: 2,
    name: "Banana",
  },
]

可以:

<Table
  rowKey="id"
  dataSource={data}
/>

也就是:

id = 1
→ 第一筆資料的 identity

id = 2
→ 第二筆資料的 identity

真實專案裡,id 也不一定真的唯一

這也是我後來才遇到的問題。

有時候 API 回來的:

id

在目前這個列表裡,竟然可能重複。

例如資料可能來自:

不同來源
不同版本
不同子項目

但剛好共用同一個:

id

這時候如果:

rowKey="id"

就可能出現:

Duplicate key

或列表更新行為異常。

這時候真正要問的是:

這一列資料真正的唯一身份是什麼?

有時可能需要複合 key。

例如:

rowKey={record =>
  `${record.documentId}-${record.operationTime}`
}

概念就是:

documentId
+
operationTime
↓
一起組成唯一識別

不是說一定要這兩個欄位。

而是:

如果單一欄位不足以代表一筆資料,就要找能真正區分每一列的組合。


所以 rowKey 不是「隨便挑一個欄位」

例如:

rowKey="name"

如果:

Apple
Apple
Banana

兩筆 Apple 名字一樣,

那:

name

就不是好的 identity。

所以選 key / rowKey 時,我現在會問:

這個值在這個列表裡真的唯一嗎?

重新取得資料後還會一樣嗎?

排序之後還能代表同一筆嗎?

key 跟畫面顯示內容是兩回事

例如:

<li key={item.id}>
  {item.name}
</li>

使用者看到的是:

item.name

React 用來辨認的是:

item.id

所以 key 通常不是拿來顯示的。

它比較像 React 自己在背後使用的 identity。


為什麼 key 不會出現在 Props 裡?

例如:

<UserCard
  key={user.id}
  name={user.name}
/>

在 UserCard 裡:

function UserCard(props) {
  console.log(props.key);
}

不能把 key 當成一般 Props 使用。

因為:

key

是 React 自己使用的特殊屬性。

如果 Component 自己也需要這個 id,要另外傳:

<UserCard
  key={user.id}
  userId={user.id}
  name={user.name}
/>

這時:

props.userId

才能正常使用。


我現在看到列表,會先找三件事

例如:

data.map(...)

或:

<Table dataSource={data} />

我會先問:

① 每一筆真正唯一的資料是什麼?

② 排序 / 刪除 / 新增之後,它還穩定嗎?

③ 現在使用的 key 是資料 identity,
   還是只是當下的位置?

如果答案只是:

index

我就會多看一眼:

這個列表之後會不會變動?


今天把最近幾篇再串起來

我們最近其實一直在處理:

資料如何被辨認

Day 15:

map()
→ 每一筆資料轉成 UI

Day 16:

Spread
→ 建立新的資料

Day 17:

Immutability
→ State 更新時保留清楚的變化

Day 18:

key
→ Render List 時辨認每一筆是誰

所以 React 不只是:

把資料畫出來。

它還需要知道:

哪些是舊資料?
哪些是新資料?
哪一筆被刪除?
哪一筆只是換位置?
哪一筆真的被更新?

而:

key / rowKey

就是其中很重要的一環。


今天真正想記的是

key 不是:

為了消除 React Warning 隨便塞一個值。

而是:

幫 React 穩定辨認列表裡每一筆資料的 Identity。

所以理想的 key:

唯一
+
穩定

比起:

key={index}

更重要的是找到:

key={item.id}

或真正能代表該筆資料的唯一組合。

當我理解這件事之後,再遇到:

Duplicate key
rowKey
列表刪除後狀態跑掉

就不會只把它當成 React 在找麻煩。

而是會先問:

React 現在到底靠什麼判斷「這一列就是這一列」?

下一篇可以繼續接列表另一個很常見的實戰問題:

為什麼 Filter 展開 / 收合,有時候竟然會讓 API 重新打一次?Component 到底什麼時候被重新建立了?


上一篇
Day 17|push() 明明有加成功,為什麼 React 還是不建議直接改 State?
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言