[React] よーし! いっちょReactやってみっか! #8 クラス型コンポーネント編

2020.10.12

はじめに

CX事業本部の中安です。まいどです。

モバイルアプリエンジニアな自分がReactを始めてみることにした 「よーし! いっちょReactやってみっか!」シリーズの続きです。 今回もよろしくお願いします。

前回は、関数コンポーネントでHooksを使ってステートを扱う基本的な書き方と動き、ルールなどを書きました。

今回はクラスコンポーネントの作り方を書こうと思います。

Reactのクラスコンポーネント

基本キーワード編でも触れましたが、Reactでは長らくクラスによるコンポーネントが主流だったとのことでした。

大きな理由としては、関数コンポーネントではHooksという仕組みがなく、ステートなどを使用することができなかったからだそうです。 関数コンポーネントは状態を持たない小さなコンポーネントのために使われることがメインだったのでしょう。

しかし、Hooksの登場により関数コンポーネントとクラスコンポーネントの立場は入れ替わったといいます。

関数コンポーネントがクラスコンポーネントと同等の機能を有し、さらにシンプルに書けるということであれば、 コンポーネントを作る際には関数コンポーネントを採用していく前提であるほうがいいでしょうね。

公式ドキュメントでも

フック、クラスのいずれを使うべきですか、あるいはその両方でしょうか?

準備ができしだい、新しいコンポーネントでフックを試すことをお勧めします。チームの全員の準備が完了し、このドキュメントに馴染んでいることを確かめましょう。(例えばバグを直すなどの理由で)何にせよ書き換える予定の場合を除いては、既存のクラスをフックに書き換えることはお勧めしません。

クラスコンポーネントの定義内でフックを使うことはできませんが、クラス型コンポーネントとフックを使った関数コンポーネントとを 1 つのコンポーネントツリー内で混在させることは全く問題ありません。あるコンポーネントがクラスで書かれているかフックを用いた関数で書かれているかというのは、そのコンポーネントの実装の詳細です。長期的には、フックが React のコンポーネントを書く際の第一選択となることを期待しています。

と、クラスコンポーネントが関数コンポーネントに取って代わられる未来を描いてるようです。

しかしながら、いくつかの理由で未だクラスコンポーネントを触ることもあるかもしれません。 たとえば以下のような理由です。

  • すでに既存のReactアプリでクラスコンポーネントが使われている。(「既存のクラスをフックに書き換えることはお勧めしません」と上記の引用にも書かれています)
  • いくつかのクラス特有の機能がHooksには搭載されていない。(近く追加されるとは書かれています)
  • いくつかのサードパーティ製のライブラリは現時点でフックとの互換性がない。

そういう意味ではクラスコンポーネントにまったく無知識でいる必要もないのかもしれません。

今シリーズで積極的にクラスコンポーネントを扱う予定はないですが、今回はクラスコンポーネントに絞ってまとめていきたいと思います。

クラスコンポーネントを定義する

前回作ったコンポーネント

まず前回のおさらいですが、関数コンポーネントで「フォームに入力した文字に挨拶をしてくれる」というHelloコンポーネントを作りました。

const Hello = (props) => {
    const [name, setName] = useState("World");
    return (
        <Card>
            <Card.Body>
                <h1>Hello {name}!</h1>
                <Form.Control 
                    value={name} 
                    onChange={(e) => setName(e.target.value)} 
                />
            </Card.Body>
        </Card>
    )
}

これをクラス型コンポーネントに置き換えてみますか。

定義

クラスコンポーネントとは、React.Componentを継承させたクラスのことを言います。

なので、Helloコンポーネントはこのような定義となります。

class Hello extends React.Component {

}

余談ですが、昔にWEBアプリやってて最近はモバイルアプリばかり開発してた人間なので、 いつJavaScriptにクラスという概念ができたのか把握していませんでした。

ECMAScript2015で採用されたという風に書かれていたので、結構最近(最近と言っていいのか微妙ですが)なのですね。

以前は「プロトタイプ」という概念でやってたような・・・という記憶で調べてみたのですが、 JavaScriptの「クラス」は「プロトタイプ」の糖衣構文(シンタックスシュガー = 他の方法で書き直すことができる構文)と書かれていたので、 なるほど奥底では以前と変わってないんだなぁと腹落ちしました。(脱線すいません)

