第 4 章:Props (屬性) 的運用

在 React 中,Props (Properties 的縮寫) 是將資料從父元件傳遞到子元件的主要方式。它們是唯讀的,這意味著子元件不能直接修改接收到的 Props。這種單向資料流的特性使得應用程式的狀態更容易追蹤和理解。

4.1 什麼是 Props?

想像一下 HTML 標籤的屬性,例如 <img src="image.jpg" alt="An image"> 中的 srcalt。在 React 元件中,Props 的作用與此類似。它們允許你在建立元件實例時,向元件傳遞自訂的資料。

Props 可以是任何 JavaScript 資料型別,包括:

  • 字串 (String)
  • 數字 (Number)
  • 布林值 (Boolean)
  • 陣列 (Array)
  • 物件 (Object)
  • 函式 (Function)
  • 甚至是其他的 React 元案 (React Element)

4.2 如何傳遞 Props?

在 JSX 中,你可以像設定 HTML 屬性一樣,將 Props 傳遞給子元件。

父元件 (App.js):

import React from 'react';
import Greeting from './Greeting'; // 假設 Greeting 元件在 Greeting.js 中

function App() {
  const userName = "React 開發者";
  return (
    <div>
      <h1>歡迎來到我的應用程式</h1>
      <Greeting name="訪客" message="你好!" />
      <Greeting name={userName} message="今天天氣真好!" />
      <Greeting name="使用者" /> {/* message prop 將是 undefined */}
    </div>
  );
}

export default App;

子元件 (Greeting.js):

import React from 'react';

// 使用函式元件接收 props
function Greeting(props) {
  return (
    <div>
      <h2>{props.message || "預設訊息"},{props.name}!</h2>
      <p>很高興見到你。</p>
    </div>
  );
}

export default Greeting;

在這個例子中:

  1. App 元件是父元件。
  2. Greeting 元件是子元件。
  3. 我們在 App 元件中三次使用了 <Greeting /> 元件,每次都傳遞了不同的 namemessage Props。
    • 第一個 <Greeting name="訪客" message="你好!" />name 是 "訪客",message 是 "你好!"。
    • 第二個 <Greeting name={userName} message="今天天氣真好!" />name 的值來自 userName 變數 ("React 開發者"),message 是 "今天天氣真好!"。注意,當 Prop 的值是 JavaScript 表達式 (如變數、函式呼叫等) 時,需要用大括號 {} 包起來。
    • 第三個 <Greeting name="使用者" />:只傳遞了 name Prop,message Prop 沒有被傳遞。

4.3 如何在子元件中讀取 Props?

子元件透過其函式參數 (對於函式元件) 或 this.props (對於類別元件) 來接收 Props。

函式元件 (Function Component):

如上方的 Greeting.js 所示,函式元件接收一個 props 物件作為其第一個參數。你可以透過 props.propertyName 來存取傳遞過來的屬性。

function Greeting(props) {
  console.log(props); // 會印出類似 { name: "訪客", message: "你好!" } 的物件
  return (
    <div>
      <h2>{props.message},{props.name}!</h2>
    </div>
  );
}

類別元件 (Class Component):

對於類別元件,Props 會被儲存在元件實例的 this.props 屬性中。

import React from 'react';

class Welcome extends React.Component {
  render() {
    console.log(this.props); // 同樣會印出 props 物件
    return (
      <div>
        <h2>{this.props.message},{this.props.name}!</h2>
        <p>這是一個類別元件。</p>
      </div>
    );
  }
}

export default Welcome;

App.js 中使用 Welcome 元件:

import React from 'react';
import Welcome from './Welcome'; // 假設 Welcome 元件在 Welcome.js 中

function App() {
  return (
    <div>
      <h1>歡迎來到我的應用程式</h1>
      <Welcome name="類別使用者" message="哈囉!" />
    </div>
  );
}

export default App;

4.4 Props 是唯讀的 (Read-Only)

一個重要的原則是:元件永遠不應該修改自己的 Props。 無論是函式元件還是類別元件,都應該將其 Props 視為唯讀的。

React 的核心理念之一是「單向資料流」(unidirectional data flow)。資料從父元件流向子元件。如果子元件可以修改 Props,那麼資料流就會變得混亂,難以追蹤和除錯。

如果你需要在元件內部處理會隨時間變化的資料,應該使用 State (我們將在下一章詳細介紹)。

錯誤的示範 (不要這樣做):

