React Class Component

Class Component 是 React 早期的元件寫法。現代 React 開發推薦使用 Function Component + Hooks,但了解 Class Component 仍然有幫助,因為你可能會遇到舊的程式碼。

基本語法

import { Component } from 'react'

class Welcome extends Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

Class Component 需要:

  • 繼承 Component(或 React.Component
  • 實作 render() 方法

Props

在 Class Component 中,props 透過 this.props 存取:

class Greeting extends Component {
  render() {
    return (
      <div>
        <h1>Hello, {this.props.name}</h1>
        <p>Age: {this.props.age}</p>
      </div>
    )
  }
}

// 使用
;<Greeting name="Mike" age={25} />

預設 Props

class Button extends Component {
  render() {
    return <button className={this.props.color}>{this.props.text}</button>
  }
}

Button.defaultProps = {
  color: 'blue',
  text: 'Click me',
}

State

Class Component 使用 this.statethis.setState() 來管理狀態:

class Counter extends Component {
  constructor(props) {
    super(props)
    // 初始化 state
    this.state = {
      count: 0,
    }
  }

  handleClick = () => {
    // 更新 state
    this.setState({ count: this.state.count + 1 })
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}

setState 是非同步的

// ❌ 可能有問題
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 }) // 可能還是原來的 count

// ✅ 使用函式形式
this.setState((prevState) => ({ count: prevState.count + 1 }))
this.setState((prevState) => ({ count: prevState.count + 1 }))

setState 的 callback

this.setState({ count: this.state.count + 1 }, () => {
  // state 已經更新
  console.log('New count:', this.state.count)
})

事件處理與 this 綁定

在 Class Component 中,事件處理函式的 this 需要特別處理:

方法 1:在 constructor 中綁定

class Toggle extends Component {
  constructor(props) {
    super(props)
    this.state = { isOn: false }
    // 綁定 this
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState((prev) => ({ isOn: !prev.isOn }))
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.isOn ? 'ON' : 'OFF'}</button>
  }
}

方法 2:使用 Arrow Function(推薦)

class Toggle extends Component {
  state = { isOn: false }

  // Arrow function 自動綁定 this
  handleClick = () => {
    this.setState((prev) => ({ isOn: !prev.isOn }))
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.isOn ? 'ON' : 'OFF'}</button>
  }
}

Class Fields 語法

使用 Class Fields 可以簡化程式碼,不需要 constructor:

class Counter extends Component {
  // 直接定義 state
  state = {
    count: 0,
  }

  // Arrow function 作為方法
  increment = () => {
    this.setState((prev) => ({ count: prev.count + 1 }))
  }

  decrement = () => {
    this.setState((prev) => ({ count: prev.count - 1 }))
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.decrement}>-</button>
        <button onClick={this.increment}>+</button>
      </div>
    )
  }
}

Function Component vs Class Component

特性Function ComponentClass Component
語法函式類別
狀態useStatethis.state + this.setState
生命週期useEffect生命週期方法
this不需要需要處理 this 綁定
程式碼量較少較多
效能略好略差
推薦度✅ 推薦⚠️ 舊版寫法

將 Class Component 轉換為 Function Component

轉換前(Class)

class UserProfile extends Component {
  state = {
    user: null,
    loading: true,
  }

  componentDidMount() {
    this.fetchUser()
  }

  componentDidUpdate(prevProps) {
    if (prevProps.userId !== this.props.userId) {
      this.fetchUser()
    }
  }

  fetchUser = async () => {
    this.setState({ loading: true })
    const response = await fetch(`/api/users/${this.props.userId}`)
    const user = await response.json()
    this.setState({ user, loading: false })
  }

  render() {
    const { user, loading } = this.state

    if (loading) return <p>載入中...</p>
    return <h1>{user.name}</h1>
  }
}

轉換後(Function)

function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    async function fetchUser() {
      setLoading(true)
      const response = await fetch(`/api/users/${userId}`)
      const data = await response.json()
      setUser(data)
      setLoading(false)
    }

    fetchUser()
  }, [userId])

  if (loading) return <p>載入中...</p>
  return <h1>{user.name}</h1>
}

何時還需要 Class Component?

目前,唯一必須使用 Class Component 的情況是 錯誤邊界(Error Boundary),因為它需要使用 componentDidCatchgetDerivedStateFromError 這兩個生命週期方法,而這些目前沒有對應的 Hook。

class ErrorBoundary extends Component {
  state = { hasError: false }

  static getDerivedStateFromError(error) {
    return { hasError: true }
  }

  componentDidCatch(error, info) {
    console.error(error, info)
  }

  render() {
    if (this.state.hasError) {
      return <h1>發生錯誤</h1>
    }
    return this.props.children
  }
}