React で独自コンポーネントを作成する方法として、大きく次の 2 種類の方法があります。
- 関数コンポーネント (Function Components)
- クラスコンポーネント (Class Components)
昔は、ステートを持つコンポーネントは「クラスコンポーネント」で作成し、ステートを持たないものは「関数コンポーネント」として作成するという使い分けがありました。 現在は、関数コンポーネントでも Hook の仕組みでステートを管理することができるようになったため、関数コンポーネントの使用が推奨されています。
関数コンポーネント
関数コンポーネントの基本
下記は、固定のテキストを表示するシンプルな関数コンポーネントの定義例です。
TypeScript (@types/react
) では、関数コンポーネントの型は React.FunctionComponent インタフェースとして定義されています。
エイリアスとして React.FC
が定義されているので、こちらを使えばより短く記述できます。
import * as React from 'react';
// Hello コンポーネントの定義
export const Hello: React.FC = () => {
return <h1>Hello</h1>
};
HTML ファイルから読み込む JS ファイルでは、ReactDOM.render()
で上記の Hello
コンポーネントを描画します。
次のコードを実行すると、<div id="root">
要素の内容が、Hello
コンポーネントの内容に置き換えられます。
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Hello } from './components/Hello';
ReactDOM.render(<Hello />, document.getElementById('root'));
一見すると、1 行目の React
モジュールのインポートは必要ないように見えますが、JSX コードが変換されると React
を参照するコードになるので、この行を消してはいけません。
プロパティを定義する (props)
関数のパラメータとして、表示すべき値(プロパティ: props
)を受け取ることができます。
このプロパティには、HTML 要素でいうところの「属性」として指定された値が格納されています。
TypeScript を使っているのであれば、プロパティの型をちゃんと定義して、React.FC
の型パラメータとして指定します。
次の例では、2 つのプロパティ(name
と age
)を受け取る関数コンポーネントを定義しています。
import * as React from 'react';
// Hello コンポーネントのプロパティ
type Props = {
name: string;
age: number;
};
// Hello コンポーネントの定義
export const Hello: React.FC<Props> = (props) => {
return <h1>私は{props.name}です {props.age}歳です</h1>
};
ReactDOM.render(
<Hello name="まく" age={14} />,
document.getElementById('root')
);
私はまくです 14歳です
と表示されれば成功です。
プロパティをオプショナルにする
あるプロパティをオプションにしたいときは、TypeScript の型定義をするときに、プロパティ名の末尾に ?
を付けます。
次の例では、age
プロパティの指定をオプショナルにしています。
import * as React from 'react';
// Hello コンポーネントのプロパティ
type Props = {
name: string;
age?: number;
};
// Hello コンポーネントの定義
export const Hello: React.FC<Props> = (props) => {
const ageText = props.age ? `${props.age}歳` : '秘密';
return <h1>私は{props.name}です 年齢は{ageText}です</h1>
};
プロパティのデフォルト値を設定する
React.FC
の deafultProps
で、プロパティのデフォルト値を定義しておくことができます。
次の例では、Hello
コンポーネントの name
と age
をオプショナルプロパティとして定義し、それぞれ指定されなかった場合のデフォルト値を設定しています。
import * as React from 'react';
type Props = {
name?: string;
age?: number;
};
export const Hello: React.FC<Props> = (props) => {
return <h1>私は{props.name}です {props.age}歳です</h1>
};
// プロパティのデフォルト値
Hello.defaultProps = {name: '名無し', age: 0};
実は、defaultProps
を使わずに、次のように分割代入の構文を使って、デフォルト値を設定してしまった方がシンプルです。
export const Hello: React.FC<Props> = (props) => {
const {name = '名無し', age = 0} = props;
return <h1>私は{name}です {age}歳です</h1>
};
TypeScript の Nullish coalescing operator (??
) を使ってデフォルト値を設定してしまう方法もあります。
分割代入の構文に慣れていないうちはこっちのほうが直感的かもしれません。
export const Hello: React.FC<Props> = (props) => {
const name = props.name ?? '名無し';
const age = props.age ?? 0;
return <h1>私は{name}です {age}歳です</h1>
};
(おまけ)props の型定義を簡略化する
次の例では、1 つの文字列を持つ Props
型を定義し、その型で受け取った値を分割代入によって name
変数に格納しています。
type Props = { name: string; };
export const Hello: React.FC<Props> = (props) => {
const {name} = props;
return <h1>私は{name}です</h1>
};
これくらいシンプルな Props
であれば、React.FC
の型パラメータ部分にインライン記述してしまうことも可能です。
次の例では、name
変数への分割代入も、アロー関数のパラメータ部分で行っています。
export const Hello: React.FC<{name: string}> = ({name}) => {
return <h1>私は{name}です</h1>;
};
オプショナルな props
を定義して、デフォルト値を設定する場合も同様に記述できます。
export const Hello: React.FC<{name?: string}> = ({name = '名無し'}) => {
return <h1>私は{name}です</h1>;
};
クラスコンポーネント
クラスコンポーネントの基本
React コンポーネントをクラスの形で定義することもできます。
TypeScript であれば、React.Component を継承し、render()
メソッドを実装します。
コンポーネントにプロパティを持たせることができるのは、関数コンポーネントの場合と同様です。
import * as React from 'react';
type Props = {
name: string;
};
export class Hello extends React.Component<Props> {
public render() : JSX.Element {
return <h1>私は{this.props.name}です</h1>
}
};
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Hello } from './components/Hello';
ReactDOM.render(<Hello name="まく" />, document.getElementById('root'));
注意点として、関数コンポーネントのときに関数のパラメータで受け取っていたプロパティ (props
) は、クラスコンポーネントの場合はメンバ変数(こちらをプロパティと呼ぶと紛らわしい^^;)として参照するというところです。
つまり、this
を付けて、this.props.プロパティ名
という形で参照する必要があります。
プロパティのデフォルト値を設定する
クラスコンポーネントのオプショナルなプロパティにデフォルト値を設定するには、次のように static な defaultPorps
を定義します。
import * as React from 'react';
type Props = {
name?: string;
};
export class Hello extends React.Component<Props> {
private static defaultProps: Props = {
name: '名無し'
}
public render() : JSX.Element {
return <h1>私は{this.props.name}です</h1>
}
};
プロパティの一部だけがオプショナルになっている場合は、defaultProps
の型は HelloProps
ではなく、ES5 の Partial
を使って Partial<HelloProps>
のように定義する必要があります(こうしないと、必須プロパティの name
が指定されていないと言うエラーになります)。
次の例では、プロパティ age
だけをオプショナルにし、そのデフォルト値を 0 に設定しています。
import * as React from 'react';
type Props = {
name: string;
age?: number;
};
export class Hello extends React.Component<Props> {
private static defaultProps: Partial<Props> = {
age: 0
}
public render() : JSX.Element {
return <h1>私は{this.props.name}です {this.props.age}歳です</h1>
}
};
と、ここまで defaultProps
を使う方法を説明しましたが、実は次のようにプロパティの値を見て条件分岐してしまった方が簡単です。
public render() : JSX.Element {
const name = this.props.name;
const age = this.props.age ?? 0;
// const {name, age = 0} = this.props;
return <h1>私は{name}です {age}歳です</h1>
}
状態を持たせる (state)
クラスコンポーネントには、状態を持たせることができ、その値を使って描画内容を動的に変更することができます。
状態の型は、React.Component
の 2 番目の型パラメータとして指定します(1 番目はプロパティの型)。
現在の状態は、インスタンスプロパティ state
によって管理します。
state
の値は、コンストラクタで初期化できます。
次の例では、ボタンを押すごとにカウントアップする CountButton
コンポーネントを定義しています。

