為什麼 React 渲染列表時需要加上 key?

2022年2月15日

💎 加入 E+ 成長計畫 如果你喜歡我們的內容,歡迎加入 E+,獲得更多深入的軟體前後端內容

當我們在 React 要渲染一個列表時,如果沒有在每一個被渲染的元件加上 key 這個 prop,就會跳出 Warning: Each child in a list should have a unique “key” prop. 這個錯誤訊息。直到開發者把 key 加上後,這個警告訊息才會消失。為什麼要加上 key? 以及 key 有什麼原則需要遵守? 這是在開發 React 時需要有的重要概念,也是面試經常會問的基礎題。

為什麼需要 key?

key 就像一個獨特身份,讓 React 可以去分辨哪些子元件被新增、 移除,或是修改。(編按:若要進一步說明此概念,推薦在面試時畫下 Dan Abramov 的這個系列推文例子)

React key example
React key example
圖片來源:https://twitter.com/dan_abramov/status/1415279090446204929

從上面的例子可以看到,當今天紅黃藍三個圈,變成紅藍黃;這會有兩個可能性。可能性一第二個圈跟第三個圈的位置互換;可能性二是位置沒互換,但第二個圈變藍色、第三個圈變黃色。如果沒有一個獨特辨識的機制,我們會沒辦法知道,究竟是哪一個可能性。

如果沒辦法有效辨識,將可能出現 bug。舉例來說,假如今天的圈圈是有狀態的,例如裡面有勾選方塊,然後第個二圈有被勾選。假如今天換顏色是因為第二個圈被交換到第三個位置,這時表示新一次的渲染時,第三個圈要是被勾選的。不過假如我們沒有一個辨識機制,要是 React 誤以為換顏色不是因為換位置,而是單純的第二個圈換顏色,那麼將會渲染出仍是第二個圈是被勾選的;那麼這就會是一個 bug。

然而,有了 key 這個辨識機制,React 就會知道在新的一次渲染時,原本的狀態應該被保留在列表中的哪一個元件中。因此,React 之所以需要 key,正是因為 key 可以讓 React 知道,哪些子元件被新增、 移除,或是修改。

除此之外,看到下面這段 React 官方文件的例子 (編按:面試時也推薦直接舉這例子)。原本有個列表,我們在最上方新增一個 <li>Connecticut</li> ,如果沒有 key,對 React 來說將會是,Duke 變成 Connecticut、Villanova 變成 Duke,而最後新增一個 Villanova。換句話說,整個列表都改變了,所以 React 會打掉舊的,重建一個新的列表。當列表變大時,就會很消耗效能。

// 原本
<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

// 改變後
<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

然而,如果有了 key。React 透過 key 發現 2015 與 2016 都沒有變,所以不動他們。這時只會在最上面多加上

<li key="2014">Connecticut</li>

這比起打掉整個列表再重建,會有效率非常多。

// 原本
<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

// 改變後
<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

總結來說,key 可以讓 React 知道,哪些子元件被新增、 移除,或是修改。這一來可以避免一些跟狀態改變有關的 bug,二來也可以讓 React 在更新時的渲染更有效率。

追問題:使用 key 有哪些原則要遵守?

key 需要是獨特唯一 (unique)

如上面提到 key 是讓 React 用來辨認列表中的子元件的機制,因此 key 需要是獨特唯一,不然會有潛在的 bug 的可能性。

key 需要穩定且可預測,不然效能會變差

舉例來說,不能用 key= {Math.random()} 。之所以不能用隨機值,是因為 React 的渲染機制與背後的演算法,會再遇到 key 時,去比對原本的節點,如果發現 key 不一樣,React 就會把舊的節點銷毀,然後創建一個新的節點。如果該節點根本沒有改變,只因為 key 不同就被打掉重建,這將會造成不必要的重建,也會讓效能變差 (編按:有興趣多了解可讀這篇 React 的官方文件)。

避免把 index 當成 key

事實上,如果沒有特別去設定 key,React 預設會把列表的 index 當成 key。如果這個列表都不會有順序上的改變,例如上面的三個圈圈不會有位置交換,只有圈圈本身的顏色改變,那不會有什麼問題。只是如果有位置的改變,把 index 當成 key 就可能產生 bug。

舉例來說,今天如果有下面這樣的列表,我們在 index 為 1 的地方加入一個項目,這時會導致原本 index 為 1 的變成 2。如果 index 為 1 的本來有一些狀態,例如原本 Duke 有被勾選,這時會變成 Boston 被勾選,但是 Duke 反而沒被勾選。這是我們要避免的 bug!

// 插入新元件前
<ul>
  <li key="0">Connecticut</li>
  <li key="1">Duke</li>
  <li key="2">Villanova</li>
</ul>

// 插入新元件後
<ul>
  <li key="0">Connecticut</li>
	<li key="1">Boston</li>
  <li key="2">Duke</li>
  <li key="3">Villanova</li>
</ul>

總結來說,key 需要是獨特唯一、key 需要是穩定可預測,以及避免用 index 作為 key。這三點是我們在使用 key 時需要避免的。

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們