React + TypeScript: コンポーネントの状態を保持する

React公式サイトのドキュメントが2023年3月16日に改訂されました(「[Introducing react.dev](https://react.dev/blog/2023/03/16/introducing-react-dev)」参照)。本稿は、基本解説の「[State: A Component's Memory](https://react.dev/learn/state-a-components-memory)」をかいつまんでまとめた記事です。ただし、コードにはTypeScriptを加えました。反面、初心者向けのJavaScriptの基礎的な説明は省いています。

なお、本シリーズ解説の他の記事については「[React + TypeScript: React公式ドキュメントの基本解説『Learn React』を学ぶ](https://qiita.com/FumioNonaka/items/d70585bd3c0a6d4edc24)」をご参照ください。

コンポーネントに対するインタラクションに応じて、画面の中身を改めなければならないことがあるでしょう。たとえば、つぎのような場合です。

* フォームに入力したときテキストフィールドの更新。
* カルーセルで[つぎへ]のボタンをクリックしたときの画像の切り替え。
* 商品の[購入]ボタンクリックによるショッピングカートへの追加。

コンポーネントは、こうした変更の現行値を覚えておかなければなりません。Reactでは、コンポーネントごとに値をメモリする仕組みは状態(state)と呼ばれます。このとき、ローカル変数は使えません。

1. コンポーネントは関数ですから、Reactがつぎにレンダリングしたとき、ローカル変数の値は保持されないのです。
2. ローカル変数の値が変わっても、レンダリングは起こりません。つまり、Reactは、新しいデータでコンポーネントを再レンダリングすべきことがわからないのです。


# 状態変数を使う
コンポーネントを新たなデータで更新するために行うべきは、つぎのふたつです。

1. レンダー間でデータの値は保持しなければなりません。
2. Reactに新たなデータでコンポーネントをレンダリングさせます(再レンダリング)。

ここで用いるのがフック[`useState`](https://react.dev/reference/react/useState)で、戻り値はつぎのふたつの要素からなる配列です。

1. **状態変数**: レンダー間でデータを保持します。
2. **状態設定関数**: 状態変数の設定関数です。Reactにコンポーネントを再レンダリングするよう伝えます。

構文はつぎのとおりです。返された配列要素の変数や関数の名前は自由に決めて構いません。ただ、状態変数名の前に`set`を加えて設定関数名にするのが慣例です。引数(`initialState`)は変数の初期値で、TypeScriptを使う場合には可能なかぎり与えてください(「[useState](https://qiita.com/FumioNonaka/items/4361d1cdf34ffb5a5338#usestate)」参照)。

```react
const [state, setState] = useState(initialState)
```

このあとご紹介するコード例では、`useState`フックはつぎのように用います。状態変数`index`は、コンポーネント`Gallery`で表示する情報のページです。ボタンクリックでつぎのページに進みますので、ハンドラ関数(`handleClick`)は状態変数の値を設定関数`setIndex`でカウントアップします。

```react:src/App.tsx
import { MouseEventHandler, useState } from 'react';

export default function Gallery() {
	const [index, setIndex] = useState(0);
	const handleClick: MouseEventHandler = () => {
		setIndex(index + 1);
	};

}
```

ルートモジュール`src/App.tsx`の記述はつぎのとおりです。状態変数は保持され、値が変わればコンポーネントは再レンダリングされます。読み込むデータ(`src/data.ts`)について、詳しくはあとに掲げるサンプル001をご参照ください。[Next]ボタンのクリックで、画面に表示される情報が切り替わるでしょう。

```react:src/App.tsx
import { MouseEventHandler, useState } from 'react';
import { sculptureList } from './data';

export default function Gallery() {
	const [index, setIndex] = useState(0);
	const handleClick: MouseEventHandler = () => {
		setIndex((index + 1) % sculptureList.length);
	};
	const { name, artist, description, url, alt } = sculptureList[index];
	return (
		<>
			<button onClick={handleClick}>Next</button>
			<h2>
				<i>{name} </i>
				by {artist}
			</h2>
			<h3>
				({index + 1} of {sculptureList.length})
			</h3>
			<img src={url} alt={alt} />
			<p>{description}</p>
		</>
	);
}
```

```typescript:src/data.ts
type Sculpture = {
	name: string;
	artist: string;
	description: string;
	url: string;
	alt: string;
};
export const sculptureList: Sculpture[] = [
	{
		name: "Homenaje a la Neurocirugía",
		artist: "Marta Colvin Andrade",
		description:
		"Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.",
		url: "https://i.imgur.com/Mx7dA2Y.jpg",
		alt:
		"A bronze statue of two crossed hands delicately holding a human brain in their fingertips."
	},
	// ...[中略]...
];
```

フックは名前が`use`ではじまる関数です。さまざまな機能を提供する特別な関数で、Reactの[レンダリング](https://react.dev/learn/render-and-commit#step-1-trigger-a-render)中にのみ使えます。`useState`もその中のひとつです。なお、**フックはコンポーネントまたは[カスタムフック](https://react.dev/learn/reusing-logic-with-custom-hooks)のトップレベルでしか呼び出せません**。条件やループ、入れ子の関数の中からは実行できないことにお気をつけください。

状態とレンダリングとはつぎのような流れで変わってゆきます。

1. コンポーネントの初期レンダリング: Reactが保持する状態変数の現行値は初期値です。
2. 状態の更新: 状態設定関数により変数値が変わると、Reactはその値を最新と改め、レンダリングがはじまります。
3. コンポーネントの再レンダリング: `useState`の引数は初期値のまま変わりません。けれど、状態変数には最新値が保持されます。


# コンポーネントに複数の状態変数を与える
状態変数の数や与えるデータ型に制限はありません。つぎのコード例は、ふたつの状態変数(`index`と`showMore`)を定めました。どちらも、値の更新はボタンクリック(`onClick`)イベントのハンドラ(`handleClick`と`handleMoreClick`)です(サンプル001)。

```react:src/App.tsx
import { MouseEventHandler, useState } from 'react';
import { sculptureList } from './data';

export default function Gallery() {
	const [index, setIndex] = useState(0);
	const [showMore, setShowMore] = useState(false);
	const handleClick: MouseEventHandler = () => {
		setIndex((index + 1) % sculptureList.length);
		setShowMore(false);
	};
	const handleMoreClick: MouseEventHandler = () => {
		setShowMore(!showMore);
	};
	const { name, artist, description, url, alt } = sculptureList[index];
	return (
		<>
			<button onClick={handleClick}>Next</button>
			<h2>
				<i>{name} </i>
				by {artist}
			</h2>
			<h3>
				({index + 1} of {sculptureList.length})
			</h3>
			<button onClick={handleMoreClick}>
				{showMore ? 'Hide' : 'Show'} details
			</button>
			{showMore && <p>{description}</p>}
			<img src={url} alt={alt} />
		</>
	);
}
```

#### サンプル001■React + TypeScript: State: A component's memory 01

https://codesandbox.io/embed/react-typescript-state-a-components-memory-01-h8roh6?fontsize=14&hidenavigation=1&theme=dark

`useState`のはじめての呼び出しで、状態変数は引数値に初期化されます。状態変数が更新されると、そのあとのレンダリングで`useState`が返す状態変数値は、初期値でなく最新の値です。Reactは、どのようにして保持した値が正しく参照できるのでしょう。

フックは、**ひとつのコンポーネントをレンダリングするとき、決まった順序で呼び出される**のです(そのため、フックはトップレベルで呼び出さなければなりません)。内部的には、Reactは状態変数と設定関数の組み(配列)を、コンポーネントごとに配列で保持します。そして、つぎに渡す組を配列に加えて、コンポーネントに渡すのです(「[How does React know which state to return?](https://beta.reactjs.org/learn/state-a-components-memory#how-does-react-know-which-state-to-return)」参照)。


# 状態は独立かつプライベート
状態は、画面上のコンポーネントインスタンスごとにローカルです。同じコンポーネントを2回レンダリングすれば、それぞれまったく独立した状態を持ちます。一方の状態を変えても、他方には影響しません。同じコンポーネントを画面にふたつ並べても、それぞれに状態が備わっていれば、異なる値にできます。状態は、コンポーネントごとに独立して保持されるのです。

プロパティは親コンポーネントから渡されます。けれど、状態について親が知ることはありませんし、変更もできません。**状態は宣言したコンポーネントだけのプライペートなデータです**。したがって、他のコンポーネントに影響を与えることなく、追加したり削除したりできます(「[State is isolated and private](https://beta.reactjs.org/learn/state-a-components-memory#state-is-isolated-and-private)」参照)。


# まとめ
この記事では、つぎのような項目についてご説明しました。

* コンポーネントのレンダリング間で情報を保持したいときに使うのが状態変数です。
* `useState`フックは状態変数を宣言します。
* `useState`フックの戻り値は、状態変数と状態設定関数を要素とした配列です。
* フックは、コンポーネントまたはカスタムフックのトップレベルで呼び出さなければなりません。
* 状態変数は複数宣言できます。内部的にReactが値を対応させるのは、フックの呼び出し順です。
* 状態はコンポーネントごとにプライベートに扱われます。同じコンポーネントを複数レンダリングしても、状態の値はそれぞれ別です。