[React] よーし! いっちょReactやってみっか! #7 ステート基本編

2020.10.09

はじめに

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

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

前回は、コンポーネントにスタイルを当てる方法を書きました。 React Bootstrapnpmでインストールして使ってみるということもやりました。

今回は状態管理「ステート」の基本的なことをやってみようと思います。

ステートとは

あらためて「ステートって何」って話ですけれども

コンポーネントには、コンポーネント自身の操作可能な"状態"を保管する場所が設けられます。 この状態管理に使う値、または仕組みのことをステートと呼んでいます。

簡単にいうと「状態管理」とは、値が変わるとコンポーネントの見た目(状態)が変わるということですね。

基本キーワードを押さえる回では、 クラスコンポーネントではthis.state、関数コンポーネントではHooksを用いることにより、このステートを扱えるというところまで書きました。

実際にどのように扱うのか、そしてその結果どうなるのか、というところまでやってみたいと思います。

関数コンポーネントの準備

このシリーズではまだクラスコンポーネントについて詳しく書いていません。 クラスコンポーネントの作り方については別途ブログにまとめようと思うので、今回は関数コンポーネントでHooksを使った方法に絞ろうと思います。

では、あらためてHelloという関数コンポーネントを作ります。

前回React Bootstrapのコンポーネントを使うところまでやったので、今回も同じように使っていきます。

import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import Card from 'react-bootstrap/Card';

const Hello = (props) => {
    return (
        <Card>
            <Card.Body>
                <h1>Hello World!</h1>
            </Card.Body>
        </Card>
    )
}

let dom = document.getElementById('root');
ReactDOM.render(
    <Hello />,
dom);

まずは "Hello World!" が表示されるコンポーネントができたと思います。

フォームを設置

今回はここに入力フォームを設置します。 React Bootstrapが用意しているFormを使うことにします。

import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import Card from 'react-bootstrap/Card';
import Form from 'react-bootstrap/Form';

const Hello = (props) => {
    return (
        <Card>
            <Card.Body>
                <h1>Hello World!</h1>
                <Form.Control/>
            </Card.Body>
        </Card>
    )
}

ブラウザで見ると

こういう感じの見た目になりました。

作るもののイメージ

さて、現時点ではHelloコンポーネントはただ「Hello World!」を出すだけのコンポーネントです。 設置した入力フォームに入力した文字列に対して挨拶してくれるコンポーネントに作り変えたいと思います。

ここから「ステート」を使う話になります。

Hooks

関数コンポーネントでステートを扱うためにはHooksを使います。 基本キーワード編でもHooksは登場しましたが、 具体的な書き方としては今回が初登場です。

楽しそうなのでちょっとやってみましょうよ。

ステートフック

一言でHooksといっても実は何個も種類があります。 今回はその内の関数コンポーネントをステート値を単純に使う「ステートフック」という仕組みを使うことになります。

インポートする

まず、ステートフックを使用するためにはインポートを変更しなければいけません。 Reactをインポートしている行を下記のように書き換えます。

import React from 'react';
↓
import React, { useState } from 'react';

{}(ブレス)で囲まれたuseStateは、reactという読み込み元で定義されている「モジュール」に当たります。 各種「モジュール」を読み込みたい時はこのような書き方になります。 その他のインポート文がブレスで囲まれてないのは、読み込み元でデフォルト定義されているモジュールを使用しようとしているからです。

ステートフックを設定する

useStateが使えるようになったところでHelloコンポーネントにそれを取り入れてみます。

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

追記したのは2行目。returnの前です。

これがステートフックの基本的な定義の書き方になるそうです。 useStateを使うと、[](ブラケット)内にあるnamesetNameに「値」と「値を更新する(状態を変える)関数」が代入されます。 useStateに与えている引数はnameの初期値として扱われます。

ただの変数定義なのですが、実質的にはまるでクラスのgettersetterを定義しているようですね。(getterと言い切ってしまうと関数なのか?と誤解されてしまいそうですが・・・)

const [《ゲッタ(値)》, 《セッタ(関数)》] = useState(《初期値》);

