いまさらですが React Hooks を勉強したので, 素振りのために React の公式チュートリアルをフックを使って書き換えてみようと思いました. この記事はその備忘録です.
準備
-
React 公式ガイドの Main Concepts を 第 1 章 Hello World から 第 12 章 React の流儀 まで読んでいき, React に入門します.
-
静的型チェック や React+TypeScript Cheatsheets を読み, React + TypeScript 環境の構成方法を学びます.
-
チュートリアルを一通り終えます. ただし僕はここでは CodePen 上で試すのではなくローカルで TypeScript も使いながら進めていくことにしたので, 最初に実行するコマンドは以下となります:
npx create-react-app rewrite-react-tutorial-in-hooks --typescript
React Hooks で書き換える
準備が整ったら本題の React Hooks による書き換えを行おうと思いますが, その前に公式ドキュメントの 1. フックの導入 の中にある クラスは人間と機械の両方を混乱させる という節を読むと, 以下のように書かれています:
コードの再利用や整頓が難しくなるということに加えてクラスについて我々が学んだことは、クラスが React を学ぶ上で障壁となっているということです。
(中略) これらの問題を解決するため、フックは、より多くの React の機能をクラスを使わずに利用できるようにします。コンセプト的には、React のコンポーネントは常に関数に近いものでした。
そういうわけで, React の世界観においてはクラスよりも関数コンポーネントのほうが好まれるようです.
そこでチュートリアルが終わった段階のコードをあらためてよく眺めてみると, Board
コンポーネントがもはや state
を持たなくなっていることがわかります (Game
コンポーネントにリフトアップしたので).
なので, これはフックと直接の関係はありませんが, Board
コンポーネントを pure function として書き直しておきましょう:
-class Board extends React.Component<BoardProps> {
- renderSquare(i: number) {
+const Board = (props: BoardProps) => {
+ const renderSquare = (i: number) => {
return (
<Square
- value={ this.props.squares[i] }
- onClick={ () => this.props.onClick(i) }
+ value={ props.squares[i] }
+ onClick={ () => props.onClick(i) }
/>
)
}
- render() {
- return (
- <div>
- <div className="board-row">
- {this.renderSquare(0)}
- {this.renderSquare(1)}
- {this.renderSquare(2)}
- </div>
- <div className="board-row">
- {this.renderSquare(3)}
- {this.renderSquare(4)}
- {this.renderSquare(5)}
- </div>
- <div className="board-row">
- {this.renderSquare(6)}
- {this.renderSquare(7)}
- {this.renderSquare(8)}
- </div>
+ return (
+ <div>
+ <div className="board-row">
+ {renderSquare(0)}
+ {renderSquare(1)}
+ {renderSquare(2)}
</div>
- );
- }
+ <div className="board-row">
+ {renderSquare(3)}
+ {renderSquare(4)}
+ {renderSquare(5)}
+ </div>
+ <div className="board-row">
+ {renderSquare(6)}
+ {renderSquare(7)}
+ {renderSquare(8)}
+ </div>
+ </div>
+ )
そうしたらフックの公式ドキュメントを 8. フックに関するよくある質問 まで読み, フックを完全理解しましょう.
useState
フック
さて, フックを使うための第一歩は, クラスのコンストラクタ内で this.state
を初期化するのをやめて useState
フックを使うようにするということです:
-import React from 'react';
+import React, { useState } from 'react';
-class Game extends React.Component<any, GameState> {
- constructor(props: any) {
- super(props)
- this.state = {
- history: [{
- squares: Array(9).fill(null)
- }],
- stepNumber: 0,
- xIsNext: true
- }
- }
+const Game = () => {
+ const [history, setHistory] = useState([{
+ squares: Array(9).fill(null)
+ }])
+ const [stepNumber, setStepNumber] = useState(0)
+ const [xIsNext, setXIsNext] = useState(true)
this.state
が存在しなくなったので, this.state.history
みたいな形で状態にアクセスするのもやめて history
等を直接読み出すようにします.
setState
this.setState
で状態を更新していた箇所は useState
の返り値であるところの setHistory
等で書き換えます.
プロトタイプメソッド
クラスの中にあった handleClick
等のメソッドは, もはやクラスが存在しなくなるので const handleClick = () => {}
のような形で関数宣言するよう変更します.
呼び出すときは this
経由ではなく直接利用するようにします.
render
JSX は render
関数が返すのではなく, 関数コンポーネントから直接 return
するようにします.
チュートリアルにおける書き換えの注意
チュートリアルのコードでは useEffect
が必要になる箇所は無いので, 上記までの作法に従えばおおよそ書き換えは完了するでしょう. 具体例としては jumpTo
がわかりやすいかもしれません:
- jumpTo(step: number) {
- this.setState({
- stepNumber: step,
- xIsNext: (step % 2) === 0
- })
+ const jumpTo = (step: number) => {
+ setStepNumber(step)
+ setXIsNext((step % 2) === 0)
}
唯一の注意点は, handleClick
の中の
const history = this.state.history.slice(0, stepNumber + 1)
という部分は history
という変数名がかぶっているので,
const historyUntilCurrentStep = history.slice(0, stepNumber + 1)
のように別名の変数を使う必要があったということです.
React.useState で型を明示的に指定できる
React+TypeScript Cheatsheets の Hooks の節にあるように, React.useState
(とそのエイリアスである useState
) による型推論は多くの場合非常にうまくいきます. しかしチュートリアルの中で history
の型だけは少し複雑なので, 推論がうまくいかない場合があるかもしれません. そのようなときのために React.useState
では以下のように明示的に型を指定することができます:
const [history, setHistory] = React.useState<Array<{
squares: Array<string | null>
}>>([{
squares: Array(9).fill(null)
}])
駆け足でしたが以上です
チュートリアルの範囲内だと useEffect
を使う必要がなかったので, フックを使ううまみはそこまで出ていないかもしれません.
それでもコードの中から this
が消え去ったので, いつ this
を使うべきか気を使わずに済むようになったぶん脳にかかる負荷が軽くなり, 素朴で素直な JavaScript になったと思います.
これは個人的な感想ですが, React 自体も公式ドキュメントもなるべく JavaScript 的に素直な流れで読めるように, 認知負荷を下げる方向に進むよう配慮されていて, 心地良い世界であるなあと現時点では感じています.
(おしまい)