はじめに
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 で型・必須チェックを行う例も多い
- デフォルトProps:
MyComponent.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コンポーネントを実装しましょう。
コメント