import * as React from 'react';
// CountButton コンポーネントの状態の型
type CountButtonState = {
count: number;
};
// CountButton コンポーネントの定義
export class CountButton extends React.Component<{}, CountButtonState> {
constructor(props: {}) {
super(props);
this.state = { count: 0 }; // 状態を初期化
}
public render(): JSX.Element {
return (
<div>
<button onClick={this.handleClick}>増やす</button>
<b>カウント = {this.state.count}</b>
</div>
);
}
// ボタンが押されたときの処理
private handleClick = (e: React.SyntheticEvent) => {
// デフォルト動作を抑制したい場合
e.preventDefault();
// コンポーネントの状態を変更する → 新しい状態で render() が実行される
this.setState({
count: this.state.count + 1
});
}
}
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { CountButton } from './components/CountButton';
ReactDOM.render(<CountButton />, document.getElementById('root'));
いくつか実装上のポイントがあるので、まとめておきます。
- コンストラクタで props を受け取る:
クラスコンポーネントのコンストラクタを定義するときは、パラメータとしてプロパティ (
props
) を受け取り、親クラス (super
) に渡してやる必要があります。上記のCountButton
コンポーネントはプロパティを持たないので、型パラメータとしては{}
を指定していますが、その場合でもコンストラクタのパラメータではprops
を受け取るように実装しておく必要があります。 - render 関数の return 直後に開き括弧:
return
の直後に改行を入れると、JavaScript が自動的にセミコロンを挿入してしまうので、すぐに改行したいときはreturn (
のように、開き括弧の後ろで改行する必要があります。 - onClick と handleClick:
JSX コードの中で配置したボタンのクリックイベントをハンドルするには、
onClick
という属性にイベントハンドラを指定します。イベントハンドラ名はhandleClick
のように、名前にhandle
というプレフィックスを付けるのが慣例となっています。 - イベントハンドラはアロー関数で:
this
で呼び出し元のオブジェクトを参照できるように、handleClick
メソッドはfunction
キーワードを使わずに、アロー関数の形で定義します。 - デフォルト動作を抑止する:
button
要素などのクリック時のデフォルト動作(フォーム内容の post など)を抑制するには、イベントハンドラのパラメータで渡される React.SyntheticEvent オブジェクトのpreventDefault()
メソッドを呼び出します。 - 状態の変更は setState 関数で:
state
プロパティはリードオンリーになっているので、コンポーネントの状態を変更したいときは、setState()
関数を使用します(コンストラクタでは例外的に代入できます)。setState()
を使って状態を変更すると、自動的にrender()
メソッドが呼び出され、新しい状態 (state
) で画面上の表示が更新されます。