本篇內容參考自 Overreacted 網站的 “How Are Function Components Different from Classes?”。
現在幾乎所有 React 新專案都用函式元件(Function Component),好像 class 元件逐漸被淘汰。但到底這兩者之間,真正的差別是什麼?
今天我們來聊聊 Overreacted 那篇文章提到的一個關鍵差異:函式元件的 closure 捕捉 vs class 的 this 可變性。
class
宣告,有 this.state
、this.props
,方法通常讀取 this
上的值。useState
、useEffect
等 Hook 管理狀態與副作用。這篇 Overreacted 的觀點是:真正的差異在於 行為模型 (behavior model),特別是在 callback 或延遲執行時,函式元件的 closure 捕捉/class 的 this 更新方式的不同。
this
可變性在 Function component 裡,每一次 render,useEffect 裡的 callback function 都會被重新建立,而它會閉包捕捉當下的 state
/ props
。
假如這個 callback 被延遲執行(如 setTimeout
、事件裡),它會用當時被捕捉的值,即使後來 state
/ props
有變,也不會去拿最新的。
在 Class component 裡,callback 通常用 this.state
或 this.props
,而 this
是 mutable(可變的),會隨著每次 render 更新。
所以 callback 被執行時,讀到的就是最新的 this.state
/ this.props
。
這就是兩者在「非同步 / 延遲行為」下會有不同行為的原因。
Class 版本:
class Counter extends React.Component {
state = { count: 0 };
componentDidMount() {
setTimeout(() => {
console.log("class count:", this.state.count);
}, 1000);
}
render() {
return <button onClick={() => this.setState({ count: this.state.count + 1 })}>
Count: {this.state.count}
</button>;
}
}
setTimeout
執行前按了一下按鈕,class 版本會在 log 裡拿到最新的 this.state.count
。Function 版本:
function CounterFn() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setTimeout(() => {
console.log("fn count:", count);
}, 1000);
}, [count]);
return <button onClick={() => setCount(count + 1)}>
Count: {count}
</button>;
}
setTimeout
裡的 callback 捕捉到了那次 render 的 count
。即便後來 count
更新,這個 callback 還是印出最初那次捕捉的數字。寫一個 unction component,有一個按鈕點擊會 count +1
,同時 setTimeout
在 2 秒後印出 count
。
問:印出什麼值?為什麼?怎麼改寫,才能印最新的 count
?
在 React 中,Function component和 Class component 最大的差異在於它們如何取得 state 和 props。
在 Function component裡,callback 會「閉包(closure)」它定義當下那一次 render 的 state/props。
所以如果這個 callback 被延遲執行(例如在setTimeout
、事件、或 Promise 之後才跑),它讀到的值可能是舊的(stale >value)。在 Class component裡,callback 通常是透過
this.state
/this.props
來存取。
因為this
永遠指向同一個實例,this.state
和this.props
在每次更新後都會被 React 換成最新的,所以 >callback 幾乎總是能讀到最新的值。
Function components differ from class components mainly by how they capture state and props.
In function components, callbacks “closure” the state/props from the render in which they were defined, so delayed callbacks can see stale values.
In class components, callbacks refer tothis.state
/this.props
, which always reflect the latest values.