第 4 章:Props (屬性) 的運用
在 React 中,Props (Properties 的縮寫) 是將資料從父元件傳遞到子元件的主要方式。它們是唯讀的,這意味著子元件不能直接修改接收到的 Props。這種單向資料流的特性使得應用程式的狀態更容易追蹤和理解。
4.1 什麼是 Props?
想像一下 HTML 標籤的屬性,例如 <img src="image.jpg" alt="An image">
中的 src
和 alt
。在 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;
在這個例子中:
App
元件是父元件。Greeting
元件是子元件。- 我們在
App
元件中三次使用了<Greeting />
元件,每次都傳遞了不同的name
和message
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 協同工作來管理元件的動態資料。