プログラミング

【2022年最新】Reactチュートリアルを徹底解説【超わかりやすく】

reactチュートリアルを徹底解説

React初心者がReactを学ぶ上で、まず最初にやるといいことはReactの公式チュートリアルです。

しかし、いきなり挑戦してもわからないことが多々あると思います。
なので、今回はつまずきやすそうなところを想定した上での解説記事を書きました。

公式のチュートリアルを進めつつ、わからないことがあったらこちらに戻ってくる。
という感じで進めていただければと思います。

0からReactエンジニアになるための手順は、全てここで解説しているので合わせてどうぞ。

Reactロードマップ
【完全無料ロードマップ】Reactエンジニアへなるための方法まとめ【現役Reactエンジニアが解説】Reactを学びたいと思ってますか?Reactエンジニアから勉強方法を学ぶのが一番効率的です。なので今回は、現役のReactエンジニアがReactの学習ロードマップを無料で公開します。Reactは今とても需要が高いスキルなので、今すぐ学びましょう。...

Reactチュートリアルとは

Reactチュートリアルとは、こちらのReactの公式ページにあるチュートリアルのことです。

かなり丁寧にコンポーネントや、props、stateなどの基本を1つずつ説明してくれてるので、良下げなチュートリアルとなってます。

今回は現役のReactエンジニアの立場から、これらを解説していきます。

Reactの開発環境

今回自分は、ローカル環境で進めていきます。

ただ、公式サイトを利用すれば、ブラウザ上でチュートリアルを進めていくことができます。

「開発環境の作り方がよくわからない、、」
という方は、無理せずブラウザ上で実行でOKかと思います。

作成するもの

tutorial-gif

今回作成するのは、このような三目並べ(OXゲーム)となります。

シンプルにどちらが勝利したかを判定する機能や、何手か前の盤面に戻すことができる機能などがあります。

このチュートリアルは、HTMLとJavaScriptに多少慣れていることを想定して作られています。

なので、それらに不安のある方は、まずそちらから学習することをオススメします。

学習サイトなどは、この記事を参考にどうぞ。

【2022年】プログラミング独学に役立つおすすめ学習サイト・教材「プログラミングを独学しているけど、どのサイトで学べば良いか分からない。」そんな悩みはありませんか?本記事では、プログラミング学習に使える無料サイト、隙間時間で学べるスマホアプリ、ゲーム感覚で学べるサイトなど目的別に解説しています。...

React チュートリアル

では、実際に順々にReactチュートリアルを進めていきましょう。

概要

Reactとは?

Reactを使うファイルは、「.jsx(jsでも可)」という拡張子を使用します。

class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <Instagram />
          <WhatsApp />
          <Oculus />
        </ul>
      </div>
    );
  }

Reactの基本は、このようなHTMLタグのような形をした、「コンポーネント」と呼ばれるものです。

このコンポーネントに「props」というパラメータを渡し、「Renderメソッド」を通して画面にその要素を描画します。

また、jsx内では{}で囲めば普通のJavaScriptの機能も使用することができます。

今はとりあえず、
コンポーネントとpropsの組み合わせで要素が画面に表示される
ということだけを覚えておけばOKです。

1、スターターコードの準備

まずは公式ページのこちらから、初期のコードをコピペします。

初期のコードは以下の通りです。

class Square extends React.Component {
  render() {
    return (
      <button className="square">
        {/* TODO */}
      </button>
    );
  }
}

class Board extends React.Component {
  renderSquare(i) {
    return <Square />;
  }

  render() {
    const status = 'Next player: X';

    return (
      <div>
        <div className="status">{status}</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>
    );
  }
}

class Game extends React.Component {
  render() {
    return (
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
        <div className="game-info">
          <div>{/* status */}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}

// ========================================

ReactDOM.render(
  <Game />,
  document.getElementById('root')
);

そして、画面にこのように描画されていればOKです。

react-set

2、データをprops経由で渡す

まずは、一番の子コンポーネントである「Square」から編集していきます。

「i」という値を「props」として、子の「Square」に渡すようにしてみましょう。

書き方は、
<Square props名={渡す値} />
です。

そして、親からprops経由で渡ってきた値を表示するようにしましょう。
今回のようにクラスコンポーネントの場合は、{this.props.props名}で参照できます。

なので、「Squareコンポーネント」とその親の「Boardコンポーネント」のコードは以下のようになります。

class Square extends React.Component {
  render() {
    return (
      <button className="square">
        {this.props.value}
      </button>
    );
  }
}

class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />;
  }

  render() {
    const status = 'Next player: X';

    return (
      <div>
        <div className="status">{status}</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>
    );
  }
}

表示が以下のようになっていたらOKです。

react-props

3、インタラクティブなコンポーネントを作る

次に、より三目並べに近づけるために、クリックされた場合に”X”と表示されるようにします。

そのためには、「State」というコンポーネントの状態を持たせる必要があります。

