0
0

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 2021-05-23

Reactチュートリアルの、クラスコンポーネントを関数コンポーネントへの置き換えについてまとめます。前回の記事でモジュール化を行った続きです。まだの方は先に確認して頂ければと思います。
https://qiita.com/nishiwaki_ff/items/9305672c82c169e06327

追加実装の流れ

前回に引き続き関数コンポーネントについて解説します。追加課題については別の記事で解説予定です。

関数コンポーネントに置き換え 解説

ゴールの確認

現状はSquareコンポーネントだけ関数コンポーネントに置き換えできていますが、それ以外はクラスコンポーネントのままになっています。全てを関数コンポーネントに置き換えた状態が今回のゴールです。

現状の確認

前回のモジュール化を終えた状態から始めます。詳細はこちらを確認して頂ければと思います。

Square.jsの修正

Squareコンポーネントはチュートリアルの中ですでに関数コンポーネントに置き換えできていますが、functionキーワードを使った書き方からアロー関数へ書き換えます。

Square.js
// アロー関数に書き換え
const Square = (props) => {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}; // セミコロン追加

export default Square;

Board.jsの修正

まずBoardコンポーネントをアロー関数で書き換える。

Board.js
// import React from 'react'; 不要になったので削除
import Square from './Square';

// アロー関数で書き換え
const Board = (props) => { // 引数でpropsを受け取る
  renderSquare(i) {
    return (
      <Square
        value={props.squares[i] /* thisは不要になるので削除 */}
        onClick={() => props.onClick(i) /* thisは不要になるので削除 */}
      />
    );
  }

  // renderメソッドの定義は不要になるのでそのままreturn
  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>
    </div>
  );
}; // セミコロン追加

export default Board;

書き換えるとrenderSquareでSyntaxErrorになると思います。関数コンポーネントに書き換えたことでメソッド定義が使えなくなった為です。renderSquareもアロー関数で書き換えます。またそれに伴いrenderSquareの呼び出し方が変わる為、thisを削除します。

Board.js
import Square from './Square';

const Board = () => {
  // アロー関数で書き換え
  const renderSquare = (i) => {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}
      />
    );
  }; // セミコロン追加

  // this削除
  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>
  );
}; // セミコロン追加

export default Board;

Game.jsの修正

Board.jsの修正と同様にGameコンポーネントをアロー関数で書き換え、さらにメソッド定義の箇所もアロー関数に書き換えます。

Game.js
// import React from 'react'; 不要になったので削除
import Board from './Board';

