React.Component徹底ガイド──クラスコンポーネントの基本からライフサイクル・State管理・最適化まで

React

はじめに

Reactのコンポーネントは関数コンポーネントが主流になっていますが、クラスベースのReact.Componentも依然として理解しておく価値があります。特に既存の大規模プロジェクトやError Boundary(エラーバウンダリ)実装にはクラスコンポーネントが欠かせません。提供する機能を1つずつ丁寧に見ていきましょう。

React.Componentとは

React.Component は、ユーザー定義のクラスコンポーネントが継承する基底クラスです。内部では以下を提供します。

  • props:親から渡される読み取り専用データ
  • state:コンポーネント内部で変化する値
  • render():UIの描画を司る必須メソッド
  • ライフサイクルメソッド:マウント・更新・アンマウント時のフック
  • setState()state更新と再レンダーのトリガー
  • エラーバウンダリstatic getDerivedStateFromError / componentDidCatch
import React from 'react';

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  render() {
    return <div>カウント: {this.state.count}</div>;
  }
}

propsの扱い

this.props には親から渡された全ての値が含まれています。コンポーネント内で変更してはいけません。

class Greeting extends React.Component {
  render() {
    return <h1>こんにちは、{this.props.name}さん!</h1>;
  }
}

// 呼び出し側
<Greeting name="太郎" />
  • 型注釈:PropTypes で型・必須チェックを行う例も多い
  • デフォルトPropsMyComponent.defaultProps = { count: 10 };

stateとsetState

state はコンポーネントの内部情報を保持し、setState で更新します。setState非同期バッチ処理されるため注意が必要です。

class Counter extends React.Component {
  state = { count: 0 };

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
    // 複数回連続呼び出すと反映タイミングがずれる可能性あり
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>;
  }
}
  • 関数型更新:前回のstateを確実に利用したい場合
this.setState(prev => ({ count: prev.count + 1 }));
  • マージ:オブジェクトをマージする形で更新される

ライフサイクルメソッド一覧

クラスコンポーネントは以下のメソッドをオーバーライドできます。

フェーズメソッド説明
マウント前static getDerivedStateFromProps(props, state)Propsからstateを生成(副作用禁止)
マウント後componentDidMount()API呼び出しやDOM操作を実行
更新判定shouldComponentUpdate(nextProps, nextState)レンダーをスキップする判断
更新前getSnapshotBeforeUpdate(prevProps, prevState)更新前のDOM情報を取得(返却値は次のcomponentDidUpdateに渡る)
更新後componentDidUpdate(prevProps, prevState, snapshot)更新後に副作用を実行
アンマウント前componentWillUnmount()タイマー解除、購読解除などのクリーンアップ
エラー処理static getDerivedStateFromError(error)エラー発生時にstateを更新
componentDidCatch(error, info)UIエラーのログ送信など

例:データフェッチ

class DataFetcher extends React.Component {
  state = { data: null };

  componentDidMount() {
    fetch(this.props.url)
      .then(r => r.json())
      .then(json => this.setState({ data: json }));
  }

  render() {
    return this.state.data
      ? <div>{this.state.data.title}</div>
      : <p>読み込み中…</p>;
  }
}

Error Boundary(エラーバウンダリ)

クラスコンポーネントのみ実装可能な仕組みで、子コンポーネントでのレンダーエラーをキャッチできます。

class ErrorBoundary extends React.Component {
  state = { hasError: false };

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

  componentDidCatch(error, info) {
    logErrorToService(error, info);
  }

  render() {
    if (this.state.hasError) {
      return <h2>エラーが発生しました</h2>;
    }
    return this.props.children;
  }
}
  • 再起動:Error Boundaryをリセットするロジックを外部から制御可能
  • 注意:副作用フックの代替ではない

コンテキストとの連携

static contextType = MyContext をクラスに指定すると this.context で値を参照できます。

MyComponent.contextType = ThemeContext;
class MyComponent extends React.Component {
  render() {
    return <div style={{ color: this.context.primary }}></div>;
  }
}

パフォーマンス最適化

shouldComponentUpdate

再レンダーを制御し、不要なrender()呼び出しを抑えます。

class PureCounter extends React.Component {
  shouldComponentUpdate(nextProps) {
    return nextProps.value !== this.props.value;
  }
  render() { /* … */ }
}

PureComponent

React.PureComponent を継承すると自動的に shallow 比較で shouldComponentUpdate を実装します。

メソッドのバインディング

this の参照を安定させるため、コンストラクタでバインディングするか、クラスフィールド構文を使います。

// コンストラクタ内で
constructor(props) {
  super(props);
  this.handleClick = this.handleClick.bind(this);
}

// クラスフィールド
handleClick = () => { /* this が正しい */ }

デフォルトPropsとdisplayName

  • defaultProps
MyComponent.defaultProps = { text: 'デフォルトテキスト' };
  • displayName
    • デバッグ時の表示名を変更できます。
MyComponent.displayName = 'カスタム名';

まとめ

React.Component を理解すると、既存のクラスコンポーネントやError Boundaryが読み書きできるようになります。ライフサイクルメソッド、state / props、パフォーマンス最適化パターンなどを活用し、堅牢で読みやすいUIコンポーネントを実装しましょう。

コメント