一個例子
有這樣的一個場景如下圖所示,有一組動態數量的input,可以增加和刪除和重新排序,數組元素生成的組件用index
作為key的值,例如下圖生成的ui展示:
上面例子中的input組件渲染的代碼如下所示,全部完整代碼可以參考 ==>完整code。
{this.state.data.map((v,idx)=><Item key={idx} v={v} />)} //Item組件render方法 render(){ return <li>{this.props.v} <input type="text"/></li> }
首先說明的是,若頁面中數組內容是固定而不是動態的話,上面的代碼也不會有什么問題(。•ˇ‸ˇ•。 但是這不是推薦的做法)。
但是,動態數組導致其渲染的組件就會有問題,從上面圖中你也能看出問題:數組動態改變后,頁面上input的輸入內容跟對應的數組元素順序不對應。
為什么會這樣呢?本文后面會有解釋。react初學者對這可能更加迷惑,本文就來跟大家探討一下react的key用法,
react key概述
key的作用
react中的key屬性,它是一個特殊的屬性,它是出現不是給開發者用的(例如你為一個組件設置key之后不能獲取組件的這個key props),而是給react自己用的。
那么react是怎么用key的呢?react的作者之一Paul O’Shannessy有提到:
Key is not really about performance, it’s more about identity (which in turn leads to better performance). Randomly assigned and changing values do not form an identity
簡單來說,react利用key來識別組件,它是一種身份標識標識,就像我們的身份證用來辨識一個人一樣。每個key對應一個組件,相同的key react認為是同一個組件,這樣后續相同的key對應組件都不會被創建。例如下面代碼:
//this.state.users內容 this.state = { users: [{id:1,name: '張三'}, {id:2, name: '李四'}, {id: 2, name: "王五"}], ....//省略 } render() return( <div> <h3>用戶列表</h3> {this.state.users.map(u => <div key={u.id}>{u.id}:{u.name}</div>)} </div> ) );
上面代碼在dom渲染掛載后,用戶列表只有張三
和李四
兩個用戶,王五
並沒有展示處理,主要是因為react根據key認為李四
和王五
是同一個組件,導致第一個被渲染,后續的會被丟棄掉。
這樣,有了key屬性后,就可以與組件建立了一種對應關系,react根據key來決定是銷毀重新創建組件還是更新組件。
- key相同,若組件屬性有所變化,則react只更新組件對應的屬性;沒有變化則不更新。
- key值不同,則react先銷毀該組件(有狀態組件的
componentWillUnmount會執行
),然后重新創建該組件(有狀態組件的constructor
和componentWillUnmount
都會執行)
另外需要指明的是:
key不是用來提升react的性能的,不過用好key對性能是有幫組的。
key的使用場景
在項目開發中,key屬性的使用場景最多的還是由數組動態創建的子組件的情況
,需要為每個子組件添加唯一的key屬性值。
那么,為何由數組動態創建的組件必須要用到key屬性呢?這跟數組元素的動態性有關。
拿上述用戶列表的例子來說,看一下babel對上述代碼的轉換情況:
// 轉換前 const element = ( <div> <h3>用戶列表</h3> {[<div key={1}>1:張三</div>, <div key={2}>2:李四</div>]} </div> ); // 轉換后 "use strict"; var element = React.createElement( "div", null, React.createElement("h3",null,"用戶列表"), [ React.createElement("div",{ key: 1 },"1:張三"), React.createElement("div",{ key: 2 },"2:李四") ] );
有babel轉換后React.createElement
中的代碼可以看出,其它元素之所以不是必須需要key是因為不管組件的state
或者props
如何變化,這些元素始終占據着React.createElement
固定的位置,這個位置就是天然的key。
而由數組創建的組件可能由於動態的操作導致重新渲染時,子組件的位置發生了變化,例如上面用戶列表子組件新增一個用戶,上面兩個用戶的位置可能變化為下面這樣:
var element = React.createElement( "div", null, React.createElement("h3",null,"用戶列表"), [ React.createElement("div",{ key: 3 },"1:王五"), React.createElement("div",{ key: 1 },"2:張三"), React.createElement("div",{ key: 2 },"3:李四") ] );
可以看出,數組創建子組件的位置並不固定,動態改變的;這樣有了key屬性后,react就可以根據key值來判斷是否為同一組件。
另外,還有一種比較常見的場景:為一個有復雜繁瑣邏輯的組件添加key后,后續操作可以改變該組件的key屬性值,從而達到先銷毀之前的組件,再重新創建該組件。
key的最佳實踐
上面說到了,由數組創建的子組件必須有key屬性,否則的話你可能見到下面這樣的warning:
Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `ServiceInfo`. See https://fb.me/react-warning-keys for more information.
可能你會發現,這只是warning而不是error,它不是強制性的,為什么react不強制要求用key而報error呢?其實是強制要求的,只不過react為按要求來默認上幫我們做了,它是以數組的index
作為key的。
index作為key是一種反模式
在list數組中,用key來標識數組創建子組件時,若數組的內容只是作為純展示,而不涉及到數組的動態變更,其實是可以使用index
作為key的。
但是,若涉及到數組的動態變更,例如數組新增元素、刪除元素或者重新排序等,這時index作為key會導致展示錯誤的數據。本文開始引入的例子就是最好的證明。
{this.state.data.map((v,idx)=><Item key={idx} v={v} />)} // 開始時:['a','b','c']=> <ul> <li key="0">a <input type="text"/></li> <li key="1">b <input type="text"/></li> <li key="2">c <input type="text"/></li> </ul> // 數組重排 -> ['c','b','a'] => <ul> <li key="0">c <input type="text"/></li> <li key="1">b <input type="text"/></li> <li key="2">a <input type="text"/>