參考文檔:
React文檔:Lists and Keys
React文檔:Reconciliation
React 實踐心得:key 屬性的原理和用法 ——淘寶前端團(tuán)隊
A “key” is a special string attribute you need to include when creating lists of elements.
最早在React文檔中看到關(guān)于組件的key,當(dāng)時并沒有很在意逃延,直到某天在控制臺看到了如下報錯信息:
Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `App`. See https://fb.me/react-warning-keys for more information.
那么這個 key 究竟有多重要呢撒遣?
React文檔中說應(yīng)當(dāng)在數(shù)組中給組件一個單獨的 key 贺氓,React 用這個 key 作為組件的身份來識別具體對哪個組件做增刪改各種操作蹲缠。通常使用這個組件的唯一標(biāo)識來作為key值仗扬,例如組件數(shù)據(jù)的id等:
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
文檔中寫到斋射,通常來說育勺,不建議使用 index 作為 key 值。
畢竟 index 太易變罗岖,如果使用 index 作為 key涧至,某種程度上來說就失去了使用 key 的意義,同時可能還會引發(fā)其他的問題桑包。但是南蓬,如果你在通過遍歷數(shù)組生成一組組件時沒有為每個組件分配key值,React 會自動使用 index 作為 key 值來分配給每個組件哑了。
注意:
- 關(guān)于 key 值的唯一性:
Keys used within arrays should be unique among their siblings. However they don’t need to be globally unique.
也就是說赘方,我們可以在生成兩組不同的組件時使用相同的一組 key。
- key 只提供給 React 使用
Keys serve as a hint to React but they don’t get passed to your components.
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
)
雖然 key 在使用時看起來和 props 很像弱左,但你并不能在組件中通過 prop 取到它的值窄陡,如上述代碼,在 Post 組件中拆火,可以取到 props.id
的值跳夭,但 props.key
的值是拿不到的涂圆。
看起來沒有使用數(shù)組 map() 時,你的組件可能也需要 key
認(rèn)識到 key 的重要性之后优妙,我也曾經(jīng)天真地以為只需要在 map 中使用它乘综,畢竟React文檔好像也只是在強(qiáng)調(diào) map,直到上一個某一天之后不太久的某天……
這次套硼,我遇到了一個略微復(fù)雜的需求卡辰,大概是要先 map 一個數(shù)據(jù)數(shù)組,加載一個用戶列表:
const userList = users.map((user) =>
<UserItem
key={user.id}
id={user.id} />
)
加載用戶列表的過程當(dāng)然每個 item 都需要一個
key 的邪意,我選擇用 user.id
作為 UserItem
的 key九妈,然后在與某個 item 發(fā)生交互(例如點擊)時,把 user.id
傳給用戶詳情組件 <UserInfoCard id={user.id}/>
雾鬼,拿著 user.id
去redux 里查詢這個用戶信息數(shù)據(jù)萌朱,如果沒有就去后端請求這個數(shù)據(jù),將后端返回的數(shù)據(jù)加入redux策菜,再從 redux 中取得數(shù)據(jù)加載到組件晶疼,每次只展示當(dāng)前點擊的 UserItem 對應(yīng)的 UserInfoCard,但所有的 UserInfo 組件都自動渲染在某一個節(jié)點(假設(shè)為Container)下又憨。所有 UserInfoCard 一旦 mount翠霍,在父節(jié)點 Container 未發(fā)生變化時不會 unmount蠢莺,僅會 update 數(shù)據(jù)和是否可見的屬性。
面對這個邏輯躏将,作為 UserInfoCard 的使用者锄弱,我沒有關(guān)心它具體渲染在哪個節(jié)點下祸憋,自然忽略了它們共有一個父節(jié)點,也就是說夺衍,雖然沒有使用 map 來生成一組 UserInfo 組件,但確確實實每增加一個UserInfoCard沟沙,都等同于在它們的父節(jié)點Container下為它們增加了一個同為 UserInfoCard 的好姐妹河劝。
這樣實際上和 map 生成的一組好兄弟本質(zhì)上并沒有什么區(qū)別矛紫,沒有 key 值的話,React 難以對它們加以區(qū)分颊咬,另外本地沒有的數(shù)據(jù)還需要從后端獲取牡辽,假設(shè)這樣一種情況:
- 點擊一號用戶的 UserItem,需向后端請求一號用戶的數(shù)據(jù)
- 在后端的數(shù)據(jù)返回之前敞临,點擊二號用戶的 UserItem,二號用戶的數(shù)據(jù)也沒有挺尿,向后端請求二號用戶的數(shù)據(jù)
從點擊一號用戶到再點擊二號用戶的過程,對父節(jié)點 Container 來說只是一個
Update 的過程编矾,在點擊二號用戶之后,收到一號用戶的數(shù)據(jù)之前窄俏,對父節(jié)點來說蹂匹,此時它有兩個完全一樣的React子元素(兩個沒有數(shù)據(jù)的 UserInfoCard )凹蜈,此時收到了一個用戶的信息數(shù)據(jù)限寞,那么把它更新給哪個 UserInfoCard 呢?請求數(shù)據(jù)這種異步操作無法預(yù)料哪一條請求的數(shù)據(jù)會先到達(dá)仰坦,這就很容易造成混亂了——可能你點擊了二號用戶的UserItem昆烁,看到的卻是一號用戶的信息。
為了避免這種混亂缎岗,使用 key 屬性就非常有必要了。
在使用 UserInfoCard 時將 user.id
作為
key 一并傳給 UserInfoCard白粉,這樣用戶在點擊不同的 UserItem 時传泊,key 也發(fā)生了變化,結(jié)合前面談到的 key 的作用鸭巴,不難發(fā)現(xiàn)眷细,對 React 來說此時就很容易分辨究竟是哪個 UserInfoCard 需要更新,即使多次點擊同一個 UserItem鹃祖,也不會創(chuàng)建多個相同的 UserInfoCard溪椎,而是會更新該 key 對應(yīng)的 UserInfoCard 實例。
總結(jié):不止在 map 一個數(shù)組創(chuàng)建一組組件實例時需要使用 key 為每個數(shù)組元素標(biāo)記恬口,幾乎在任何由統(tǒng)一結(jié)構(gòu)或同一組件實例化為一組對象時校读,都應(yīng)使用 key 作為它們的身份識別。
參考文檔里最后一篇文章提到了在擁有復(fù)雜狀態(tài)(每個狀態(tài)有具體與之對應(yīng)的對象)的組件里使用 key 來使組件在適當(dāng)?shù)臅r候觸發(fā)銷毀和重建祖能,以此來保持組件內(nèi)部邏輯的清晰歉秫,這也是文檔里沒有提到的一種對 React key 的應(yīng)用。