談談 react 中的 key

大搜車無線開發中心發表於2018-02-08

前言

key-warn

如果你用過 react,並且曾經嘗試遍歷陣列來渲染一個元件,就應該遇到過上面的提示。因為提示的等級為 Warning,而非 Error,所以很多開發同學可能就不會去在意,包括我自己。在前幾天開發一個需要動態渲染的元件時,才發現的了 key 的妙用,也因此打算研究下 key 到底是幹什麼用的。

定義

Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.

key 是用來幫助 react 識別哪些內容被更改、新增或者刪除。key 需要寫在用陣列渲染出來的元素內部,並且需要賦予其一個穩定的值。穩定在這裡很重要,因為如果 key 值發生了變更,react 則會觸發 UI 的重渲染。這是一個非常有用的特性。

key 的唯一性

在相鄰的元素間,key 值必須是唯一的,如果出現了相同的 key,同樣會丟擲一個 Warning,告訴相鄰元件間有重複的 key 值。並且只會渲染第一個重複 key 值中的元素,因為 react 會認為後續擁有相同 key 的都是同一個元件。

key 值不可讀

雖然我們在元件上定義了 key,但是在其子元件中,我們並沒有辦法拿到 key 的值,因為 key 僅僅是給 react 內部使用的。如果我們需要使用到 key 值,可以通過其他方式傳入,比如將 key 值賦給 id 等。

arr.map(item => <Component key={item.id} id={item.id} />)
複製程式碼

反模式

很多時候,我們可能並沒有在遍歷陣列渲染元件的時候寫上 key 的習慣,因為除了控制檯報到一個 Warning,並不會有任何影響。因為賦 key 值這一步 react 幫我們做了,預設使用的是遍歷過程中的 index 值。

let arr = ['first', 'second'];

// list1 和 list2 是等價的
const list1 = arr.map(item => <p>{item}</p>);
const list2 = arr.map((item, index) => <p key={index}>{item}</p>);
複製程式碼

在上面的例子中,如果陣列發生了變化,我們需要在陣列的末尾插入一個元素 arr.push('third'),react 經過 diff 後就會發現 :key 值為0和1的元素並沒有發生如何變化,所以 react 會認為, 最後需要在 UI 上發生變更,僅僅是插入一個 key 值為2的新元素。

但是如果我們在陣列的開頭插入了一個新元素arr.unshift('zero'),react 經過 diff 後就會發現每一個元素的 key 值都發生了變化,也是就說每個元素都要重新渲染一次,雖然從結果來看,僅僅是在開頭新增了一個元素而已。如果負責渲染的陣列資料量較大的話,則會對效能造成較大的影響。與 react 使用的啟發式演算法是相悖的。

<!-- before -->
<p key="0">first</p>
<p key="1">second</p>

<!-- after -->
<p key="0">zero</p>
<p key="1">first</p>
<p key="2">second</p>
複製程式碼

因此,推薦的做法是每個兄弟元素都加上一個穩定唯一的 key 值。

應用

主要應用在 dynamic stateful components中,也就是需要動態渲染的子元件。

下圖是一個名為 Filters的元件,內部由一個個condition子元件組成,由 defaultValue負責初始化渲染。defaultValue的值可由元件生成,可以通過 onChange事件獲取,預設不傳為空陣列。通過將之前儲存好的值回傳給元件,即可渲染需要的 Filters

const defalutValue = [{
  id: 1515134128441,
  tag: 'mobile',	// 手機號 決定最後需要使用的 input 輸入框
  flag: [
    '身份標籤',
    `{"code":"location_province_code","valueType":"area_province"}`
  ],                // 決定第一級 級聯選框 的預設值
  operator: "neq",  // 操作符
  target: "00001"   // input 輸入框的值
}, {
    ...
}]
複製程式碼
談談 react 中的 key

這裡遇到的問題是,condition 子元件的內部已經比較複雜了,需要處理多種情況:

  1. 預設值的渲染
  2. 不同篩選條件要對應不同的賦值符以及條件框
  3. 按照級聯的邏輯,父級的變更需要判斷子級的狀態是否需要改變

在上述情況中,如果condition要實現接收不同的預設值來展示不同的效果,則需要寫一系列複雜的 componentWillReceiveProps生命週期。而且不能保證程式碼的可讀性以及維護性,是一個很可怕的事情。

但是,如果你在用 defaultValue渲染每個 Condition 的時候,給它加一個唯一穩定的 key 值,就可以完美解決這個問題。我在實現的過程中,用了當前時間戳作為Condition的 key 值,保證其唯一穩定性。在刪除、增加標籤都能確保正確渲染。如果沒有 key 值也沒有寫componentWillReceiveProps生命週期,在刪除的時候就會發生渲染錯誤。

簡而言之,改變 key 值來重渲染元件是一種——相較於複雜componentWillReceiveProps生命週期——十分低成本的方式。

參考連結

自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證

相關文章