// アロー関数で書き換え
const Game = () => {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null)
        }
      ],
      stepNumber: 0,
      xIsNext: true
    };
  }

  // アロー関数で書き換え
  const handleClick = (i) => {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? "X" : "O";
    this.setState({
      history: history.concat([
        {
          squares: squares
        }
      ]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext
    });
  }; // セミコロン追加

  // アロー関数で書き換え
  const jumpTo = (step) => {
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0
    });
  }; // セミコロン追加

  // renderの定義は不要になる
  const history = this.state.history;
  const current = history[this.state.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={() => jumpTo(move) /* this削除 */}>{desc}</button>
      </li>
    );
  });

  let status;
  if (winner) {
    status = "Winner: " + winner;
  } else {
    status = "Next player: " + (this.state.xIsNext ? "X" : "O");
  }

  return (
    <div className="game">
      <div className="game-board">
        <Board
          squares={current.squares}
          onClick={i => handleClick(i) /* this削除 */}
        />
      </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;

次にstateで定義していたhistory、stepNumber、xIsNextをHooksで書き換えます。Hooksについて不安な方はこちらを参考にしてください。

xIsNextの修正

まずは単純なstateから修正しようと思います。xIsNextをHooksで書き換えます。またxIsNextの参照方法も変わるので修正します。

Game.js
import {useState} from 'react'; // import文追加
import Board from './Board';

const Game = () => {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null)
        }
      ],
      stepNumber: 0,
      // 不要になるので削除
    };
  }

  // xIsNextをHooksで書き換え
  const [xIsNext, setXIsNext] = useState(true);

  const handleClick = (i) => {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = xIsNext ? "X" : "O"; // xIsNextの参照を修正
    this.setState({
      history: history.concat([
        {
          squares: squares
        }
      ]),
      stepNumber: history.length,
      // 不要になるので削除
    });
    setXIsNext(!xIsNext); // xIsNextの参照及び更新部分を修正

  };

  const jumpTo = (step) => {
    this.setState({
      stepNumber: step,
      // 不要になるので削除
    });
    setXIsNext((step % 2) === 0); // stateの更新部分を修正
  };

  const history = this.state.history;
  const current = history[this.state.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={() => jumpTo(move)}>{desc}</button>
      </li>
    );
  });

  let status;
  if (winner) {
    status = "Winner: " + winner;
  } else {
    status = "Next player: " + (xIsNext ? "X" : "O"); // xIsNextの参照を修正
  }

  return (
  // 以下変更無し

stepNumberの修正

xIsNextと同様にstepNumberも修正します。

Game.js
import {useState} from 'react';
import Board from './Board';

const Game = () => {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null)
        }
      ],
      // 不要になるので削除
    };
  }

  // stepNumberをHooksで書き換え
  const [stepNumber, setStepNumber] = useState(0);
  const [xIsNext, setXIsNext] = useState(true);

  const handleClick = (i) => {
    const history = this.state.history.slice(0, stepNumber + 1); // stepNumberの参照を修正
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = xIsNext ? "X" : "O";
    this.setState({
      history: history.concat([
        {
          squares: squares
        }
      ]),
      // 不要になるので削除
    });
    setStepNumber(history.length); // stepNumberの更新部分を修正
    setXIsNext(!xIsNext);
  };

  const jumpTo = (step) => {
    // 不要になるので削除

    setStepNumber(step); // stepNumberの更新部分を修正
    setXIsNext((step % 2) === 0);
  };

  const history = this.state.history;
  const current = history[stepNumber]; // stepNumberの参照を修正
  const winner = calculateWinner(current.squares);

  const moves = history.map((step, move) => {
  // 以下変更無し

historyの修正

同様にhistoryも修正します。またこの時Gameコンポーネント内で宣言していた変数historyの名前も修正します。historyをHooksで定義することで、Gameコンポーネント内で新たに変数historyを宣言すると名前が重複してエラーになるためです。

Game.js
import {useState} from 'react';
import Board from './Board';

const Game = () => {

  // constructorは不要になるので削除

  // historyをHooksで書き換え
  const [history, setHistory] = useState(
    [
      {
        squares: Array(9).fill(null)
      }
    ]
  );
  const [stepNumber, setStepNumber] = useState(0);
  const [xIsNext, setXIsNext] = useState(true);

  const handleClick = (i) => {
    // 変数名historyが使用できなくなったのでcopyHistoryに修正
    const copyHistory = history.slice(0, stepNumber + 1); // historyの参照を修正
    const current = copyHistory[copyHistory.length - 1];
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = xIsNext ? "X" : "O";
 
    // 不要になるので削除

    setHistory(
      copyHistory.concat(
        [
          {
            squares: squares
          }
        ]
      )
    ); // historyの更新部分を修正
    setStepNumber(copyHistory.length);
    setXIsNext(!xIsNext);
  };

  const jumpTo = (step) => {
    setStepNumber(step);
    setXIsNext((step % 2) === 0);
  };

  // 不要になるので削除
  const current = history[stepNumber]; // 修正はしていないが、historyはHooksを直接参照するようになった
  const winner = calculateWinner(current.squares);

  const moves = history.map((step, move) => { // 修正はしていないが、historyはHooksを直接参照するようになった
    const desc = move ?
      'Go to move #' + move :
      'Go to game start';
    return (
      <li key={move}>
        <button onClick={() => jumpTo(move)}>{desc}</button>
      </li>
    );
  });
  // 以下変更無し

最終結果

index.js
import ReactDOM from 'react-dom';
import './index.css';
import Game from './Game.js';

ReactDOM.render(<Game />, document.getElementById("root"));
Square.js
const Square = (props) => {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
};

export default Square;
Board.js
import Square from './Square';

const Board = (props) => {
  const renderSquare = (i) => {
    return (
      <Square
        value={props.squares[i]}
        onClick={() => props.onClick(i)}
      />
    );
  };

  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>
  );
};

export default Board;
Game.js
import {useState} from 'react';
import Board from './Board';

const Game = () => {
  const [history, setHistory] = useState(
    [
      {
        squares: Array(9).fill(null)
      }
    ]
  );
  const [stepNumber, setStepNumber] = useState(0);
  const [xIsNext, setXIsNext] = useState(true);

  const handleClick = (i) => {
    const copyHistory = history.slice(0, stepNumber + 1);
    const current = copyHistory[copyHistory.length - 1];
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = xIsNext ? "X" : "O";
    setHistory(
      copyHistory.concat(
        [
          {
            squares: squares
          }
        ]
      )
    );
    setStepNumber(copyHistory.length);
    setXIsNext(!xIsNext);
  };

  const jumpTo = (step) => {
    setStepNumber(step);
    setXIsNext((step % 2) === 0);
  };

  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={() => jumpTo(move)}>{desc}</button>
      </li>
    );
  });

  let status;
  if (winner) {
    status = "Winner: " + winner;
  } else {
    status = "Next player: " + (xIsNext ? "X" : "O");
  }

  return (
    <div className="game">
      <div className="game-board">
        <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;

まとめ

以上関数コンポーネント化でした。今回はJavaScriptのアロー関数と、ReactのHooksが主な内容でした。次はこのコードを用いて、React公式チュートリアルに記載の追加課題の実装を行いたいと思います。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?