必須メソッド render

React.Componentを継承させたコンポーネントクラスは、1つだけ実装しなければならないメソッドがあります。 render()というメソッドです。

render()メソッドはコンポーネントの構成を主にJSXで返すメソッドになります。 今回では、関数コンポーネントでのreturn部分がほぼそのまま使えると思います。

ただしHooksはクラスでは使えないので外しておいて、このように書いてみました。

class Hello extends React.Component {
    
    render() {
        return (
            <Card>
                <Card.Body>
                    <h1>Hello {name}!</h1>
                    <Form.Control 
                        value={name} 
                        onChange={(e) => setName(e.target.value)} 
                    />
                </Card.Body>
            </Card>
        )
    }
}

これは関数コンポーネントからそのまま移してきたものなので、当然ですがこのままではエラーになります。 ステートについて何も書いてないからです。

コンストラクタ

クラスにはコンストラクタ(生成された時に最初に必ず通るメソッド)を設置することができます。 ここでステートの設定を行うのがセオリーです。

class Hello extends React.Component {
    
    constructor(props) {
        super(props);
    }

React.Componentのコンストラクタではpropsが引数として渡されてきます。 こちらは後でつかうことにしましょう。

1行目にsuper(props);というふうに継承元のコンストラクタを最初に呼び出します。

ステートの初期設定

コンストラクタ内でステートの初期設定を行います。 Hooksの時とは書き方が変わります。

class Hello extends React.Component {
    
    constructor(props) {
        super(props);
        this.state = {
            name: "World"
        };
    }

このようにthis.stateにオブジェクトを渡す形になります。

const Hello = (props) => {
    const [name, setName] = useState("World");

↑関数コンポーネントと比べるとだいぶ違いますね。

ステートの値

render()側も変更しないといけません。 関数コンポーネントではnameという変数でアクセスできましたが、 クラスの場合はコンストラクタで指定したthis.stateのオブジェクトにアクセスすることになります。

render() {
    return (
        <Card>
            <Card.Body>
                <h1>Hello {this.state.name}!</h1>
                <Form.Control 
                    value={this.state.name} 
                    onChange={(e) => setName(e.target.value)} 
                />
            </Card.Body>
        </Card>
    )
}

ステートの更新

現時点でもまだ動かないと思います。なぜならsetName()というものが存在しないからです。 クラスコンポーネントでは代わりにsetState()というものに統一されます。 setState()ではコンストラクタと同じようにオブジェクトで渡すことになります。

render() {
    return (
        <Card>
            <Card.Body>
                <h1>Hello {this.state.name}!</h1>
                <Form.Control 
                    value={this.state.name} 
                    onChange={(e) => this.setState({
                        name: e.target.value
                    })} 
                />
            </Card.Body>
        </Card>
    )
}

これでブラウザで見てみると動くかと思います。

関数コンポーネントの内容がクラスコンポーネントに書き換えれましたね。

差分更新

関数コンポーネントのステート基本編では、下記のようにオブジェクトを渡す方法も書きました。

const Hello = (props) => {
    const [person, setPerson] = useState({
        name: "",
        email: "",
        address: "",
        age: 0
    });

この場合はsetPerson()ではnameを更新する際には、他のキーの値も合わせて変更する必要があることも書きました。

クラスコンポーネントのsetState()も同じようにしなければならないのでしょうか。

いえ。そんなことありません。 setState()変更しようとした値だけが差分更新される仕組みになっています。

なので、コンストラクタで

class Hello extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            name: "World",
            email: "",
            address: "",
            age: 0
        };
    }

このように定義したとしても、変わらずに

<Form.Control 
    value={this.state.name} 
    onChange={(e) => this.setState({
        name: e.target.value
    })} 
/>

とすることで、nameだけが更新され、それ以外の値には影響は出ません。

最後に

ということで、前回のステートを用いた関数コンポーネントをクラスコンポーネントに書き換えながら、 クラスコンポーネントの実装方法をまとめてみました。

冒頭の話の通り、基本的には関数コンポーネントの方が主流になるので、 クラスコンポーネントはこれくらいにしておきます。 また何か必要そうな話が出てきたらブログにまとめます。

では、またー。