你經常會想要從資料集合中顯示多個類似的元件。 你可以使用 JavaScript 陣列方法 來操作資料陣列。 在這個頁面上,你將使用 filter()map() 與 React 來過濾和轉換你的資料陣列成元件陣列。

你將學習

  • 如何使用 JavaScript 的 map() 從陣列渲染元件
  • 如何使用 JavaScript 的 filter() 只渲染特定元件
  • 何時以及為何使用 React key

從陣列渲染資料

假設你有一個內容列表。

<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>

這些列表項之間的唯一區別是它們的內容,它們的資料。 在構建介面時,你經常需要使用不同的資料顯示同一個元件的多個實例:從評論列表到個人資料圖片庫。 在這些情況下,你可以將該資料儲存在 JavaScript 物件和陣列中,並使用方法,例如 map()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'
];
  1. people 成員**映射**到一個新的 JSX 節點陣列 listItems
const listItems = people.map(person => <li>{person}</li>);
  1. 從你的元件**返回** listItems,並用 <ul> 包裹
return <ul>{listItems}</ul>;

結果如下

const people = [
  'Creola Katherine Johnson: mathematician',
  'Mario José Molina-Pasquel Henríquez: chemist',
  'Mohammad Abdus Salam: physicist',
  'Percy Lavon Julian: chemist',
  'Subrahmanyan Chandrasekhar: astrophysicist'
];

export default function List() {
  const listItems = people.map(person =>
    <li>{person}</li>
  );
  return <ul>{listItems}</ul>;
}

請注意,上面的沙盒顯示一個控制台錯誤

控制台
警告:列表中的每個子項都應該有一個唯一的「key」屬性。

你將在本頁稍後學習如何修復此錯誤。 在我們開始之前,讓我們為你的資料添加一些結構。

過濾項目陣列

這些資料可以更有結構地組織。

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',
}, {
id: 3,
name: 'Percy Lavon Julian',
profession: 'chemist',
}, {
id: 4,
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicist',
}];

假設你想要一種只顯示職業為 'chemist'(化學家)的人的方法。 你可以使用 JavaScript 的 filter() 方法只返回這些人。 這個方法接收一個項目陣列,將它們通過一個「測試」(一個返回 truefalse 的函式),並返回一個只包含通過測試(返回 true)的項目的新陣列。

你只想要 profession'chemist' 的項目。 這個「測試」函式看起來像 (person) => person.profession === 'chemist'。 以下是組合方式

  1. 建立一個只包含「化學家」的新陣列 chemists,方法是對 people 呼叫 filter() 並使用 person.profession === 'chemist' 進行過濾
const chemists = people.filter(person =>
person.profession === 'chemist'
);
  1. 現在對 chemists 進行映射
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>
);
  1. 最後,從你的元件返回 listItems
return <ul>{listItems}</ul>;
import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const chemists = people.filter(person =>
    person.profession === 'chemist'
  );
  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>
  );
  return <ul>{listItems}</ul>;
}

陷阱

箭頭函式會隱式返回 => 之後的表達式,因此你不需要 return 陳述式

const listItems = chemists.map(person =>
<li>...</li> // Implicit return!
);