// Greeting.js - 錯誤示範
function Greeting(props) {
  // 錯誤!試圖修改 props
  // props.name = "新的名字"; // 這會導致錯誤或非預期的行為

  return (
    <div>
      <h2>你好,{props.name}!</h2>
    </div>
  );
}

React 要求所有的元件都像「純函式」(pure functions) 一樣對待它們的 Props。純函式是指在給定相同輸入的情況下,總是返回相同的輸出,並且不會產生任何副作用 (例如修改外部變數或輸入參數)。

4.5 預設 Props (Default Props)

有時候,你希望為 Props 提供預設值,以防父元件沒有傳遞該 Prop。你可以使用 defaultProps (對於類別元件) 或在函式元件內部直接處理。

函式元件中使用預設值:

你可以使用邏輯 OR 運算子 || 或 ES6 的預設參數語法。

// Greeting.js - 使用邏輯 OR
function Greeting(props) {
  const message = props.message || "預設的問候!";
  const name = props.name || "預設使用者";
  return (
    <div>
      <h2>{message},{name}!</h2>
    </div>
  );
}

// Greeting.js - 使用 ES6 預設參數 (更推薦)
function Greeting({ name = "預設使用者", message = "預設的問候!" }) {
  return (
    <div>
      <h2>{message},{name}!</h2>
    </div>
  );
}

類別元件中使用 defaultProps

// Welcome.js
import React from 'react';

class Welcome extends React.Component {
  render() {
    return (
      <div>
        <h2>{this.props.message},{this.props.name}!</h2>
      </div>
    );
  }
}

// 設定 defaultProps
Welcome.defaultProps = {
  name: "預設類別使用者",
  message: "來自類別元件的預設問候!"
};

export default Welcome;

現在,如果在 App.js 中這樣使用 Welcome 元件,它將使用預設值:

<Welcome /> // name 會是 "預設類別使用者", message 會是 "來自類別元件的預設問候!"
<Welcome name="小明" /> // message 會使用預設值

4.6 Props 解構 (Destructuring Props)

為了讓程式碼更簡潔,你可以在函式元件的參數中或類別元件的 render 方法中對 Props 進行解構。

函式元件解構 Props:

// Greeting.js - 解構 props
function Greeting({ name, message }) { // 直接在參數中解構
  return (
    <div>
      <h2>{message || "預設訊息"},{name}!</h2>
    </div>
  );
}

// 或者在函式內部解構
function Greeting(props) {
  const { name, message } = props;
  return (
    <div>
      <h2>{message || "預設訊息"},{name}!</h2>
    </div>
  );
}

類別元件解構 Props:

// Welcome.js - 在 render 方法中解構 props
import React from 'react';

class Welcome extends React.Component {
  render() {
    const { name, message } = this.props; // 在 render 方法頂部解構
    return (
      <div>
        <h2>{message || "預設訊息"},{name}!</h2>
      </div>
    );
  }
}

Welcome.defaultProps = {
  name: "預設類別使用者",
  message: "預設問候!"
};

export default Welcome;

解構可以讓你在使用 Props 時省略 props.this.props. 前綴,使程式碼更易讀。

4.7 props.children

每個元件都有一個特殊的 Prop:props.children。它包含了元件的「子內容」。當你在 JSX 中像這樣巢狀元件時:

<Card title="我的卡片">
  <p>這是卡片的內容。</p>
  <button>點擊我</button>
</Card>

Card 元件的 props.children 就會是:

[
  <p>這是卡片的內容。</p>,
  <button>點擊我</button>
]

這在你想要建立可重用的容器元件 (如 Card, Dialog, Sidebar 等) 時非常有用。

Card.js 元件範例:

import React from 'react';
import './Card.css'; // 假設有一個 Card.css 檔案來設定樣式

function Card(props) {
  return (
    <div className="card">
      {props.title && <h3 className="card-title">{props.title}</h3>}
      <div className="card-content">
        {props.children} {/* 渲染子內容 */}
      </div>
    </div>
  );
}

export default Card;

使用 Card 元件:

// App.js
import React from 'react';
import Card from './Card';

function App() {
  return (
    <div>
      <Card title="資訊卡片">
        <p>這是一些重要的資訊。</p>
        <ul>
          <li>項目一</li>
          <li>項目二</li>
        </ul>
      </Card>

      <Card> {/* 沒有 title prop */}
        <p>沒有標題的卡片內容。</p>
        <img src="image.png" alt="範例圖片" style={{ maxWidth: '100%' }} />
      </Card>
    </div>
  );
}

