state:コンポーネントのメモリ
コンポーネントによっては、ユーザ操作の結果として画面上の表示内容を変更する必要があります。フォーム上でタイプすると入力欄が更新される、画像カルーセルで「次」をクリックすると表示される画像が変わる、「購入」をクリックすると買い物かごに商品が入る、といったものです。コンポーネントは、現在の入力値、現在の画像、ショッピングカートの状態といったものを「覚えておく」必要があります。React では、このようなコンポーネント固有のメモリのことを state と呼びます。
このページで学ぶこと
useState
を使って state 変数を追加する方法useState
フックが返す 2 つの値- 複数の state 変数を追加する方法
- state がローカルと呼ばれる理由
通常の変数ではうまくいかない例
以下は、彫刻の画像をレンダーするコンポーネントです。“Next” ボタンをクリックすると、index
が 1
、2
のように変わりながら次の彫刻が表示されて欲しいのですが、これは正しく動作しません(試してみてください)。
handleClick
イベントハンドラは、ローカル変数 index
を更新しています。しかし、以下の 2 つの理由により、目に見える変化が起きません。
- ローカル変数はレンダー間で保持されません。React がこのコンポーネントを次にレンダーするときは、まっさらな状態からレンダーします。過去にローカル変数を変更したことは考慮されません。
- ローカル変数の変更は、レンダーをトリガしません。新しいデータでコンポーネントを再度レンダーする必要があることに React は気づきません。
コンポーネントを新しいデータで更新するためには、次の 2 つのことが必要です。
- レンダー間でデータを保持する。
- 新しいデータでコンポーネントをレンダー(つまり再レンダー)するよう React に伝える。
useState
フックは、これら 2 つの機能を提供します。
- レンダー間でデータを保持する state 変数。
- 変数を更新し、React がコンポーネントを再度レンダーするようにトリガする state セッタ関数。
state 変数の追加
state 変数を追加するには、ファイルの先頭で React から useState
をインポートします:
import { useState } from 'react';
次に、この行を:
let index = 0;
以下のように置き換えます:
const [index, setIndex] = useState(0);
index
は state 変数であり、setIndex
はセッタ関数です。
ここでの
[
と]
という構文は配列の分割代入と呼ばれるもので、配列から個々の値を読み取ることができます。useState
から返される配列は常に正確に 2 個の要素を持っています。
これらは handleClick
の中で以下のように動作します:
function handleClick() { setIndex(index + 1); }
これで、“Next” ボタンをクリックすると、現在の彫刻が切り替わるようになります:
はじめてのフック
React では、useState
やその他の use
で始まる関数はフック (Hook) と呼ばれます。
フックは、React がレンダーされている間のみ利用可能な特別な関数です(この点については、次ページで詳しく説明します)。フックを使うことで、さまざまな React の機能に「接続 (hook into)」して使用することができます。
state はそれらの機能のうちのひとつですが、他のフックについても後で紹介します。
useState
の構造
useState
を呼び出すということは、このコンポーネントに何かを覚えさせるよう React に指示を出すということです:
const [index, setIndex] = useState(0);
この場合、React には index
を覚えてもらいます。
useState
に渡す唯一の引数は、state 変数の初期値です。この例では、index
の初期値を useState(0)
で 0
に設定しています。
コンポーネントがレンダーされるたびに、useState
は以下の 2 つの値を含む配列を返します。
- 保存した値を保持している state 変数 (
index
)。 - state 変数を更新し、React にコンポーネントの再レンダーをトリガする state セッタ関数 (
setIndex
)。
以下は、これが実際にどのように動作するかを示しています。
const [index, setIndex] = useState(0);
- コンポーネントが初めてレンダーされる。
useState
にindex
の初期値として0
を渡したので、[0, setIndex]
を返す。React は0
が最新の state 値であることを覚える。 - state を更新する。ユーザがボタンをクリックすると、
setIndex(index + 1)
が呼び出される。現在index
は0
なので、setIndex(1)
になる。これにより、React はindex
が1
になったことを覚え、再レンダーがトリガされる。 - コンポーネントの 2 回目のレンダー。React は再び
useState(0)
というコードに出会うが、React はindex
を1
にセットしたことを覚えているので、代わりに[1, setIndex]
を返す。 - 以降も続く。
コンポーネントで複数の state 変数を使う
1 つのコンポーネントは、いくつでも好きな型の state 変数を持つことができます。このコンポーネントは、数値型の index
と、“Show details” をクリックすると切り替わるブーリアン型の showMore
という、2 つの state 変数を持っています。
この例の index
と showMore
のように state が互いに関連していない場合、複数の state 変数を持つのが良いでしょう。ただし、2 つの state 変数を一緒に更新することが多い場合は、それらを 1 つにまとめる方が簡単かもしれません。たとえば、多くのフィールドがあるフォームの場合、フィールドごとに state 変数を持つよりも、オブジェクトを保持する 1 つの state 変数を持つ方が便利です。詳しくは state 構造の選択を参照してください。
さらに深く知る
useState
の呼び出しには、どの state 変数を参照しているかに関する情報が含まれていないことに気付いたかもしれません。useState
に「識別子」のようなものを渡さないのに、どの state 変数が返されるべきなのか、どのようにしてわかるのでしょうか。あなたの関数を解析するといった魔術的なものに頼っているのでしょうか? 答えはノーです。
そうではなく、簡潔な構文を実現するため、フックは同一コンポーネントの各レンダー間で同一の順番で呼び出されることに依存しています。上記のルール(「フックはトップレベルでのみ呼び出す」)に従っていれば、フックは常に同じ順序で呼び出されるので、これは実用上うまく機能します。また、リンタプラグインがほとんどの間違いをキャッチします。
内部的には、React はすべてのコンポーネントに対して state のペアの配列を保持しています。また、現在のペアインデックスも管理しており、レンダー前に 0
に設定されます。useState
が呼び出されるたびに、React は次の状態ペアを提供し、インデックスをインクリメントします。このメカニズムについては、React Hooks: Not Magic, Just Arrays で詳しく説明されています。
以下の例は React を使っていませんが、useState
が内部的にどのように機能するかの考え方がわかります。
React を使用するためにこのことを理解する必要はありませんが、脳内モデルとして知っておくと役立つかもしれません。
state は独立しておりプライベート
state は画面上の個々のコンポーネントインスタンスに対してローカルです。言い換えると、同じコンポーネントを 2 回レンダーした場合、それぞれのコピーは完全に独立した state を有することになります! そのうちの 1 つを変更しても、もう 1 つには影響しません。
この例では、先ほどの Gallery
コンポーネントが、そのロジックには変更を加えずに 2 回レンダーされています。それぞれのギャラリーの中のボタンをクリックしてみてください。これらの state が独立していることが分かるでしょう。
これが state 変数と、モジュールのトップレベルで宣言する通常の変数との違いです。state は特定の関数呼び出しやコードの場所に紐付いているのではなく、画面上の特定の場所に対して「ローカル」になります。あなたが 2 つの <Gallery />
コンポーネントをレンダーしたので、それらの state は別々に保持されているのです。
また、Page
コンポーネントは、Gallery
の state の値も、そもそも state が存在するかどうかも「知らない」ということにとにも注目してください。props と違い、state はそれを宣言したコンポーネントに完全にプライベートなものです。親コンポーネントがそれを変更することはできません。このおかげで、任意のコンポーネントに state を追加したり削除したりしても、他のコンポーネントに影響を与えることはありません。
両方のギャラリーで state を同期させたい場合はどうすればよいでしょうか? React での正解は、子コンポーネントから state を削除して、それらに最も近い共有の親に追加することです。ここからの数ページでは、1 つのコンポーネント内での state の管理に焦点を当てていますが、コンポーネント間での state 共有で改めてこのトピックに戻って解説します。
まとめ
- レンダー間で情報を「記憶」しておく必要があるコンポーネントには、state 変数を使う。
- state 変数は、
useState
フックを呼び出すことで宣言される。 - フックは
use
から始まる特殊な関数であり、state などの React 機能に「接続」できる。 - フックはインポートと似ており、無条件に呼び出す必要がある。
useState
などのフックの呼び出しは、コンポーネントのトップレベルか別のフックでのみ有効である。 - useState フックは、現在の state とそれを更新する関数の組み合わせを返す。
- 複数の state 変数を持つことができる。内部で React はそれらを呼び出し順を用いて対応付ける。
- state はコンポーネントにプライベートなものである。2 つの場所でレンダーすると、それぞれのコピーが独立した state を得る。
チャレンジ 1/4: ギャラリーの完成
最後の彫刻が表示されているときに “Next” を押すと、コードがクラッシュします。クラッシュを防ぐためにロジックを修正してください。そのためには、イベントハンドラに追加のロジックを追加するか、操作が不可能な場合はボタンを無効化しましょう。
クラッシュを修正できたら、“Previous” ボタンを追加して、ひとつ前の彫刻を表示するようにしてください。最初の彫刻でクラッシュしないようにしましょう。