詳解React之key的使用和實(shí)踐
在渲染列表時(shí),React的差異比較算法需要一個(gè)在列表范圍內(nèi)的唯一key來(lái)提高性能(通常用于獲知哪個(gè)列表項(xiàng)改變了)。這個(gè)唯一的key需要我們手動(dòng)提供。React官方建議使用列表數(shù)據(jù)中可用于唯一性標(biāo)識(shí)的字段來(lái)作為列表項(xiàng)渲染時(shí)的key。如果實(shí)在沒(méi)有,則可使用數(shù)組的index勉為其難,性能上可能會(huì)打折扣。
一個(gè)例子
有這樣的一個(gè)場(chǎng)景如下圖所示,有一組動(dòng)態(tài)數(shù)量的input,可以增加和刪除和重新排序,數(shù)組元素生成的組件用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>
}
首先說(shuō)明的是,若頁(yè)面中數(shù)組內(nèi)容是固定而不是動(dòng)態(tài)的話,上面的代碼也不會(huì)有什么問(wèn)題(。•ˇ‸ˇ•。 但是如此這也是不是推薦的做法)。
但是,動(dòng)態(tài)數(shù)組導(dǎo)致其渲染的組件就會(huì)有問(wèn)題,從上面圖中你也能看出問(wèn)題:數(shù)組動(dòng)態(tài)改變后,頁(yè)面上input的輸入內(nèi)容跟對(duì)應(yīng)的數(shù)組元素順序不對(duì)應(yīng)。
為什么會(huì)這樣呢?本文后面會(huì)有解釋。react初學(xué)者對(duì)這可能更加迷惑,本文就來(lái)跟大家探討一下react的key用法,
react key概述
key的作用
react中的key屬性,它是一個(gè)特殊的屬性,它是出現(xiàn)不是給開(kāi)發(fā)者用的(例如你為一個(gè)組件設(shè)置key之后不能獲取組件的這個(gè)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
簡(jiǎn)單來(lái)說(shuō),react利用key來(lái)識(shí)別組件,它是一種身份標(biāo)識(shí)標(biāo)識(shí),就像我們的身份證用來(lái)辨識(shí)一個(gè)人一樣。每個(gè)key對(duì)應(yīng)一個(gè)組件,相同的key react認(rèn)為是同一個(gè)組件,這樣后續(xù)相同的key對(duì)應(yīng)組件都不會(huì)被創(chuàng)建。例如下面代碼:
//this.state.users內(nèi)容
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渲染掛載后,用戶列表只有張三和李四兩個(gè)用戶,王五并沒(méi)有展示處理,主要是因?yàn)閞eact根據(jù)key認(rèn)為李四和王五是同一個(gè)組件,導(dǎo)致第一個(gè)被渲染,后續(xù)的會(huì)被丟棄掉。
這樣,有了key屬性后,就可以與組件建立了一種對(duì)應(yīng)關(guān)系,react根據(jù)key來(lái)決定是銷毀重新創(chuàng)建組件還是更新組件。
- key相同,若組件屬性有所變化,則react只更新組件對(duì)應(yīng)的屬性;沒(méi)有變化則不更新。
- key值不同,則react先銷毀該組件(有狀態(tài)組件的
componentWillUnmount會(huì)執(zhí)行),然后重新創(chuàng)建該組件(有狀態(tài)組件的constructor和componentWillUnmount都會(huì)執(zhí)行)
另外需要指明的是:
key不是用來(lái)提升react的性能的,不過(guò)用好key對(duì)性能是有幫組的。
key的使用場(chǎng)景
在項(xiàng)目開(kāi)發(fā)中,key屬性的使用場(chǎng)景最多的還是由數(shù)組動(dòng)態(tài)創(chuàng)建的子組件的情況,需要為每個(gè)子組件添加唯一的key屬性值。
那么,為何由數(shù)組動(dòng)態(tài)創(chuàng)建的組件必須要用到key屬性呢?這跟數(shù)組元素的動(dòng)態(tài)性有關(guān)。
拿上述用戶列表的例子來(lái)說(shuō),看一下babel對(duì)上述代碼的轉(zhuǎn)換情況:
// 轉(zhuǎn)換前
const element = (
<div>
<h3>用戶列表</h3>
{[<div key={1}>1:張三</div>, <div key={2}>2:李四</div>]}
</div>
);
// 轉(zhuǎn)換后
"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轉(zhuǎn)換后React.createElement中的代碼可以看出,其它元素之所以不是必須需要key是因?yàn)椴还芙M件的state或者props如何變化,這些元素始終占據(jù)著React.createElement固定的位置,這個(gè)位置就是天然的key。
而由數(shù)組創(chuàng)建的組件可能由于動(dòng)態(tài)的操作導(dǎo)致重新渲染時(shí),子組件的位置發(fā)生了變化,例如上面用戶列表子組件新增一個(gè)用戶,上面兩個(gè)用戶的位置可能變化為下面這樣:
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:李四")
]
);
可以看出,數(shù)組創(chuàng)建子組件的位置并不固定,動(dòng)態(tài)改變的;這樣有了key屬性后,react就可以根據(jù)key值來(lái)判斷是否為同一組件。
另外,還有一種比較常見(jiàn)的場(chǎng)景:為一個(gè)有復(fù)雜繁瑣邏輯的組件添加key后,后續(xù)操作可以改變?cè)摻M件的key屬性值,從而達(dá)到先銷毀之前的組件,再重新創(chuàng)建該組件。
key的最佳實(shí)踐
上面說(shuō)到了,由數(shù)組創(chuàng)建的子組件必須有key屬性,否則的話你可能見(jiàn)到下面這樣的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.
可能你會(huì)發(fā)現(xiàn),這只是warning而不是error,它不是強(qiáng)制性的,為什么react不強(qiáng)制要求用key而報(bào)error呢?其實(shí)是強(qiáng)制要求的,只不過(guò)react為按要求來(lái)默認(rèn)上幫我們做了,它是以數(shù)組的index作為key的。
index作為key是一種反模式
在list數(shù)組中,用key來(lái)標(biāo)識(shí)數(shù)組創(chuàng)建子組件時(shí),若數(shù)組的內(nèi)容只是作為純展示,而不涉及到數(shù)組的動(dòng)態(tài)變更,其實(shí)是可以使用index作為key的。
但是,若涉及到數(shù)組的動(dòng)態(tài)變更,例如數(shù)組新增元素、刪除元素或者重新排序等,這時(shí)index作為key會(huì)導(dǎo)致展示錯(cuò)誤的數(shù)據(jù)。本文開(kāi)始引入的例子就是最好的證明。
{this.state.data.map((v,idx)=><Item key={idx} v={v} />)}
// 開(kāi)始時(shí):['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>
// 數(shù)組重排 -> ['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>
上面實(shí)例中在數(shù)組重新排序后,key對(duì)應(yīng)的實(shí)例都沒(méi)有銷毀,而是重新更新。具體更新過(guò)程我們拿key=0的元素來(lái)說(shuō)明, 數(shù)組重新排序后:
- 組件重新render得到新的虛擬dom;
- 新老兩個(gè)虛擬dom進(jìn)行diff,新老版的都有
key=0的組件,react認(rèn)為同一個(gè)組件,則只可能更新組件; - 然后比較其children,發(fā)現(xiàn)內(nèi)容的文本內(nèi)容不同(由
a--->c),而input組件并沒(méi)有變化,這時(shí)觸發(fā)組件的componentWillReceiveProps方法,從而更新其子組件文本內(nèi)容; - 因?yàn)榻M件的children中input組件沒(méi)有變化,其又與父組件傳入的任
props沒(méi)有關(guān)聯(lián),所以input組件不會(huì)更新(即其componentWillReceiveProps方法不會(huì)被執(zhí)行),導(dǎo)致用戶輸入的值不會(huì)變化。
這就是index作為key存在的問(wèn)題,所以不要使用index作為key。
key的值要穩(wěn)定唯一
在數(shù)組中生成的每項(xiàng)都要有key屬性,并且key的值是一個(gè)永久且唯一的值,即穩(wěn)定唯一。
在理想情況下,在循環(huán)一個(gè)對(duì)象數(shù)組時(shí),數(shù)組的每一項(xiàng)都會(huì)有用于區(qū)分其他項(xiàng)的一個(gè)鍵值,相當(dāng)數(shù)據(jù)庫(kù)中主鍵。這樣就可以用該屬性值作為key值。但是一般情況下可能是沒(méi)有這個(gè)屬性值的,這時(shí)就需要我們自己保證。
但是,需要指出的一點(diǎn)是,我們?cè)诒WC數(shù)組每項(xiàng)的唯一的標(biāo)識(shí)時(shí),還需要保證其值的穩(wěn)定性,不能經(jīng)常改變。例如下面代碼:
{
this.state.data.map(el=><MyComponent key={Math.random()}/>)
}
上面代碼中中MyComponent的key值是用Math.random隨機(jī)生成的,雖然能夠保持其唯一性,但是它的值是隨機(jī)而不是穩(wěn)定的,在數(shù)組動(dòng)態(tài)改變時(shí)會(huì)導(dǎo)致數(shù)組元素中的每項(xiàng)都重新銷毀然后重新創(chuàng)建,有一定的性能開(kāi)銷;另外可能導(dǎo)致一些意想不到的問(wèn)題出現(xiàn)。所以:
key的值要保持穩(wěn)定且唯一,不能使用
random來(lái)生成key的值。
所以,在不能使用random隨機(jī)生成key時(shí),我們可以像下面這樣用一個(gè)全局的localCounter變量來(lái)添加穩(wěn)定唯一的key值。
var localCounter = 1;
this.data.forEach(el=>{
el.id = localCounter++;
});
//向數(shù)組中動(dòng)態(tài)添加元素時(shí),
function createUser(user) {
return {
...user,
id: localCounter++
}
}
key其它注意事項(xiàng)
當(dāng)然除了為數(shù)據(jù)元素生成的組件要添加key,且key要穩(wěn)定且唯一之外,還需要注意以下幾點(diǎn):
key屬性是添加到自定義的子組件上,而不是子組件內(nèi)部的頂層的組件上。
//MyComponent
...
render() {//error
<div key={{item.key}}>{{item.name}}</div>
}
...
//right
<MyComponent key={{item.key}}/>
key值的唯一是有范圍的,即在數(shù)組生成的同級(jí)同類型的組件上要保持唯一,而不是所有組件的key都要保持唯一
不僅僅在數(shù)組生成組件上,其他地方也可以使用key,主要是react利用key來(lái)區(qū)分組件的,相同的key表示同一個(gè)組件,react不會(huì)重新銷毀創(chuàng)建組件實(shí)例,只可能更新;key不同,react會(huì)銷毀已有的組件實(shí)例,重新創(chuàng)建組件新的實(shí)例。
{
this.state.type ?
<div><Son_1/><Son_2/></div>
: <div><Son_2/><Son_1/></div>
}
例如上面代碼中,this.state.type的值改變時(shí),原Son_1和Son2組件的實(shí)例都將會(huì)被銷毀,并重新創(chuàng)建Son_1和Son_2組件新的實(shí)例,不能繼承原來(lái)的狀態(tài),其實(shí)他們只是互換了位置。為了避免這種問(wèn)題,我們可以給組件加上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的值改變時(shí),Son_1和Son2組件的實(shí)例沒(méi)有重新創(chuàng)建,react只是將他們互換位置。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于react中列表渲染的局部刷新問(wèn)題
這篇文章主要介紹了關(guān)于react中列表渲染的局部刷新問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
react實(shí)現(xiàn)簡(jiǎn)單的拖拽功能
這篇文章主要為大家詳細(xì)介紹了react實(shí)現(xiàn)簡(jiǎn)單的拖拽功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
React報(bào)錯(cuò)之組件不能作為JSX組件使用的解決方法
本文主要介紹了React報(bào)錯(cuò)之組件不能作為JSX組件使用的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
react基于react-slick實(shí)現(xiàn)多圖輪播效果
React slick是一個(gè)使用React構(gòu)建的輪播組件,下面這篇文章主要給大家介紹了關(guān)于react基于react-slick實(shí)現(xiàn)多圖輪播效果的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
webpack構(gòu)建react多頁(yè)面應(yīng)用詳解
這篇文章主要介紹了webpack構(gòu)建react多頁(yè)面應(yīng)用詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
解決React報(bào)錯(cuò)Cannot assign to 'current'
這篇文章主要為大家介紹了React報(bào)錯(cuò)Cannot assign to 'current' because it is a read-only property的解決方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12

