30
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

React公式チュートリアルのクラスコンポーネントを関数コンポーネントに書き替える

Last updated at Posted at 2020-07-27

なかなか手をつけられなかったReact公式サイトのチュートリアルを、一通りやってみた。
ステートフックuseState()を使って、クラスコンポーネントから関数コンポーネントに書き換えてみたのでメモに残す。

公式サイトより:チュートリアルReact の導入
三目並べ完成形

hookを呼び出す際に気をつけること => フックを呼び出すのはトップレベルのみ

フックをループや条件分岐、あるいはネストされた関数内で呼び出してはいけません。代わりに、あなたの React の関数のトップレベルでのみ呼び出してください。これを守ることで、コンポーネントがレンダーされる際に毎回同じ順番で呼び出されるということが保証されます。これが、複数回 useState や useEffect が呼び出された場合でも React がフックの状態を正しく保持するための仕組みです。

参照: React公式サイト フックのルール

Square(三目並べの正方形のマス目)

Square.jsx
import React from "react";

// function Square(props) {
//   return (
//     <button className="square" onClick={props.onClick}>
//       {props.value}
//     </button>
//   );
// }

// Board(盤面) コンポーネントから { onClick, value } = props を受け取っている
const Square = ({ onClick, value }) => {
  return (
    <button className="square" onClick={onClick}>
      {value}
    </button>
  );
};

export default Square;

Board(盤面)

Board.jsx
import React from "react";
// Square(三目並べの正方形のマス目)コンポーネントをimport
import Square from "./Square";

// class Board extends React.Component {
//   renderSquare(i) {
//     return (
//       <Square
//         value={this.props.squares[i]}
//         onClick={() => this.props.onClick(i)}
//       />
//     );
//   }

// Game コンポーネントから { squares, onClick } = props を受け取っている
const Board = ({ squares, onClick }) => {
  const renderSquare = i => {
    return (
      <Square
        value={squares[i]}
        onClick={() => {
          onClick(i);
        }}
      />
    );
  };

  // render() {
  return (
    <>
      <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>
    </>
  );
  // }
};

export default Board;

Game

Game(親)コンポーネントは props を使うことで子に情報を渡すことができた。こうすることで、子コンポーネントとの間で常に同期されるようになる。

Game.jsx
// useStateをimport
import React, { useState } from "react";
// Board(盤面)コンポーネントをimport
import Board from "./Board";
import "./style.css";

// class Game extends React.Component {
//   constructor(props) {
//     super(props);
const Game = () => {
  //  クラスのconstructor内で `this.state` の初期化をやめて `useState` フックを使う
  //  this.state = {
  //    history: [
  //      {
  //        squares: Array(9).fill(null)
  //      }
  //   ],
  // useState()を使って、関数コンポーネントに状態を持たせる
  // const [state変数, set関数] = useState(初期値)
  const [history, setHistory] = useState([
    {
      squares: Array(9).fill(null)
    }
  ]);
  // stepNumber: 0,
  const [stepNumber, setStepNumber] = useState(0);
  // xIsNext: true
  const [xIsNext, setXIsNext] = useState(true);
  //     };
  //   }

  // handleClick(i) {
  const handleClick = i => {
    // const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const historyCurrent = history.slice(0, stepNumber + 1);
    // const current = history[history.length - 1];
    const current = historyCurrent[historyCurrent.length - 1];
    // const squares = current.squares.slice();
    const squares = [...current.squares];

    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    // squares[i] = this.state.xIsNext ? "X" : "O";
    squares[i] = xIsNext ? "X" : "O";
    //this.setState({
    //  history: history.concat([
    //    {
    //      squares: squares
    //    }
    //  ]),

    setHistory([...historyCurrent, { squares }]);
    // stepNumber: history.length,
    setStepNumber(historyCurrent.length);
    // xIsNext: !this.state.xIsNext
    setXIsNext(!xIsNext);
  };
  //   });
  // }

  // jumpTo(step) {
  //   this.setState({
  //     stepNumber: step,
  //     xIsNext: (step % 2) === 0
  //   });
  // }

  const jumpTo = step => {
    setStepNumber(step);
    setXIsNext(step % 2 === 0);
  };
  // render() {
  // const history = this.state.history;
  // const current = history[this.state.stepNumber];
  const current = history[stepNumber];
  const winner = calculateWinner(current.squares);

  const moves = history.map((step, move) => {
    const desc = move ? `Go to move # ${move}` : `Go to game start`;
    return (
      <li key={move}>
        {/* <button onClick={() => this.jumpTo(move)}>{desc}</button> */}
        <button onClick={() => jumpTo(move)}>{desc}</button>
      </li>
    );
  });

  let status;
  if (winner) {
    status = `Winner : ${winner}`;
  } else {
    // status = "Next player: " + (this.state.xIsNext ? "X" : "O");
    status = `Next Player : ${xIsNext ? "X" : "O"}`;
  }
  return (
    <div className="game">
      <div className="game-board">
        {/* <Board squares={current.squares} onClick={i => this.handleClick(i)} /> */}
        <Board squares={current.squares} onClick={i => handleClick(i)} />
      </div>
      <div className="game-info">
        <div>{status}</div>
        <ol>{moves}</ol>
      </div>
    </div>
  );
  // }
};

const 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]
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
};

export default Game;

参考: React公式サイト

30
29
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?