サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

Create React App フックによる状態管理 01: 基本のuseStateを使う


このチュートリアルシリーズでは、Reactにおける状態(state)の管理について、おもにフックを使った解説をしてゆきます。React 16.8からは、関数コンポーネントでも、状態が保てるようになりました。そのための機能がフック(hook)です(「フックの導入」参照)。本稿では、もっとも基本となるステートフックuseStateで、関数コンポーネントに状態を備えます。サンプルに採り上げるのは簡単なカウンターのアプリケーションです(図001)。

図001■リセット・減算・加算の機能を備えたカウンター

図001

01 Reactアプリケーションのひな形をつくる

アプリケーションのひな形は、Create React Appでつくります。まずは、Create React Appのインストールです。予めNode.js(npm)が入っていなければなりません。コマンドラインツールからnpmで、npx create-react-appコマンドにつづけてアプリケーション名(react-state-managementとしました)を入力してください。その名前でフォルダとアプリケーションのひな形がつくられます。


npx create-react-app react-tic-tac-toe

ひな形がつくられたら、アプリケーションのディレクトリに移って、yarn startとコマンドを打てば、ローカルサーバーでひな形のページが開きます(図002)。ローカルサーバーを閉じたいときは[control]/[Ctrl] + [C]、再開には改めてyarn startを入力してください。


cd react-tic-tac-toe
yarn start

図002■ひな形のReactアプリケーションのページ

図001

02 素朴なuseStateの使い方

useStateを使うと、関数コンポーネントに状態を備えることができます。フック(hook)というのは、Reactの機能をつなぐ(hook into)関数です。useStateは、関数コンポーネントにReactの状態(state)をつなぎ止めます。戻り値は、ふたつの要素を収めた配列です。第1要素がstate変数で状態を保ち、第2要素の関数によりその値を書き替えます。配列の分割代入を使って変数(const)に取り出すのが便利でしょう。useStateの引数には、状態の初期値を渡してください。なお、フックはReactの関数内からしか呼び出せません。


import React, { useState } from 'react';

function 関数コンポーネント() {
	const [state変数, set関数] = useState(初期値);

}

カウンターのコンポーネント(src/CounterDisplay.js)とアプリケーションモジュール(src/App.js)の記述は、以下のコード001にまとめました。他のファイルのコードや動きは、CodeSandboxに掲げたサンプル001でお確かめください。useStateフックの構文は、つぎの表001のとおりです。設定関数(setCount())の引数は、関数型で更新しました(コード001のモジュールsrc/CounterDisplay.jsに定めたコンポーネントCounterDisplay()参照)。

表001■useStateフック

useState
引数

state変数に与える初期値。

  • はじめてレンダリングするときに、state変数に設定される。
  • 以降のレンダリング時には、直近のstate変数値を受け取る。
戻り値 state変数と設定関数を要素に収めた配列。
  1. 現在の状態をもつstate変数。
  2. state変数の値を引数値に改める設定関数

設定関数には、値でなく関数を渡すこともできる。その場合、引数は直近のstate変数値で、戻り値が新たなstate変数値となる(「関数型の更新」参照)。


(直近のstate変数値) => {
	// 処理
	return 新たなstate変数値
}

コード001■useStateをカウンターのコンポーネントで使う

src/CounterDisplay.js

import React, { useState } from 'react';

const CounterDisplay = ({ initialCount = 0 }) => {
	const [count, setCount] = useState(initialCount);
	return (
		<>
			Count: {count}
			<button onClick={() => setCount(initialCount)}>Reset</button>
			<button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
			<button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
		</>
	);
};

export default CounterDisplay;

src/App.js

import React from 'react';
import CounterDisplay from './CounterDisplay';
import './App.css';

function App() {
	return (
		<div className="App">
			<CounterDisplay />
		</div>
	);
}

export default App;

サンプル001■React state management 01-01: Simple useState