命名規則は上記のようにnameという状態を扱いたい場合は、 左側は名詞そのもののname、 右側はsetを付けたキャメルケースでserNameというようにするのがセオリーのようです。

ステートの値を使う

では、定義したステートを使ってみるのですが、まずは値の方のnameをコンポーネントに配置していきます。

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

このように<h1>要素とフォームの値にnameを差し込みました。 JSX上で変数をブレスで囲むと値が反映されるようになるわけですね。

HTML上だと属性値はダブルクォートで囲む必要がありますが、JSXであると不要であるところを注意してください。

ブラウザで確認すると

このようになりました。

ステートの更新をする

次にsetNameを使ってみましょう。

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>
    )
}

このように書き換えました。

まずonChangeという属性ですが、これは「フォームの入力値が変わる度」というイベントになります。 なので、ここに渡すのはその時の処理をする関数を渡してやる必要があります。

JavaScriptを触ったことがある人にはお馴染みですが、イベントデータとして自動的に関数の引数に情報が渡されてきます。 これをeという変数で受け取っています。 これにより、入力フォームに入力された値をe.target.valueという形で取得することができますね。

その取得できた値をsetNameに渡しています。 ここが今回のミソになります。 こうすることでnameの値が更新されることになります。 しかも、リアルタイムにです。

ブラウザで確認します。

画像であると分かりづらいですが、実際にブラウザで試してみてください。 フォームに入力された値がすぐさま「Hello」のあとに反映されるようになったと思います。

「反応」という意味を持つReactの、このリアルタイム性がつまり状態操作ということですね。

ちなみに、このように属性が多くなってくると見づらくなってしまうので適宜改行したほうが良いですね。

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

値は直接変えれない

ステートの値は直接に変えてはいけないというルールがあります。 例えばonChangeの中身は下記のようには変更してはいけません。

(e) => setName(e.target.value)
↓
(e) => name = e.target.value

このようにnameに直接代入しようとどうなるでしょうか。 これは入力フォームの内容が変わった瞬間にエラーが表示されることになると思います。

constで宣言しているのでnameは定数扱いになるわけで、その値に代入しようとするからですね。

ですのでnameの値を変えたい時はsetNameを確実に使うようにしましょう。

複数のステート

もちろん、1つのコンポーネントに対して1つしかステートが持てないというわけではありません。 useStateはいくつも並べることができます。

const Hello = (props) => {
    const [name, setName] = useState("");
    const [email, setEmail] = useState("");
    const [address, setAddress] = useState("");
    const [age, setAge] = useState(0);
    :
    :

また、上記のように初期値には文字列や数値、ブール値などのプリミティブな値を渡すケースは多いかと思いますが、 オブジェクトも渡すこともできます。

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

ただし、下記のように更新時に 他のキーも一緒に更新してやらないとデータの整合性が取れなくなってしまいます。

<Form.Control 
    value={person.name} 
    onChange={(e) => setPerson({
        name: e.target.value,
        email: "",
        address: "",
        age: 0
    })} 
/>

「渡すことができる」けれども「渡すべきか」という使い所はちゃんと設計しないといけないですね。

フックのルール

公式ドキュメントには大事な「フックのルール」が記載さています。

トップレベル以外の箇所でフックを呼んではいけない

「トップレベル以外の箇所」というのは、ループや条件分岐やネストした関数の中のことです。

つまり

if (condition) {
    const [name, setName] = useState("World");
}

こういうことをしてはいけないということです。

フックは関数コンポーネントの内部のみで呼び出さなければならない

一部例外はありますが、フックを関数コンポーネント以外の箇所で使ってはいけません。

コード

ここまでのソースコードを一旦載せておきます。

import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import Card from 'react-bootstrap/Card';
import Form from 'react-bootstrap/Form';

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>
    )
}

let dom = document.getElementById('root');
ReactDOM.render(
    <Hello />,
dom);

最後に

というわけで、今回はステートについて基本的なところを書きました。 その方法として、関数コンポーネントでHooksを使用して実際にリアルタイムに反映されるUIを作ってみましたというところです。

次回はクラス型コンポーネントをちょっとやってみましょうか。

では、またー。