Skip to main content

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 值。

解決方法

  1. 綁定 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;
  2. 儲存當前 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;

操作步驟

  1. 確認事件處理函數是否需要綁定 this(檢查是否有非同步操作)。

  2. 若需要,使用 constructor 綁定或改用箭頭函數。

  3. 若擔心 props 在非同步操作中改變,儲存當前 props 到變數。

  4. 測試非同步操作,確保存取的 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,可以使用以下方法:

  1. 使用 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;
  2. 依賴 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。