状態変数を更新するとき状態変数はできるだけ参照しない方が望ましい

前掲コード001の状態設定関数(setCount)は、関数型の更新で状態変数(count)値を書き替えました。つぎのように状態変数を参照して、設定関数の引数に渡してはいけないのでしょうか。


{/* <button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button> */}
<button onClick={() => setCount(count - 1)}>-</button>

今回の例では、問題は生じないでしょう。けれど、複数の状態変数を参照して処理する場合など、更新値を正しく得られないことがあります。関数型の更新であれば、状態変数を直に参照したことにはなりません。可能な場合は、できるだけ状態変数を直接得るのは避けることが望ましいでしょう(「Reactのstate更新におけるバッチ処理と『関数型のstate更新』がなぜ必要なのか?について」)。

03 状態は親コンポーネントに管理させる

前項のコード001で、カウンターは動きました。でも、動けいただけでは、ユーザーインタフェースにはなりません。その設定を他のコンポーネントが参照できなければならないでしょう。今回の例では、状態を親のアプリケーションモジュールにもたせれば、その下層の子コンポーネントに値を与えられます。そこで、カウンターのコンポーネント(CounterDisplay)の状態とロジックは、つぎのようにほぼそのままアプリケーションのモジュールsrc/App.jsに移してください。カウンターのコンポーネントには、プロパティとして加えればよいのです。

src/App.js

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

const initialCount = 0;
function App() {
	const [count, setCount] = useState(initialCount);
	const reset = () => setCount(initialCount);
	const decrement = () => setCount((prevCount) => prevCount - 1);
	const increment = () => setCount((prevCount) => prevCount + 1);
	return (
		<div className="App">
			{/* <CounterDisplay /> */}
			<CounterDisplay
				count={count}
				reset={reset}
				decrement={decrement}
				increment={increment}
			/>
		</div>
	);
}

カウンターのコンポーネント(src/CounterDisplay.js)は、受け取ったプロパティをJSXコードに差し込みます。

src/CounterDisplay.js

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

// const CounterDisplay = ({ initialCount = 0 }) => {
const CounterDisplay = ({ count, reset, decrement, increment }) => {
	// const [count, setCount] = useState(initialCount);
	/* ...[中略]... */
	return (
		<>
			Count: {count}
			{/* <button onClick={() => setCount(initialCount)}>Reset</button> */}
			<button onClick={reset}>Reset</button>
			{/* <button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button> */}
			<button onClick={decrement}>-</button>
			{/* <button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button> */}
			<button onClick={increment}>+</button>
		</>
	);
};

これでカウンターの動きは変わらず、状態とロジックをアプリケーションモジュールに移せました。動作とコードを確かめるためのサンプル001をCodeSandboxに公開します。

コード002■useStateとロジックを親コンポートに移した

src/App.js

import React, { useState } from 'react';
import CounterDisplay from './CounterDisplay';
import './App.css';

const initialCount = 0;
function App() {
	const [count, setCount] = useState(initialCount);
	const reset = () => setCount(initialCount);
	const decrement = () => setCount((prevCount) => prevCount - 1);
	const increment = () => setCount((prevCount) => prevCount + 1);
	return (
		<div className="App">
			<CounterDisplay
				count={count}
				reset={reset}
				decrement={decrement}
				increment={increment}
			/>
		</div>
	);
}

export default App;

src/CounterDisplay.js

import React from 'react';

const CounterDisplay = ({ count, reset, decrement, increment }) => {
	return (
		<>
			Count: {count}
			<button onClick={reset}>Reset</button>
			<button onClick={decrement}>-</button>
			<button onClick={increment}>+</button>
		</>
	);
};

export default CounterDisplay;

サンプル002■React state management 01-02: useState in App module


作成者: 野中文雄
作成日: 2020年11月03日


Copyright © 2001-2020 Fumio Nonaka.  All rights reserved.