HTML5テクニカルノート
Create React App 入門 04: クラスのコンポーネントをuseStateで関数に書き替える
- ID: FN2101004
- Technique: ECMAScript 2015
- Library: React 17.0.1
これまでのReactのコンポーネントでは、状態(state)をもたせたいときはクラス(class)で定めました。けれど、React 16.8からは、関数コンポーネントでも、状態が保てます。そのための機能がフック(hook)です(「フックの導入」参照)。今回は、もっとも基本となるステートフックuseStateにより、状態が備わったクラスコンポーネントを関数コンポーネントに書き替えます。
01 クラスを関数に書き替える
「Create React App 入門 02」02「データをルートコンポーネントにもたせる」では、ルートモジュールsrc/components/App.jsのコンポーネントに状態をもたせたいがため、関数からクラスに書き替えました。ステートフックuseStateを用いれば、再び関数コンポーネントに戻せるのです。
まず状態(state)は一旦置いて、コンポーネント(App)の構文をクラスから関数に書き替えます。関数にコンストラクタ(constructor())はありません。コンストラクタで初期化した状態は、このあとuseStateで定めます。つぎに、コンポーネントに備えたメソッドです。ローカル変数(const)にアロー関数式=>で書くことにします。関数コンポーネントの中では、this参照は用いません。このあとステートフックで加える状態についても同じです。
src/components/App.js// import React from 'react'; // class App extends React.Component { function App() { /* constructor(props) { } */ // handleClick(i) { const handleClick = (i) => { }; // render() { return ( ); // }; }
02 ステートフックuseStateを使う
ステートフックuseStateを使うと、関数コンポーネントに状態を備えることができます。フック(hook)というのは、Reactの機能をつなぐ(hook into)関数です。useStateは、関数コンポーネントにReactの状態(state)をつなぎ止めます。戻り値は、ふたつの要素を収めた配列です。第1要素がstate変数で状態を保ち、第2要素の関数によりその値を書き替えます。配列の分割代入を使って変数(const)に取り出すのが便利でしょう。useStateの引数には、状態の初期値を渡してください。なお、フックはReactの関数内からしか呼び出せません。
import { useState } from 'react'; function 関数コンポーネント() { const [state変数, set関数] = useState(初期値); }
ルートモジュールsrc/components/App.jsのクラスコンポーネント(App)にもたせたstateおよびsetState()の呼び出しは、useStateフックから受け取ったstate変数(squares)および設定関数(setSquares())によりつぎのように書き替えてください。ローカル変数(const)に宣言した変数および関数ですので、参照するときthisは添えません。
src/components/App.js// import React from 'react'; import { useState } from 'react'; // class App extends React.Component { function App() { /* constructor(props) { super(props); this.state = { squares: Array(9).fill(null), xIsNext: true, finished: false }; } */ const [squares, setSquares] = useState(Array(9).fill(null)); const [xIsNext, setXIsNext] = useState(true); const [finished, setFinished] = useState(false); }
コンポーネントがクラスから関数に替わっても、処理の中身は同じです。それでも、this参照がなくなるので、つぎのような書き替えが必要になります。
this.state.プロパティ→state変数this.setState()→state設定関数()this.メソッド()→ローカル関数()
ルートモジュールsrc/components/App.jsの書き直しは、つぎに抜き出したコードのとおりです。state変数と重複するローカル変数の名前も、少し変えました。
src/components/App.jsfunction App() { const [squares, setSquares] = useState(Array(9).fill(null)); const [xIsNext, setXIsNext] = useState(true); const [finished, setFinished] = useState(false); const handleClick = (i) => { // const squares = [...this.state.squares]; const _squares = [...squares]; // if (squares[i]) { return; } if (_squares[i]) { return; } // if (this.state.finished) { return; } if (finished) { return; } // squares[i] = this.state.xIsNext ? 'X' : 'O'; _squares[i] = xIsNext ? 'X' : 'O'; /* this.setState({ squares, xIsNext: !this.state.xIsNext }); */ setSquares(_squares); setXIsNext(!xIsNext); // const winner = calculateWinner(squares); const winner = calculateWinner(_squares); if (winner) { // this.setState({finished: true}); setFinished(true); } }; // const winner = calculateWinner(this.state.squares); const winner = calculateWinner(squares); const status = (winner) ? `Winner: ${winner}` : // `Next player: ${this.state.xIsNext ? 'X' : 'O'}`; `Next player: ${xIsNext ? 'X' : 'O'}`; return ( <div className="game"> <Board // squares={this.state.squares} squares={squares} // onClick={(i) => this.handleClick(i)} onClick={(i) => handleClick(i)} /> <div className="game-info"> <div>{status}</div> </div> </div> ); }
これで、クラスコンポーネントが、ステートフックuseStateを用いて関数コンポーネントに書き替えられるということです。クラスコンポーネントのstateは、ひとつのオブジェクトでした。けれども、state変数は単純な値でよく、いくつでも宣言できます。まとめたい値があったらオブジェクトにして、それぞれをプロパティとして定めればよいのです。useStateフックの構文は、以下の表001にまとめておきました。詳しくは、リンクしたReact公式サイトの情報をご参照ください。
表001■useStateフック
useState |
|
| 引数 |
|
| 戻り値 | state変数と設定関数のふたつの要素を収めた配列。
設定関数には、値でなく関数を渡すこともできる。その場合、引数は直近の
|
ノート01■フックを呼び出すのはトップレベルのみ
React公式サイトの「フックのルール」にはつぎのような注意が記されています。フックはレンダリングのたびに、必ずすべてが同じ順序で呼び出されなければならないのです(「最近Reactを始めた人向けのReact Hooks入門」の「Hooksを使う上で絶対に守ること」参照)。
フックをループや条件分岐、あるいはネストされた関数内で呼び出してはいけません。代わりに、あなたの React の関数のトップレベルでのみ呼び出してください。これを守ることで、コンポーネントがレンダーされる際に毎回同じ順番で呼び出されるということが保証されます。
書き直したルートモジュールの記述全体を、以下のコード001にまとめました。useStateフックだけでなく、関数コンポーネントに変えたことによるthis参照の修正が手間だったかもしれません。けれど、組み立てや処理の流れは、クラスコンポーネントのときと同じです。以下のサンプル001をCodeSandboxに公開しました。
さて、関数コンポーネントにすると、状態(データ)の保持や操作、つまりロジックを別モジュールに切り出しやすくなります。コンポーネントは、データの表示やインタフェースに専念できるということです。すると、アプリケーションの動作を確かめたり、機能追加や拡張するにも見通しがよくなります。ロジックの切り出しは、次回とそのつぎの2回にわたって解説する予定です。
コード001■クラスから関数コンポーネントに書き替えたルートモジュール
src/components/App.js
import { useState } from 'react';
import Board from './Board';
import './App.css';
function App() {
const [squares, setSquares] = useState(Array(9).fill(null));
const [xIsNext, setXIsNext] = useState(true);
const [finished, setFinished] = useState(false);
const handleClick = (i) => {
const _squares = [...squares];
if (_squares[i]) { return; }
if (finished) { return; }
_squares[i] = xIsNext ? 'X' : 'O';
setSquares(_squares);
setXIsNext(!xIsNext);
const winner = calculateWinner(_squares);
if (winner) {
setFinished(true);
}
};
const winner = calculateWinner(squares);
const status = (winner) ?
`Winner: ${winner}` :
`Next player: ${xIsNext ? 'X' : 'O'}`;
return (
<div className="game">
<Board
squares={squares}
onClick={(i) => handleClick(i)}
/>
<div className="game-info">
<div>{status}</div>
</div>
</div>
);
}
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
const length = lines.length;
for (let i = 0; i < length; i++) {
const [a, b, c] = lines[i];
const player = squares[a];
if (player && player === squares[b] && player === squares[c]) {
return player;
}
}
return null;
}
export default App;
サンプル001■Create React App: Tic Tac Toe 04
Create React App 入門
- Create React App 入門 01: 3×3マスのゲーム盤をつくる
- Create React App 入門 02: クリックしたマス目にXをつける
- Create React App 入門 03: マルバツで勝ち負けを決める
- Create React App 入門 04: クラスのコンポーネントをuseStateで関数に書き替える
- Create React App 入門 05: useContextで状態をコンポーネントツリー内に共有する
- Create React App 入門 06: アプリケーションのロジックをコンテクストに切り出す
- Create React App 入門 07: ゲームの履歴をさかのぼる
- Create React App 入門 08: useMemoフックで無駄な再計算を省く
- Create React App 入門 09: useCallbackフックで無駄な処理を省く
- Create React App 入門 10: 条件によってハンドラは無効にする ー useMemoを使って
作成者: 野中文雄
作成日: 2021年01月23日 FN2004003「React Hooks: クラスのコンポーネントをuseState()で関数に書き替える」を「Create React App 入門」シリーズに組み込むかたちで大幅に改訂。
Copyright © 2001-2021 Fumio Nonaka. All rights reserved.