然而,如果您的 => 後面跟著一個 { 大括號,則必須明確地寫出 return

const listItems = chemists.map(person => { // Curly brace
return <li>...</li>;
});

包含 => { 的箭頭函式被稱為具有「區塊主體」。它們允許您編寫多行程式碼,但您必須自己編寫 return 陳述式。 如果您忘記它,將不會返回任何值!

使用 key 保持列表項目的順序

請注意,以上所有沙盒都在主控台中顯示錯誤

控制台
警告:列表中的每個子項都應該有一個唯一的「key」屬性。

您需要為每個陣列項目提供一個 key —— 一個字串或數字,用於在該陣列的其他項目中唯一標識它

<li key={person.id}>...</li>

注意

map() 呼叫中直接包含的 JSX 元素始終需要鍵值!

鍵值告訴 React 每個組件對應哪個陣列項目,以便稍後可以將它們匹配起來。如果您的陣列項目可以移動(例如,由於排序)、插入或刪除,這一點就變得非常重要。一個精心選擇的 key 可以幫助 React 推斷究竟發生了什麼,並對 DOM 樹進行正確的更新。

您應該將鍵值包含在您的數據中,而不是動態生成它們

export const people = [{
  id: 0, // Used in JSX as a key
  name: 'Creola Katherine Johnson',
  profession: 'mathematician',
  accomplishment: 'spaceflight calculations',
  imageId: 'MK3eW3A'
}, {
  id: 1, // Used in JSX as a key
  name: 'Mario José Molina-Pasquel Henríquez',
  profession: 'chemist',
  accomplishment: 'discovery of Arctic ozone hole',
  imageId: 'mynHUSa'
}, {
  id: 2, // Used in JSX as a key
  name: 'Mohammad Abdus Salam',
  profession: 'physicist',
  accomplishment: 'electromagnetism theory',
  imageId: 'bE7W1ji'
}, {
  id: 3, // Used in JSX as a key
  name: 'Percy Lavon Julian',
  profession: 'chemist',
  accomplishment: 'pioneering cortisone drugs, steroids and birth control pills',
  imageId: 'IOjWm71'
}, {
  id: 4, // Used in JSX as a key
  name: 'Subrahmanyan Chandrasekhar',
  profession: 'astrophysicist',
  accomplishment: 'white dwarf star mass calculations',
  imageId: 'lrWQx8l'
}];

深入探討

為每個列表項目顯示多個 DOM 節點

當每個項目需要渲染多個 DOM 節點,而不是一個時,您該怎麼辦?

簡短的 <>...</> Fragment 語法不允許您傳遞鍵值,因此您需要將它們分組到單個 <div> 中,或者使用稍長且更明確的 <Fragment> 語法:

import { Fragment } from 'react';

// ...

const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);

Fragments 會從 DOM 中消失,因此這將產生一個扁平的 <h1><p><h1><p> 列表,依此類推。

哪裡可以取得您的 key

不同的數據來源提供不同的鍵值來源

  • 來自資料庫的數據:如果您的數據來自資料庫,您可以使用資料庫鍵值/ID,它們本質上是唯一的。
  • 本地生成的數據:如果您的數據是在本地生成和保存的(例如,筆記應用程式中的筆記),請在創建項目時使用遞增計數器、crypto.randomUUID() 或像 uuid 這樣的套件。

鍵值的規則

  • 鍵值在兄弟節點之間必須是唯一的。 但是,在不同陣列中為 JSX 節點使用相同的鍵值是可以的。
  • 鍵值不得更改,否則會失去其作用!不要在渲染時生成它們。

為什麼 React 需要鍵值?

想像一下,您桌面上的檔案沒有名稱。相反地,您將按照順序來引用它們——第一個檔案、第二個檔案,依此類推。您可以習慣它,但是一旦您刪除一個檔案,它就會變得混亂。第二個檔案會變成第一個檔案,第三個檔案會變成第二個檔案,依此類推。

資料夾中的檔案名稱和陣列中的 JSX key 具有類似的用途。它們讓我們能夠在其兄弟元素之間唯一地識別一個項目。一個精心選擇的 key 提供比陣列中的位置更多的資訊。即使由於重新排序導致*位置*發生變化,key 讓 React 在其整個生命週期中都能識別該項目。

陷阱

您可能會想使用項目在陣列中的索引作為其 key。事實上,如果您根本沒有指定 key,React 就會使用索引。但是,如果您插入、刪除項目,或者陣列重新排序,您渲染項目的順序會隨著時間而改變。使用索引作為 key 通常會導致微妙且令人困惑的錯誤。

同樣地,不要動態生成 key,例如使用 key={Math.random()}。這將導致 key 在每次渲染之間永遠不會匹配,導致您的所有組件和 DOM 每次都被重新創建。這不僅速度慢,還會丟失列表項目中的任何使用者輸入。相反地,請使用基於資料的穩定 ID。

請注意,您的組件不會收到 key 作為 prop。它僅被 React 本身用作提示。如果您的組件需要一個 ID,您必須將其作為一個單獨的 prop 傳遞:<Profile key={id} userId={id} />

重點回顧

在本頁中,您學習了

  • 如何將資料從組件移出到陣列和物件等資料結構中。
  • 如何使用 JavaScript 的 map() 生成多組類似的組件。
  • 如何使用 JavaScript 的 filter() 創建過濾後的項目陣列。
  • 為什麼以及如何在集合中的每個組件上設置 key,以便 React 即使在它們的位置或資料發生變化時也能追蹤它們中的每一個。

挑戰 1 4:
將列表拆分為兩個

此範例顯示所有人員的列表。

將其更改為顯示兩個單獨的列表,一個接一個:**化學家**和**其他人**。與之前一樣,您可以通過檢查 person.profession === 'chemist' 來判斷一個人是否是化學家。

import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const listItems = people.map(person =>
    <li key={person.id}>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        known for {person.accomplishment}
      </p>
    </li>
  );
  return (
    <article>
      <h1>Scientists</h1>
      <ul>{listItems}</ul>
    </article>
  );
}