之前有說到,在 React 中渲染列表的時候,要給每一個資料加一個 key 值,賦予一個確定的標示,而且也詳細描述了如何給一個標示,方法知道了,那麼為什麼要這麼做呢?
在預設條件下,當遞迴 DOM 節點的子元素時,React 會同時遍歷兩個子元素的列表;當產生差異時,生成一個 mutation。
在子元素列表末尾新增元素時,更新開銷比較小。比如:
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
React 會先匹配兩個 <li>first</li>
對應的樹,然後匹配第二個元素 <li>second</li>
對應的樹,最後插入第三個元素的 <li>third</li>
樹。
如果只是簡單的將新增元素插入到表頭,那麼更新開銷會比較大。比如:
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
React 不會意識到應該保留 <li>Duke</li>
和 <li>Villanova</li>
,而是會重建每一個子元素 。這種情況會帶來性能問題。
key
為了解決上述問題, React 支援 key
屬性,當子元素擁有 key 時,React 使用 key 來匹配原有樹上的子元素以及最新樹上的子元素。以下例子在新增 key
之後使得之前的低效轉換變得高效:
<ul>
<li key="1">Duke</li>
<li key="2">Villanova</li>
</ul>
<ul>
<li key="0">Connecticut</li>
<li key="1">Duke</li>
<li key="2">Villanova</li>
</ul>
現在 React 知道只有帶著 '0'
key 的元素是新元素,帶著 '1'
以及 '2'
key 的元素僅僅移動了。
現實場景中,產生一個 key 並不困難。你要展現的元素可能已經有了一個唯一 ID,於是 key 可以直接從你的資料中提取:
<li key={item.id}>{item.name}</li>
當以上情況不成立時,你可以新增一個 ID 欄位到你的模型中,或者利用一部分內容作為哈希值來生成一個 key。這個 key 不需要全域唯一,但在列表中需要保持唯一。
最後,你也可以使用元素在陣列中的下標作為 key。這個策略在元素不進行重新排序時比較合適,如果有順序修改,diff 就會變得慢。
當基於下標的元件進行重新排序時,元件 state 可能會遇到一些問題。由於元件實例是基於它們的 key 來決定是否更新以及複用,如果 key 是一個下標,那麼修改順序時會修改當前的 key,導致非受控元件的 state(比如輸入框)可能相互篡改導致無法預期的變動。