將 Props 傳遞給組件

React 組件使用 *props* 彼此通訊。每個父組件都可以透過給予子組件 props 來傳遞一些資訊。 Props 可能會讓你聯想到 HTML 屬性,但你可以透過它們傳遞任何 JavaScript 值,包括物件、陣列和函式。

你將學到

  • 如何將 props 傳遞給組件
  • 如何從組件讀取 props
  • 如何指定 props 的預設值
  • 如何將一些 JSX 傳遞給組件
  • props 如何隨著時間改變

熟悉的 props

Props 是您傳遞給 JSX 標籤的資訊。例如,classNamesrcaltwidthheight 是您可以傳遞給 <img> 的一些 props

function Avatar() {
  return (
    <img
      className="avatar"
      src="https://i.imgur.com/1bX5QH6.jpg"
      alt="Lin Lanying"
      width={100}
      height={100}
    />
  );
}

export default function Profile() {
  return (
    <Avatar />
  );
}

您可以傳遞給 <img> 標籤的 props 是預先定義的(ReactDOM 符合 HTML 標準)。但是您可以將任何 props 傳遞給*您自己的*組件,例如 <Avatar>,來自定義它們。方法如下!

將 props 傳遞給組件

在此程式碼中,Profile 組件沒有將任何 props 傳遞給其子組件 Avatar

export default function Profile() {
return (
<Avatar />
);
}

您可以透過兩個步驟將 props 提供給 Avatar

步驟 1:將 props 傳遞給子組件

首先,將一些 props 傳遞給 Avatar。例如,讓我們傳遞兩個 props:person(一個物件)和 size(一個數字)

export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}

注意事項

如果 person= 後面的雙重大括號讓您感到困惑,請回想一下它們只是一個在 JSX 大括號內的物件。

現在您可以在 Avatar 組件內讀取這些 props。

步驟 2:在子組件內讀取 props

您可以透過在 function Avatar 後面直接的 ({}) 內列出以逗號分隔的名稱 person, size 來讀取這些 props。這讓您可以在 Avatar 程式碼內使用它們,就像使用變數一樣。

function Avatar({ person, size }) {
// person and size are available here
}

在 `Avatar` 元件中加入一些邏輯,使用 `person` 和 `size` 屬性 (props) 來進行渲染,這樣就完成了。

現在你可以使用不同的屬性,以多種不同的方式設定 `Avatar` 的渲染方式。試試調整看看這些屬性的值吧!

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

function Avatar({ person, size }) {
  return (
    <img
      className="avatar"
      src={getImageUrl(person)}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}

export default function Profile() {
  return (
    <div>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi', 
          imageId: 'YfeOqp2'
        }}
      />
      <Avatar
        size={80}
        person={{
          name: 'Aklilu Lemma', 
          imageId: 'OKS67lh'
        }}
      />
      <Avatar
        size={50}
        person={{ 
          name: 'Lin Lanying',
          imageId: '1bX5QH6'
        }}
      />
    </div>
  );
}

屬性讓你能夠獨立思考父元件和子元件。例如,你可以在 `Profile` 中更改 `person` 或 `size` 屬性,而無需考慮 `Avatar` 如何使用它們。同樣地,你可以更改 `Avatar` 使用這些屬性的方式,而無需查看 `Profile`。

你可以將屬性想像成可以調整的「旋鈕」。它們的作用與函式的參數相同——事實上,屬性 *就是* 你元件的唯一參數!React 元件函式接受單個參數,一個 `props` 物件。

function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}

通常你不需要整個 `props` 物件本身,因此你可以將其解構為個別的屬性。

陷阱

**宣告屬性時,不要遺漏 `(` 和 `)` 中間的 `{` 和 `}` 大括號**。

function Avatar({ person, size }) {
// ...
}

這種語法稱為「解構」,相當於從函式參數中讀取屬性。(參考連結)

function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}

指定屬性的預設值

如果你想給一個屬性一個預設值,以便在未指定值時使用,你可以在解構時,在參數後面加上 `=` 和預設值。

function Avatar({ person, size = 100 }) {
// ...
}

現在,如果 `<Avatar person={...} />` 在渲染時沒有 `size` 屬性,`size` 將會被設定為 `100`。

只有在缺少 `size` 屬性或傳遞 `size={undefined}` 時,才會使用預設值。但如果你傳遞 `size={null}` 或 `size={0}`,則**不會**使用預設值。

使用 JSX 展開語法傳遞屬性

有時,傳遞屬性會變得非常重複。

function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);
}

重複的程式碼本身沒有錯——它可以更易讀。但在某些時候,你可能會更重視簡潔性。有些元件會將其所有屬性都轉發給其子元件,就像這個 `Profile` 對 `Avatar` 所做的一樣。因為它們沒有直接使用任何自己的屬性,所以使用更簡潔的「展開」語法是有意義的。

function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}

這會將 `Profile` 的所有屬性都轉發給 `Avatar`,而無需列出每個屬性的名稱。