そして、それは「constructor」というものを定義し、その中にtihs.state ={持たせたいstateのキー名: それに対応する値}と書けばOK。

「State」の値を変更したい場合は、this.setState(変更したい値)というようにすれば変更でき、「State」の値を参照したい場合はthis.state.参照したいstateの値で参照可能です。

なので、「State」を持たせた「Square」コンポーネントのコードはこのようになります。

class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null,
    };
  }

  render() {
    return (
      <button
        className="square"
        onClick={() => this.setState({value: 'X'})}
      >
        {this.state.value}
      </button>
    );
  }
}


このように盤面がクリックされた場合に状態が変更され、Xを表示するようになっていればOK。

setState

ゲームを完成させる

5、Stateのリフトアップ

前回までで、「Square」が自分の状態をそれぞれ持つことができるようになりました。
ただ、これだと1つ問題があります。

それは全ての「Squareコンポーネント」を見て勝敗を判断するのが難しくなってしまっていることです。

基本的に複数の子コンポーネントからデータを集めたい場合や、2つの子コンポーネントにやりとりをさせたい場合は、親コンポーネント内で共有の「state」を宣言しておくことがベストプラクティスとされています。

なので、今回は全ての「Square」の状態を1つ親のBoardコンポーネントで扱うようにリファクタリングします。

次に、「Square」がクリックされた時に「Board」の持っている「state」の値を変更できるようにしたいのですが、他のコンポーネントから他のコンポーネントの「state」を変更することはできません。

なので、クリックされた時に「square」の値を変更する関数を「props」として子コンポーネントに渡すようにします。

そうすることで、子コンポーネントから親コンポーネントの「state」の値を変更することが可能になります。

まとめますとやることは、次の2つ。

  • 「Square」で定義されていた「state」を「Board」にリフトアップする
  • 「state」の値を変更するための関数を定義し、「Squareコンポーネント」に「props」として渡すようにする

こうすることで、「squareコンポーネント」で書かれていた「state」の内容を「boardコンポーネント」にリフトアップが完了しました。

コードは以下のようになります。

class Square extends React.Component {
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}
      >
        {this.props.value}
      </button>
    );
  }
}

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
    };
  }

  handleClick(i) {
    const squares = this.state.squares.slice();
    squares[i] = 'X';
    this.setState({squares: squares});
  }

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }

  render() {
    const status = 'Next player: X';

    return (
      <div>
        <div className="status">{status}</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>
    );
  }
}

関数コンポーネント

今は全てのコンポーネントがclassコンポーネントで書かれています。
けれど、Reactにはもう1つ関数コンポーネントというものがあります。

これは状態を持たないコンポーネントのことです。
リファクタリングしたことで、squareコンポーネントは状態を持たなくなりました。

なので、この関数コンポーネントに書き換えることが可能です。
関数コンポーネントはクラスコンポーネントとして簡潔に書けるため、関数コンポーネントで書けるものは関数コンポーネントで書いた方がいいです。

書き変えるとこのようになります。

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

関数コンポーネントを”状態を持たないコンポーネント”と説明しましたが、hooksという機能を使えば状態を持たせられます。

今回のチュートリアルとは関係ないので、詳しく知りたい方はこの記事を参考にどうぞ。

reactチュートリアルをリファクタリング
ReactチュートリアルをTypeScriptでリファクタリング【&Hooks】React公式サイトには入門に最適なチュートリアルが用意されています。 マルバツゲームの作成を通して、Reactとその構文について...

6、手番の処理

今の状態だと、どちらのプレイヤーもXになってしまっているため不完全です。
なので、最初のプレイヤーはXを使い、あとのプレイヤーはOになるようにしましょう。

これも「state」を使えば実現することができます。
「xIsNext」という「stateを」追加して、XとOが交互に表示されるようにし、Next Playerの文言も切り替わるようにしましょう。

コードは以下の通りになります。



class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true,
    };
  }

  handleClick(i) {
    const squares = this.state.squares.slice();
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
    });
  }

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }

  render() {
    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');

    return (
      <div>
        <div className="status">{status}</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>
    );
  }
}

7、ゲームの勝者の判定

では、最後にゲームの勝者を判定する機能を追加します。

まずは、このゲームの勝敗が付いたかどうかの判断をするための関数を追加します。

そして、これを画面描画時の「render関数」に追加します。

最後にゲームの決着がついている場合は、盤面を押しても何も起こらないように「handleClick関数」を変更します。

また、今の状態だとすでに埋まっている盤面を押しても、表示が切り替わってしまうバグがあるので同時に修正します。

これでOXゲーム自体は完成しました。

コードは以下の通りです。

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

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true,
    };
  }

  handleClick(i) {
    const squares = this.state.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
    });
  }

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }

  render() {
    const winner = calculateWinner(this.state.squares);
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }

    return (
      <div>
        <div className="status">{status}</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>
    );
  }
}

