Function component 與 class component 關鍵區別
在 React 中,Function Component(函數元件)和 Class Component(類別元件)是用來定義 React 元件的兩種主要方式。以下是它們的關鍵區別,幫助你理解兩者的差異與適用場景:
1. 語法結構
-
Function Component:
-
使用 JavaScript 函數(ES6 箭頭函數或普通函數)定義。
-
語法簡潔,直接回傳 JSX。
-
範例:
import React from "react";
const MyFunctionComponent = (props) => {
return (
<div>
<h1>{props.title}</h1>
</div>
);
};
export default MyFunctionComponent;
-
-
Class Component:
-
使用 ES6 類別(class)繼承 React.Component。
-
必須定義 render 方法來回傳 JSX。
-
範例:
import React, { Component } from "react";
class MyClassComponent extends Component {
render() {
return (
<div>
<h1>{this.props.title}</h1>
</div>
);
}
}
export default MyClassComponent;
-
2. 狀態管理
-
Function Component:
-
早期無內建狀態管理,需依賴 React Hooks(如 useState、useEffect)來管理狀態和副作用。
-
範例:
import React, { useState } from "react";
const MyFunctionComponent = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>計數: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
};
export default MyFunctionComponent;
-
-
Class Component:
-
使用 this.state 和 this.setState 管理狀態。
-
範例:
import React, { Component } from "react";
class MyClassComponent extends Component {
state = {
count: 0,
};
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>計數: {this.state.count}</p>
<button onClick={this.handleClick}>增加</button>
</div>
);
}
}
export default MyClassComponent;
-
3. this 關鍵字
-
Function Component:
-
無 this,因為它是純函數,直接透過參數(如 props)或 Hooks 存取資料。
-
避免了 this 綁定問題,程式碼更直觀。
-
-
Class Component:
-
使用 this 存取 props、state 和方法。
-
需要注意 this 的綁定問題,特別在事件處理函數中,可能需要手動綁定(如在 constructor 中或使用箭頭函數)。
-
範例(綁定問題):
class MyClassComponent extends Component {
handleClick() {
console.log(this); // 若未綁定,this 會是 undefined
}
render() {
return <button onClick={this.handleClick}>點擊</button>;
}
}解決方式:
class MyClassComponent extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this); // 綁定 this
}
handleClick() {
console.log(this); // 正確指向元件實例
}
render() {
return <button onClick={this.handleClick}>點擊</button>;
}
}
-
4. 生命週期
-
Function Component:
-
無傳統的生命週期方法,使用 useEffect Hook 來處理類似 componentDidMount、componentDidUpdate 和 componentWillUnmount 的邏輯。
-
範例:
import React, { useEffect } from "react";
const MyFunctionComponent = () => {
useEffect(() => {
console.log("元件掛載");
return () => console.log("元件卸載");
}, []); // 空依賴陣列類似 componentDidMount
return <div>函數元件</div>;
};
export default MyFunctionComponent;
-
-
Class Component:
-
提供明確的生命週期方法,如 componentDidMount、componentDidUpdate、componentWillUnmount 等。
-
範例:
import React, { Component } from "react";
class MyClassComponent extends Component {
componentDidMount() {
console.log("元件掛載");
}
componentWillUnmount() {
console.log("元件卸載");
}
render() {
return <div>類別元件</div>;
}
}
export default MyClassComponent;
-
5. 效能與簡潔性
-
Function Component:
-
通常更輕量,無需繼承 React.Component,減少記憶體開銷。
-
配合 Hooks,程式碼更簡潔且易於組合。
-
-
Class Component:
-
程式碼較冗長,包含更多樣板代碼。
-
在複雜邏輯下,可能需要更多手動管理(如 this 綁定)。
-
6. 現代 React 的趨勢
-
React 官方自 Hooks 推出後(React 16.8),強烈推薦使用 Function Component,因為它更簡潔、靈活,且易於測試和重用。
-
Class Component 仍被支援,但逐漸被取代,特別在新的專案中。
Class Component 的 this.props
在非同步事件中的存取陷阱
在 Class Component 中,this.props 在非同步事件(如 setTimeout、API 呼叫或事件處理)中可能會遇到存取問題,主要是因為 JavaScript 的閉包 和 this 綁定 的行為。以下是詳細說明與操作步驟:
問題描述
當在非同步操作中存取 this.props
,由於 this 的上下文可能未正確綁定,或者 props 在非同步執行時可能已經改變,導致存取到過期的或不正確的 props 值。
範例:非同步事件中的陷阱
以下是一個常見的陷阱範例:
import React, { Component } from "react";
class MyClassComponent extends Component {
handleClick() {
setTimeout(() => {
console.log(this.props.title); // 可能為 undefined 或過期值
}, 1000);
}
render() {
return (
<div>
<h1>{this.props.title}</h1>
<button onClick={this.handleClick}>點擊</button>
</div>
);
}
}
export default MyClassComponent;
問題:
-
如果 handleClick 未正確綁定 this,則 this 在 setTimeout 內會變成 undefined,導致
this.props
無法存取。 -
即使綁定了 this,如果
props.title
在 setTimeout 執行前被父元件更新,setTimeout 內可能存取到舊的 props 值。
解決方法
-
綁定 this:
-
在 constructor 中綁定事件處理函數,或使用箭頭函數。
-
範例:
import React, { Component } from "react";
class MyClassComponent extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this); // 綁定 this
}
handleClick() {
setTimeout(() => {
console.log(this.props.title); // 正確存取 props
}, 1000);
}
render() {
return (
<div>
<h1>{this.props.title}</h1>
<button onClick={this.handleClick}>點擊</button>
</div>
);
}
}
export default MyClassComponent; -
或者使用箭頭函數:
import React, { Component } from "react";
class MyClassComponent extends Component {
handleClick = () => {
setTimeout(() => {
console.log(this.props.title); // 正確存取 props
}, 1000);
};
render() {
return (
<div>
<h1>{this.props.title}</h1>
<button onClick={this.handleClick}>點擊</button>
</div>
);
}
}
export default MyClassComponent;
-
-
儲存當前 props:
-
在非同步操作開始前,將需要的 props 值儲存到變數中,避免存取過期的 props。
-
範例:
import React, { Component } from "react";
class MyClassComponent extends Component {
handleClick = () => {
const currentTitle = this.props.title; // 儲存當前 props
setTimeout(() => {
console.log(currentTitle); // 使用儲存的 props 值
}, 1000);
};
render() {
return (
<div>
<h1>{this.props.title}</h1>
<button onClick={this.handleClick}>點擊</button>
</div>
);
}
}
export default MyClassComponent;
-
操作步驟
-
確認事件處理函數是否需要綁定 this(檢查是否有非同步操作)。
-
若需要,使用 constructor 綁定或改用箭頭函數。
-
若擔心 props 在非同步操作中改變,儲存當前 props 到變數。
-
測試非同步操作,確保存取的 props 是預期值。
Function Component 會自動「捕捉」render 時的資料
在 Function Component 中,由於其函數性質和 React Hooks 的閉包行為,元件會在每次渲染時「捕捉」當下的 props 和 state 值,這與 Class Component 的行為不同。以下是詳細說明:
問題描述
-
在 Function Component 中,props 和 state 會在每次渲染時形成一個閉包,確保非同步操作(如 setTimeout 或 API 呼叫)存取的是該次渲染時的資料,而不是最新的資料。
-
這種行為被稱為「捕捉」(capture)渲染時的資料,對於初學者來說可能會感到意外。
範例:Function Component 的閉包行為
import React, { useState, useEffect } from "react";
const MyFunctionComponent = ({ title }) => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`當前 title: ${title}, count: ${count}`); // 捕捉渲染時的值
}, [title, count]);
const handleClick = () => {
setTimeout(() => {
console.log(`setTimeout 中的 title: ${title}, count: ${count}`); // 捕捉渲染時的值
}, 1000);
setCount(count + 1); // 更新 state
};
return (
<div>
<h1>{title}</h1>
<p>計數: {count}</p>
<button onClick={handleClick}>點擊</button>
</div>
);
};
export default MyFunctionComponent;
說明:
-
每次點擊按鈕,setTimeout 會捕捉當次渲染的 title 和 count 值,而不是最新的值。
-
例如,若 count 從 0 變為 1,setTimeout 內仍會記錄當次渲染的 count(例如 0)。
如何處理捕捉行為
若需要在非同步操作中使用最新的 props 或 state,可以使用以下方法:
-
使用 useRef 儲存最新值:
-
useRef 可以保存一個可變的對象,其值在元件生命週期內保持不變。
-
範例:
import React, { useState, useEffect, useRef } from "react";
const MyFunctionComponent = ({ title }) => {
const [count, setCount] = useState(0);
const latestTitle = useRef(title); // 儲存最新的 title
useEffect(() => {
latestTitle.current = title; // 更新最新值
}, [title]);
const handleClick = () => {
setTimeout(() => {
console.log(`setTimeout 中的最新 title: ${latestTitle.current}`); // 使用最新值
}, 1000);
setCount(count + 1);
};
return (
<div>
<h1>{title}</h1>
<p>計數: {count}</p>
<button onClick={handleClick}>點擊</button>
</div>
);
};
export default MyFunctionComponent;
-
-
依賴 useEffect 更新非同步邏輯:
-
將非同步邏輯放入 useEffect,確保依賴最新的 props 或 state。
-
範例:
import React, { useState, useEffect } from "react";
const MyFunctionComponent = ({ title }) => {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
console.log(`setTimeout 中的 title: ${title}, count: ${count}`); // 使用最新值
}, 1000);
return () => clearTimeout(timer); // 清理 timer
}, [title, count]);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<h1>{title}</h1>
<p>計數: {count}</p>
<button onClick={handleClick}>點擊</button>
</div>
);
};
export default MyFunctionComponent;
-
總結
-
Function Component 語法簡潔,無 this 問題,適合現代 React 開發,推薦使用。
-
Class Component 的 this.props 在非同步事件中需注意 this 綁定和過期值的問題,可透過綁定或儲存當前值解決。
-
Function Component 會自動捕捉渲染時的 props 和 state,若需最新值,需使用 useRef 或 useEffect。