**謹慎使用展開語法。** 如果你在每個元件中都使用它,那就表示哪裡出錯了。通常,這表示你應該拆分你的元件,並將子元件作為 JSX 傳遞。接下來會詳細說明!

將 JSX 作為子元件傳遞

巢狀內建瀏覽器標籤是很常見的。

<div>
<img />
</div>

有時你會想要以相同的方式巢狀你自己的元件。

<Card>
<Avatar />
</Card>

當你在 JSX 標籤內巢狀內容時,父元件將會在名為 `children` 的屬性中收到該內容。例如,下面的 `Card` 元件將會收到一個設定為 `<Avatar />` 的 `children` 屬性,並將其渲染在一個包裝器 `div` 中。

import Avatar from './Avatar.js';

function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

export default function Profile() {
  return (
    <Card>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi',
          imageId: 'YfeOqp2'
        }}
      />
    </Card>
  );
}

試著將 `<Card>` 內的 `<Avatar>` 替換成一些文字,看看 `Card` 元件如何包裝任何巢狀的內容。它不需要「知道」它裡面渲染的是什麼。你會在很多地方看到這種彈性的模式。

你可以將具有 `children` 屬性的元件想像成有一個「洞」,可以由其父元件使用任意 JSX 來「填補」。你經常會將 `children` 屬性用於視覺包裝器:面板、網格等等。

A puzzle-like Card tile with a slot for "children" pieces like text and Avatar

插圖作者: Rachel Lee Nabors

屬性如何隨時間變化

下面的 `Clock` 元件從其父元件接收兩個屬性:`color` 和 `time`。(父元件的程式碼被省略,因為它使用了狀態,我們現在還不會深入探討。)

嘗試更改下方選擇框中的顏色

export default function Clock({ color, time }) {
  return (
    <h1 style={{ color: color }}>
      {time}
    </h1>
  );
}

這個例子說明了一個組件可能會隨著時間收到不同的屬性。屬性並非一成不變!這裡,time 屬性每秒都會變化,而 color 屬性則會在您選擇其他顏色時發生變化。屬性反映了組件在任何時間點的數據,而不僅僅是在開始時。

然而,屬性是不可變的——一個來自電腦科學的術語,意思是「不可更改的」。當一個組件需要更改其屬性時(例如,響應使用者互動或新的數據),它必須「請求」其父組件傳遞給它不同的屬性——一個新的物件!它的舊屬性將被捨棄,最終 JavaScript 引擎將回收它們佔用的記憶體。

不要嘗試「更改屬性」。當您需要響應使用者輸入(例如更改所選顏色)時,您需要「設定狀態」,您可以在狀態:組件的記憶體中學習。

重點回顧

  • 要傳遞屬性,請將它們添加到 JSX 中,就像使用 HTML 屬性一樣。
  • 要讀取屬性,請使用 function Avatar({ person, size }) 解構語法。
  • 您可以指定一個預設值,例如 size = 100,它用於缺少的和 undefined 的屬性。
  • 您可以使用 <Avatar {...props} /> JSX 展開語法轉發所有屬性,但不要濫用它!
  • 巢狀的 JSX,例如 <Card><Avatar /></Card> 將會以 Card 組件的 children 屬性出現。
  • 屬性是時間上的唯讀快照:每次渲染都會收到新的屬性版本。
  • 您無法更改屬性。當您需要互動性時,您需要設定狀態。

挑戰 1 3:
提取組件

這個 Gallery 組件包含兩個個人檔案的一些非常相似的標記。從中提取一個 Profile 組件以減少重複。您需要選擇要傳遞給它的屬性。

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

export default function Gallery() {
  return (
    <div>
      <h1>Notable Scientists</h1>
      <section className="profile">
        <h2>Maria Skłodowska-Curie</h2>
        <img
          className="avatar"
          src={getImageUrl('szV5sdG')}
          alt="Maria Skłodowska-Curie"
          width={70}
          height={70}
        />
        <ul>
          <li>
            <b>Profession: </b> 
            physicist and chemist
          </li>
          <li>
            <b>Awards: 4 </b> 
            (Nobel Prize in Physics, Nobel Prize in Chemistry, Davy Medal, Matteucci Medal)
          </li>
          <li>
            <b>Discovered: </b>
            polonium (chemical element)
          </li>
        </ul>
      </section>
      <section className="profile">
        <h2>Katsuko Saruhashi</h2>
        <img
          className="avatar"
          src={getImageUrl('YfeOqp2')}
          alt="Katsuko Saruhashi"
          width={70}
          height={70}
        />
        <ul>
          <li>
            <b>Profession: </b> 
            geochemist
          </li>
          <li>
            <b>Awards: 2 </b> 
            (Miyake Prize for geochemistry, Tanaka Prize)
          </li>
          <li>
            <b>Discovered: </b>
            a method for measuring carbon dioxide in seawater
          </li>
        </ul>
      </section>
    </div>
  );
}