本篇筆記摘錄自官方文件 Rendering Lists
在 React 中,當我們想渲染多個相似元件時,可以使用 JavaScript array methods 裡的filter()
、map()
來操縱陣列內的資料。
Rendering data from arrays
以下範例為一個簡單的列表:
<ul>
<li>Creola Katherine Johnson: mathematician</li>
<li>Mario José Molina-Pasquel Henríquez: chemist</li>
<li>Mohammad Abdus Salam: physicist</li>
<li>Percy Lavon Julian: chemist</li>
<li>Subrahmanyan Chandrasekhar: astrophysicist</li>
</ul>
列表內的每個項目,唯一的不同處是它們的內容,也就是它們的資料。
使用同一個元件來展現不同資料是很常遇到的情況,以之前分享過的 Side Project Good Good Eat POS System 為例,一次顯示多筆菜單與報表資料,就是使用map()
實作出來的。
通常我們不一定需要將 API 的所有資料都顯示出來,這時候就是filter()
派上用場的時候了。
以下為將列表中項目轉換為陣列的方法:
1. 將資料放進陣列:
const people = [
'Creola Katherine Johnson: mathematician',
'Mario José Molina-Pasquel Henríquez: chemist',
'Mohammad Abdus Salam: physicist',
'Percy Lavon Julian: chemist',
'Subrahmanyan Chandrasekhar: astrophysicist'
];
2. 對people
內的成員使用map()
,map()
會 return 一個新陣列listItems
:
const listItems = people.map(person => <li>{person}</li>);
3. 在元件內 returnlistItems
,並使用<ul>
包覆:
return <ul>{listItems}</ul>;
完整程式碼如下:
( 將左側拉桿向右移動,可以看見程式碼的部分,拉開後點擊左上角三條線漢堡圖示可以看見完整檔案,右邊則為輸出結果。 )
請注意 console 裡的警告訊息,等等我們會來修復它:
Warning: Each child in a list should have a unique “key” prop.
Filtering arrays of items
下方的程式碼與上方的是同一個,只是資料內容有了更完整的結構:
const people = [{
id: 0,
name: 'Creola Katherine Johnson',
profession: 'mathematician',
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chemist',
}, {
id: 2,
name: 'Mohammad Abdus Salam',
profession: 'physicist',
}, {
name: 'Percy Lavon Julian',
profession: 'chemist',
}, {
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicist',
}];
以下步驟假定只想篩選出profession
為'chemist'
的人,我們可以使用 JavaScript 中的filter()
達成目的,filter()
會根據我們給它的條件設定,回傳一個新的陣列,而新陣列的內容,將會只有符合條件設定的項目。
1 . 篩選出profession
為'chemist'
的人:
const chemists = people.filter(person =>
person.profession === 'chemist'
);
2 . 對chemist
使用map()
,並放入想要顯示的資訊:
const listItems = chemists.map(person =>
<li>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
known for {person.accomplishment}
</p>
</li>
);
3 . 最後,在元件內 returnlistItems
:
return <ul>{listItems}</ul>;
完整程式碼如下,可以透過右側的輸出顯示看到目前的畫面:
【 注意 】
箭頭函式會 return=>
後的程式碼,所以不需要寫出return
:
const listItems = chemists.map(person =>
<li>...</li> // Implicit return!
);
如果箭頭後面接了大括號{ }
則需要加上return
:
const listItems = chemists.map(person => { // Curly brace
return <li>...</li>;
});
Keeping list items in order with key
在剛剛所有的範例裡,都有下面這個警告訊息:
Warning: Each child in a list should have a unique “key” prop.
我們需要給陣列裡每個項目一個key
,key
可以是字串或數字,但它必須是獨特的:
<li key={person.id}>...</li>
JSX 元素如果是直接使用
map()
回傳的資料,通常都會需要設定key
。
key
將會告訴 React,陣列內的項目分別對應到哪一個元件,以利在後續配對它們,這在陣列項目可以移動的情形下是非常重要的 ( 像是插入或刪除 )。
key
會幫助 React 推斷發生的事情,並正確的更新 DOM tree。
key
的來源應該要從資料取得,而不是自己隨便給它一個。
以這個範例來說,id
為每個項目獨特的值,所以我們應該要用它來當key
:
【 DEEP DIVE 】
Fragment 的簡易語法<>...</>
沒有辦法傳遞key
,所以需要額外使用<div>
包覆,或者是使用顯式的 Fragment 語法<Fragment>
。
使用此方法必須先從 React 引入 Fragment。
import { Fragment } from 'react';
// ...
const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);
Fragment 不會產生 DOM 節點,是可以避免產生多餘節點的方法,以上面程式碼為例,因為 Fragment 不會產生節點,最後只會獲得同層級的內容,像是<h1>
,<p>
,<h1>
,<p>
。
Where to get your key
key
的來源大致可以分為兩種:
- 資料庫內的資料:如果資料來源是資料庫,可以直接使用資料庫裡的 ID,因為是獨特的資訊。
- 本地產生的資料:如果你的資料來源是在本地產生,可以遞增的計數器
crypto.randomUUID()
,或是使用像uuid
這樣的套件。
Rules of keys
key
的規則:
- 在同一個陣列裡,
key
必須是獨特的,在不同的陣列裡則可以使用相同的key
。 key
不能被改變,不可在渲染時產生key
。
Why does React need keys?
為什麼 React 會需要key
呢?
想像電腦桌面有一堆沒有名字的檔案,我們希望它們依序排好,第一個檔案、第二個檔案…,但在這樣的情形下,如果刪除了第一個檔案,將會對順序產生困惑,原本的第二個檔案將會變成第一個檔案,以此類推。
檔案名稱與key
在陣列內的目的是相似的,這讓它們變得有獨特性、有辨識度,一個好的key
所能帶來的好處,不僅僅只是位置而已。
假定陣列的順序被重新排序,但key
在整個生命週期中始終是獨特且不變的。
【Pitfall】
你可能會想要使用陣列中的索引 ( index ) 來當key
。
事實上,如果沒有定義key
,React 將會使用索引來當key
。
但是順序是會改變的,陣列有可能會有插入或刪除的操作,或是重新排列,如果將索引設為key
,將有可能發生 bug。
同樣地,不可使用 Math.random 當作key
的來源,此方法會造成渲染期間key
無法順利配對,導致元件與 DOM 不斷重建,請使用穩定的資料 ID。
請注意,元件不會將key
視為一個 prop,key
只是用來提醒 React 本身,如果元件需要 ID 資料,必須將它們分開傳入:
<Profile key={id} userId={id} />