stateについて
- フォーム上でタイプすると入力欄が更新される
- 画像カルーセルで「次」をクリックすると表示される画像が変わる
- 「購入」をクリックすると買い物かごに商品が入る
などユーザーの操作によって画面上の表示を変えたい際に使用するコンポーネント固有のメモリを指します。
ただの変数ではなく再描画と結びついた変数というイメージになります。
今回の内容に関してはReact学習ログ #5|画面の更新でも触れています。
通常の変数ではうまくいかない例
「Next」をクリックするとindexの値が連動して変化して、次の画像に切り替わるという機能を実現したいとします。
よくあるページング機能です。
まずは、機能が実現しない例を試してみます。
*data.jsに関してはコードが長くなるため割愛していますが、上記のチュートリアルリンク内で確認出来ます。
import { sculptureList } from './data.js';
export default function Gallery() {
let index = 0;
function handleClick() {
index = index + 1;
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<img
src={sculpture.url}
alt={sculpture.alt}
/>
<p>
{sculpture.description}
</p>
</>
);
}
分かりづらいですが、「Next」ボタンをクリックしても何も反応していません。
理由は二つあります。
- ローカル変数はレンダー間で保持されません。React がこのコンポーネントを次にレンダーするときは、まっさらな状態からレンダーします。過去にローカル変数を変更したことは考慮されません
- ローカル変数の変更は、レンダーをトリガしません。新しいデータでコンポーネントを再度レンダーする必要があることに React は気づきません
原因のコードとして、indexがローカル変数になっています。
let index = 0;
そのため、「Next」ボタンをクリックしても単にindexが1になるだけで画面上は何も変化しません。
React的には何をしているのか気付いてない状態です。
Reactが監視しているのはstateとpropsなので、Reactの管理外のただのJavaScript変数を定義して値を変更しても、何もかわらないのです。
そのため、今回機能を実現するために必要なことは
- レンダー間でデータを保持する
- 新しいデータでコンポーネントをレンダー(つまり再レンダー)するよう React に伝える
ということです。
state 変数の追加
state変数を追加するには、ファイルの先頭でReactからuseStateをインポートします。
import { useState } from 'react';
次にindexの定義の記述を変更します。
let index = 0;
const [index, setIndex] = useState(0);
これでindexはただのローカル変数からstate変数となり、setIndexというセッタ関数も定義されました。
それぞれの役割としては、以下のようになります。
index:レンダー間でデータを保持するstate変数
satIndex:変数を更新し、Reactがコンポーネントを再度レンダーするようにトリガするstateセッタ関数
最後にindexの値を変更していたhandleClickの記述も変更します。
function handleClick() {
index = index + 1;
}
function handleClick() {
setIndex(index + 1);
}
イメージとしては以下のような動きとなります。
setIndex が呼ばれる
Reactが「stateが変わった」と認識
再レンダリング
新しい index でUIを描画
では実際にコードを書き直してみます。
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
// 定義の方法を変更
const [index, setIndex] = useState(0);
function handleClick() {
// 際レンダリング用に記述変更
setIndex(index + 1);
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<img
src={sculpture.url}
alt={sculpture.alt}
/>
<p>
{sculpture.description}
</p>
</>
);
}
画面上で確認してみます。
「Next」ボタンのクリックで画面内の描写が切り替わりました。
コンポーネントで複数の state 変数を使う
コンポーネントでは複数のstateを設定出来ます。
今回は先ほどのページング機能チックなものに、要素の表示非表示を管理するtoggle機能付与してみます。
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0);
// 初期値をfalseで定義する
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
// toggleボタンのイベント用
function handleMoreClick() {
setShowMore(!showMore);
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleNextClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<button onClick={handleMoreClick}>
{/* 値によって表示する文字を変化させている */}
{showMore ? 'Hide' : 'Show'} details
</button>
{/* もし表示状態(値がtrue)であれば要素を表示する*/}
{showMore && <p>{sculpture.description}</p>}
<img
src={sculpture.url}
alt={sculpture.alt}
/>
</>
);
}
このように一つのコンポーネントで違う役割のstateを設定して機能を拡張出来ました。
state は独立しておりプライベート
stateはコンポーネント内でローカル使用されます。
そのため、親コンポーネント内で子コンポーネントを複数回呼び出した場合stateは共有されません。
まとめ
- レンダー間で情報を「記憶」しておく必要があるコンポーネントには、
state変数を使う
-
state変数は、useStateフックを呼び出すことで宣言される
- フックは
useから始まる特殊な関数であり、stateなどのReact機能に「接続」できるフックはインポートと似ており、無条件に呼び出す必要がある
-
useStateなどのフックの呼び出しは、コンポーネントのトップレベルか別のフックでのみ有効である
-
useStateフックは、現在のstateとそれを更新する関数の組み合わせを返す複数のstate変数を持つことができる
- 内部で
Reactはそれらを呼び出し順を用いて対応付ける
-
stateはコンポーネントにプライベートなものである。
- 2 つの場所でレンダーすると、それぞれのコピーが独立した
stateを得る


