Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
12
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

React 公式チュートリアルをフックで書き換える

いまさらですが React Hooks を勉強したので, 素振りのために React の公式チュートリアルをフックを使って書き換えてみようと思いました. この記事はその備忘録です.

準備

  1. React 公式ガイドの Main Concepts を 第 1 章 Hello World から 第 12 章 React の流儀 まで読んでいき, React に入門します.

  2. 静的型チェックReact+TypeScript Cheatsheets を読み, React + TypeScript 環境の構成方法を学びます.

  3. チュートリアルを一通り終えます. ただし僕はここでは 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 CheatsheetsHooks の節にあるように, 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 的に素直な流れで読めるように, 認知負荷を下げる方向に進むよう配慮されていて, 心地良い世界であるなあと現時点では感じています.

(おしまい)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
12
Help us understand the problem. What are the problem?