class Game extends React.Component {
  render() {
    return (
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
        <div className="game-info">
          <div>{/* status */}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}

// ========================================

ReactDOM.render(
  <Game />,
  document.getElementById('root')
);

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],
  ];
  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;
}

タイムトラベル機能の追加

最後に応用として、以前の着手まで時間を巻き戻す機能を追加します。

完成形は、最初に見せたGifの画像になります。

着手の履歴の保存

まずは過去の「squares」の配列を、「history」という名前の配列に保存するようにします。
なので、まずこの「history」という状態を、どのコンポーネントに持たせるべきかを考える必要があります。

8、Stateのリフトアップ、再び

機能を実現する上で、過去の履歴を「Gameコンポーネント」から呼び出せるようにしたいです。
なので、前回「Boardコンポーネント」にリフトアップしたコンポーネントを、次は「Gameコンポーネント」にリフトアップしていきます。

また同じように、「state」を変更するための「handleClick関数」も一緒に「Gameコンポーネント」に移動させます。

さらに、「history」という変数を追加して、勝敗の判断も「Gameコンポーネント」でできるようにします。

ここまでのコードはこんな感じになります。

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

class Board extends React.Component {
  renderSquare(i) {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.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>
      </div>
    );
  }
}

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [{
        squares: Array(9).fill(null)
      }],
      xIsNext: true
    };
  }

  handleClick(i) {
    const history = this.state.history;
    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
      }]),
      xIsNext: !this.state.xIsNext,
    });
  }
  
  render() {
    const history = this.state.history;
    const current = history[history.length - 1];
    const winner = calculateWinner(current.squares);

    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) => this.handleClick(i)}
          />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}

// ========================================

ReactDOM.render(
  <Game />,
  document.getElementById('root')
);

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],
  ];
  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;
}

9、過去の着手の表示

次に、「hisotry」を元に過去の盤面に戻るためのボタンを実装します。

しかし、ただ普通にボタンを実装しても、コンソール上にはこのような警告が出てるかと思います。
Warning: Each child in an array or iterator should have a unique “key” prop. Check the render method of “Game”.

Reactはリストが変更になった場合、どのアイテムが変更になったかを知る必要があります。
どのアイテムが追加、削除、並び替えされたのかをReactに伝えなければReactは正しく動かない場合があるのです。

なので、Reactにおいてはリストのアイテムにそれぞれ一意のものを「Key」として設定する必要があります。
なので今回は「move」をリストの「key」として使用します。

10、タイムトラベルの実装

最後に、きちんと過去の盤面に戻れるようなタイムトラベルの機能を実装していきます。

なので、まずは「state」に「stepNumber」という、今何手目の状態を見ているのかを表す状態を追加します。

次に、過去の盤面に戻るための「jumpTo関数」を追加し、「stepNumber」を切り替えられるようにします。

また、偶数盤目の手の時は「xIsNext」をtrueにする必要があるので、その機能も実装します。

最後、「stepNumber」の「hisotry」を表示するように修正すれば完了です。

お疲れ様です。
これでチュートリアルは完璧に終わりました。

完成のコードをこちらと見比べて見てください。

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

class Board extends React.Component {
  renderSquare(i) {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.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>
      </div>
    );
  }
}

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null)
        }
      ],
      stepNumber: 0,
      xIsNext: true
    };
  }

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

  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={() => this.jumpTo(move)}>{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 => this.handleClick(i)}
          />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{moves}</ol>
        </div>
      </div>
    );
  }
}

// ========================================

ReactDOM.render(<Game />, document.getElementById("root"));

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

Reactチュートリアル まとめ

お疲れ様でした。
これで、Reactの基礎は理解することができたかと思います。

今後はこのチュートリアルを元にした応用の記事も書いていきたいと考えていますので楽しみにしていてください。

まだ理解できたか不安な場合は何度も繰り返して理解できるようにしましょう。
基本的に何事も1度やっただけでは理解できないものなので。

また、僕のブログではReactエンジニアを目指す上でおすすめできるプログラミングスクールについてをまとめた記事があります。

Reactエンジニアになりたい人はこちらからどうぞ

Reactロードマップ
【完全無料ロードマップ】Reactエンジニアへなるための方法まとめ【現役Reactエンジニアが解説】Reactを学びたいと思ってますか?Reactエンジニアから勉強方法を学ぶのが一番効率的です。なので今回は、現役のReactエンジニアがReactの学習ロードマップを無料で公開します。Reactは今とても需要が高いスキルなので、今すぐ学びましょう。...

おわり

ABOUT ME
ひのしん
【執筆者情報】ひのしんです。「正しい努力をすれば誰でも成果は出せる」 をモットーに発信しています。元ニートの現フリーランスエンジニアです。 プログラミング歴は4年ほどで、TOEIC885点を取得。科学と実体験を元にした効率的で正しいスキルアップ(プログラミング・英語・ブログ)の方法を発信してます。
\\こちらの記事が大好評です//