天天看點

React 實踐心得:key 屬性的原理和用法

React 實踐心得:key 屬性的原理和用法

我們知道,react 元素可以具有一個特殊的屬性 key,這個屬性不是給使用者自己用的,而是給 react 自己用的。如果我們動态地建立 react 元素,而且 react 元素内包含數量或順序不确定的子元素時,我們就需要提供 key 這個特殊的屬性。

如果你有下面這樣的代碼:

react 會在控制台列印出報警資訊:

你必須為數組中的元素提供唯一的 <code>key</code> 屬性,就像下面這樣:

為什麼呢?我們知道當元件的屬性發生了變化,其 render 方法會被重新調用,元件會被重新渲染。比如 userlist 元件的 users 屬性改變了,就得重新渲染 userlist 元件,包括外部的 <code>&lt;div&gt;</code>(容器),内部的一個 <code>&lt;h3&gt;</code> 和若幹個 <code>&lt;div&gt;</code>(每一個描述一個使用者)。

對後一種 <code>&lt;div&gt;</code>(表示使用者的),由于其處在一個長度不确定的數組中,react 需要判斷,對數組中的每一項,到底是建立一個元素加入到頁面中,還是更新原來的元素。比如以下幾種情況:

<code>[{name: '張三', age: 20}]</code> =&gt; <code>[{name: '張三', age: 21}]</code>:這種情況明顯隻需要更新元素,沒有必要重新建立元素。因為人還是那個人,除了 age,其他資訊沒有變,顯示使用者姓名的那個(更小的)元素,是不需要更新(被 reactdom 操作到)的。

<code>[{name: '張三'}]</code> =&gt; <code>[{name: '張三'}, {name: '李四'}]</code> 這種情況,顯然需要添加一個新元素來表示李四,這個新元素對應的 dom 元素會被插入到頁面中。

<code>[{name: '張三'}]</code> =&gt; <code>[{name: '李四'}]</code>:這種情況就有點複雜了,似乎兩種方案都可以。可以把表示張三的元素删掉,為李四建立一個,當然是非常合理的選擇。但是直接把張三的元素換成李四,似乎也無不可。

實際上,如果真的認為上述第3種的後一種方案也無不可,那可是大錯特錯了。為什麼呢:

考慮這種情況:<code>[{name: '張三'}, {name: '李四'}]</code> =&gt; <code>[{name: '李四'}, {name: '張三'}]</code>,難道也需要把張三的元素更新成李四的,李四的元素更新成張三的嗎?

那麼,為數組中的元素傳一個唯一的 key(比如使用者的 id),就很好地解決了這個問題。react 比較更新前後的元素 key 值,如果相同則更新,如果不同則銷毀之前的,重新建立一個元素。

那麼,為什麼隻有數組中的元素需要有唯一的 key,而其他的元素(比如上面的<code>&lt;h3&gt;使用者清單&lt;/h3&gt;</code>)則不需要呢?答案是:react 有能力辨識出,更新前後元素的對應關系。這一點,也許直接看 jsx 不夠明顯,看 babel 轉換後的 react.createelement 則清晰很多:

不管 props 如何變化,數組外的每個元素始終出現在 react.createelement() 參數清單中的固定位置,這個位置就是天然的 key。

題外話 初學 react 時還容易産生另一個困惑,那就是為什麼 jsx 不支援 if 表達式來有選擇地輸出(不能這樣:<code>{if(yes){ &lt;div {...props}/&gt; }}</code>),而必須采用三元運算符來完成這項工作(必須這樣:<code>{yes ? &lt;div {...props}/&gt;} : null</code>)。那是因為,react 需要一個 null 去占住那個元素本來的位置。

曾經,我天真的以為 <code>key</code> 這個元素隻應在數組中使用。直到我在一個複雜的項目中寫出了及其惡心的 <code>componentwillreceiveprops</code>方法。我嘗試尋找銷毀和重建元件,觸發<code>componentdidmount</code> 方法,重置 <code>state</code>,然後才突然發現 <code>key</code> 這個屬性已經在那裡了。

舉個例子,我們有一個展示使用者資訊的 <code>userdashboard</code> 元件。傳給元件的 <code>props</code> 隻有使用者的 基本資訊(id,姓名等),而有關使用者的詳細資訊(比如目前是否線上等)是需要請求過來的。元件内的一些操作(比如嘗試與該使用者聊天)也會使用請求,元件本身也有各種狀态(比如是否顯示聊天框)。

整個界面上最多隻有一個 <code>userdashboard</code>,但某些操作(比如點選旁邊的 <code>userlist</code>)可能會切換 <code>userdashboard</code> 的目标使用者,那麼問題就來了:當目标使用者切換的時候,<code>userdashboard</code>僅僅是一個普通的更新操作,觸發的是<code>componentwillreceiveprops</code>,<code>shouldcomponentupdate</code>,<code>componentwillupdate</code>,<code>componentdidupdate</code>這一套方法。我們需要在 <code>componentwillreceiveprops</code> 中做太多的事情:檢測這次 props 的更新是否改變了使用者的 id,如果是的話,我們需要檢查 <code>userdashboard</code> 發出去的請求是否都得到了響應,對還未收到響應的請求登出其響應函數(否則上一個使用者的線上狀态有可能顯示在這一個使用者上);我們還要更新 <code>userdashboard</code> 上的幾乎所有狀态(切換使用者的時候總要把聊天框關閉吧);如果我們還不幸地用的 <code>ref</code> 做了一些神奇的 hack,那麼你還要去手動把之前做的事情複原回來,這簡直要成一團亂麻了!當 <code>userdashborad</code> 的邏輯,你的<code>componentwillreceiveprops</code> 方法裡會充斥着晦澀難懂的隻有你能看懂的代碼(兩周後你自己也看不懂了)。

解決方案是什麼?就是用 <code>key</code> 屬性。在 jsx 中使用 <code>userdashboard</code> 的時候,不僅把<code>userinfo</code> 傳入,把 <code>userinfo.id</code> 作為名為 <code>key</code> 的 <code>props</code> 傳入(盡管 <code>userdashboard</code> 不是數組中的元件)。這樣切換目标使用者的時候,<code>key</code> 屬性也變了,react 會自動銷毀之前的元件,用一個全新的元件來渲染新的使用者:我們可以從容地在 <code>componentwillunmount</code> 裡作清理工作(登出請求的響應函數,防止其更新一個 unmounted component),至于重置<code>state</code> 這些工作已經不需要做了,由于元件不再是更新,而是銷毀和重建,已經是天然完成的。

當然,你可以質疑這樣做是否會影響性能。我認為,隻要目标使用者的切換不夠頻繁,對性能的影響是很小的。如果不使用 <code>key</code> 觸發元件的銷毀和重建,任由元件自行「更新」,每次切換時更新的内容也是很多的。這時,我們使用 <code>key</code> 帶來的性能損耗是完全可以接受的,而帶來的收益卻非常大。

是以,我想說的結論是:為了元件内部邏輯的清晰,你幾乎應該在任何複雜的有狀态元件(尤其是有具體對應對象的)上使用<code>key</code>屬性(隻要 <code>key</code> 屬性的改變不是很頻繁),這樣做,才能在合适的時候觸發元件的銷毀與重建,元件才能有一個健康的生命周期。

配合 react-router 時,通常要為 route 元件賦 key,但通常情況下我們是沒法傳 props 給 route 元件的。解決的方案是 <code>createelement</code> 方法,如下所示。

轉載自:http://taobaofed.org/blog/2016/08/24/react-key/作者: 葉齋

繼續閱讀