有這樣的一個場景以下圖所示,有一組動態數量的input,能夠增長和刪除和從新排序,數組元素生成的組件用index
做爲key的值,例以下圖生成的ui展現:javascript
上面例子中的input組件渲染的代碼以下所示,所有完整代碼能夠參考 ==>完整code。java
{this.state.data.map((v,idx)=><Item key={idx} v={v} />)} //Item組件render方法 render(){ return <li>{this.props.v} <input type="text"/></li> }
首先說明的是,若頁面中數組內容是固定而不是動態的話,上面的代碼也不會有什麼問題(。•ˇ‸ˇ•。 可是如此這也是否是推薦的作法)。react
可是,動態數組致使其渲染的組件就會有問題,從上面圖中你也能看出問題:數組動態改變後,頁面上input的輸入內容跟對應的數組元素順序不對應。數據庫
爲何會這樣呢?本文後面會有解釋。react初學者對這可能更加迷惑,本文就來跟你們探討一下react的key用法,segmentfault
react中的key屬性,它是一個特殊的屬性,它是出現不是給開發者用的(例如你爲一個組件設置key以後不能獲取組件的這個key props),而是給react本身用的。數組
那麼react是怎麼用key的呢?react的做者之一Paul O’Shannessy有提到:babel
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 identitydom
簡單來講,react利用key來識別組件,它是一種身份標識標識,就像咱們的身份證用來辨識一我的同樣。每一個key對應一個組件,相同的key react認爲是同一個組件,這樣後續相同的key對應組件都不會被建立。例以下面代碼:ide
//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屬性呢?這跟數組元素的動態性有關。
拿上述用戶列表的例子來講,看一下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屬性,不然的話你可能見到下面這樣的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的。
在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"/></li> </ul>
上面實例中在數組從新排序後,key對應的實例都沒有銷燬,而是從新更新。具體更新過程咱們拿key=0
的元素來講明, 數組從新排序後:
組件從新render獲得新的虛擬dom;
新老兩個虛擬dom進行diff,新老版的都有key=0
的組件,react認爲同一個組件,則只可能更新組件;
而後比較其children,發現內容的文本內容不一樣(由a--->c
),而input組件並無變化,這時觸發組件的componentWillReceiveProps
方法,從而更新其子組件文本內容;
由於組件的children中input組件沒有變化,其又與父組件傳入的任props
沒有關聯,因此input組件不會更新(即其componentWillReceiveProps
方法不會被執行),致使用戶輸入的值不會變化。
這就是index
做爲key存在的問題,因此不要使用index做爲key
。
在數組中生成的每項都要有key屬性,而且key的值是一個永久且惟一的值
,即穩定惟一。
在理想狀況下,在循環一個對象數組時,數組的每一項都會有用於區分其餘項的一個鍵值,至關數據庫中主鍵。這樣就能夠用該屬性值做爲key值。可是通常狀況下多是沒有這個屬性值的,這時就須要咱們本身保證。
可是,須要指出的一點是,咱們在保證數組每項的惟一的標識時,還須要保證其值的穩定性,不能常常改變。例以下面代碼:
{ this.state.data.map(el=><MyComponent key={Math.random()}/>) }
上面代碼中中MyComponent的key值是用Math.random
隨機生成的,雖然可以保持其惟一性,可是它的值是隨機而不是穩定的,在數組動態改變時會致使數組元素中的每項都從新銷燬而後從新建立,有必定的性能開銷;另外可能致使一些意想不到的問題出現。因此:
key的值要保持穩定且惟一,不能使用
random
來生成key的值。
因此,在不能使用random隨機生成key時,咱們能夠像下面這樣用一個全局的localCounter變量來添加穩定惟一的key值。
var localCounter = 1; this.data.forEach(el=>{ el.id = localCounter++; }); //向數組中動態添加元素時, function createUser(user) { return { ...user, id: localCounter++ } }
固然除了爲數據元素生成的組件要添加key,且key要穩定且惟一以外,還須要注意如下幾點:
key屬性是添加到自定義的子組件上,而不是子組件內部的頂層的組件上。
//MyComponent ... render() {//error <div key={{item.key}}>{{item.name}}</div> } ... //right <MyComponent key={{item.key}}/>
key值的惟一是有範圍的,即在數組生成的同級同類型的組件上要保持惟一,而不是全部組件的key都要保持惟一
不只僅在數組生成組件上,其餘地方也可使用key,主要是react利用key來區分組件的,相同的key表示同一個組件,react不會從新銷燬建立組件實例,只可能更新;key不一樣,react會銷燬已有的組件實例,從新建立組件新的實例。
{ this.state.type ? <div><Son_1/><Son_2/></div> : <div><Son_2/><Son_1/></div> }
例如上面代碼中,this.state.type的值改變時,原Son_1和Son2組件的實例都將會被銷燬,並從新建立Son_1和Son_2組件新的實例,不能繼承原來的狀態,其實他們只是互換了位置。爲了不這種問題,咱們能夠給組件加上key。
{ this.state.type ? <div><Son_1 key="1"/><Son_2 key="2"/></div> : <div><Son_2 key="2" /><Son_1 key="1"/></div> }
這樣,this.state.type的值改變時,Son_1和Son2組件的實例沒有從新建立,react只是將他們互換位置。