export default App;

props.children 可以是任何有效的 JSX 內容:文字、React 元素、JavaScript 表達式,甚至是 undefined (如果沒有傳遞子內容)。

4.8 使用 PropTypes 進行型別檢查 (Type Checking with PropTypes)

隨著應用程式規模的擴大,手動追蹤每個元件的 Props 和它們的預期型別可能會變得困難。PropTypes 是一個 React 內建 (在 React 15.5 之後分離為獨立套件 prop-types) 的機制,用於在開發模式下檢查傳遞給元件的 Props 是否符合預期的型別。

雖然在現代 React 開發中,許多人轉向使用 TypeScript 來進行更強大的靜態型別檢查,但 PropTypes 仍然是一個有用的工具,特別是在不使用 TypeScript 的專案中。

安裝 prop-types

npm install prop-types
# 或者
yarn add prop-types

使用 PropTypes

// Greeting.js
import React from 'react';
import PropTypes from 'prop-types'; // 引入 PropTypes

function Greeting({ name, message, count, isActive, user, onGreet }) {
  return (
    <div>
      <h2>{message},{name}!</h2>
      <p>計數: {count}</p>
      <p>狀態: {isActive ? "活躍" : "非活躍"}</p>
      <p>使用者郵箱: {user.email}</p>
      <button onClick={onGreet}>問候</button>
    </div>
  );
}

// 定義 propTypes
Greeting.propTypes = {
  name: PropTypes.string.isRequired, // 字串,且必填
  message: PropTypes.string,        // 字串,選填
  count: PropTypes.number,          // 數字
  isActive: PropTypes.bool,         // 布林值
  user: PropTypes.shape({           // 特定形狀的物件
    id: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired,
    email: PropTypes.string
  }),
  onGreet: PropTypes.func,          // 函式
  // 你也可以指定一個 React 元素
  // customElement: PropTypes.element
  // 或者一個節點 (任何可以被渲染的東西:數字、字串、元素或包含這些型別的陣列)
  // children: PropTypes.node
};

// 設定預設值
Greeting.defaultProps = {
  message: "你好",
  count: 0,
  isActive: false,
  user: { id: 0, name: "匿名", email: "n/a" },
  onGreet: () => console.log("預設問候函式")
};

export default Greeting;

PropTypes 提供的常用型別檢查器:

  • PropTypes.array
  • PropTypes.bool
  • PropTypes.func
  • PropTypes.number
  • PropTypes.object
  • PropTypes.string
  • PropTypes.symbol
  • PropTypes.node (任何可渲染的內容:數字、字串、元素或陣列)
  • PropTypes.element (一個 React 元素)
  • PropTypes.instanceOf(Constructor) (某個類別的實例)
  • PropTypes.oneOf(['News', 'Photos']) (枚舉,值必須是其中之一)
  • PropTypes.oneOfType([PropTypes.string, PropTypes.number]) (多種類型之一)
  • PropTypes.arrayOf(PropTypes.number) (特定型別的陣列)
  • PropTypes.objectOf(PropTypes.number) (具有特定型別值的物件)
  • PropTypes.shape({ color: PropTypes.string, fontSize: PropTypes.number }) (特定形狀的物件)
  • PropTypes.exact({ name: PropTypes.string, quantity: PropTypes.number }) (精確形狀的物件,不允許額外屬性)

你可以在任何型別檢查器後面加上 .isRequired 來表示該 Prop 是必填的。

如果在開發模式下傳遞了錯誤型別的 Prop,或者缺少了 isRequired 的 Prop,React 會在瀏覽器控制台中顯示警告訊息,幫助你及早發現問題。在生產模式下,為了效能,PropTypes 檢查會被跳過。

4.9 總結

Props 是 React 中元件間通訊的基石。它們使得我們可以建立可重用、可配置的元件。

  • Props 用於從父元件向子元件傳遞資料。
  • Props 是唯讀的,子元件不應修改它們。
  • 可以使用預設 Props 來為未傳遞的 Props 提供預設值。
  • 解構 Props 可以讓程式碼更簡潔。
  • props.children 用於存取元件的子內容。
  • PropTypes (或 TypeScript) 可以幫助我們在開發過程中驗證 Props 的型別,提高程式碼的健壯性。

理解 Props 的工作原理對於掌握 React 至關重要。在下一章,我們將學習另一個核心概念:State,以及它如何與 Props 協同工作來管理元件的動態資料。

results matching ""

    No results matching ""