LoginSignup
23
25

More than 5 years have passed since last update.

Tutorial: Intro To React 日本語化+個人的メモ@2017/11

Last updated at Posted at 2017-11-22

よくあるチュートリアル翻訳記事です。個人的な勉強がてら詰まったところを補足しつつ翻訳します。
(Chromeの翻訳機能が便利過ぎて、自力で翻訳なんてしばらくしてなかったので、英語の勉強も兼ねて)
https://reactjs.org/tutorial/tutorial.html
翻訳時点のreactバージョン:16(2017/9/27〜)
翻訳開始:2017/11/22
翻訳終了:(翻訳なう/毎日ちょっとずつ進めてます:あと2セクション!)

始める前に

-Before We Start

何を作るか

-What We’re Building
インタラクティブな「○×ゲーム(tic-tac-toe game)」を作るぞ!

最終結果は これ だ。
全然分かんなくても大丈夫だ、これから段階的に作り込みながら説明していくからよ。

まぁとりあえずゲームやってみろって。( こちら の下部)
ちなみに「Go to move #x」とかのボタンを押すと、その時に戻れるぞ。

なんとなくどんなゲームか分かったなら、そのタブを閉じてくれ。
次のセクションから、シンプルなテンプレートを元に始めて行くぞ。

事前準備

-Prerequisites
まぁオレ達はHTMLとJavascriptめっちゃ得意だから、これ読んでるアンタがもし「何それ美味しいの?」状態であっても、ついて来られるようにしてあるから安心しな。

※訳者注※
Javascript知らなくても安心しな
これ、嘘だろ。って私は感じました。
Javascriptのバージョンアップのせいで全然別物だし、新しい構文使いまくってるので、苦労しました。
そこらへん噛み砕いたつもりなので、もしよろしければ最後までお付き合いください。
色々補足しているため、原文より文章量が増えている点は、ご了承ください。

もしアンタがJavascriptを学びなおしたいなら、 このガイド を読むのをお勧めするよ。
このチュートリアルでは、ES6(最新バージョンのJavascript)の機能をいくつか使っているから注意してくれ。
具体的には、アロー関数クラスletconstなどだな。
ちなみに、 Babel REPL を使えば、ES6のコードが何にコンパイルされるのかチェックできるぞ。

※訳者注※
ES6とは
ECMAScriptバージョン6のこと。Javascriptの最新バージョンだと思えば良いかと。
2015生まれ。ちなみに、このES6が新しすぎて、ブラウザが対応していない場合があります。
具体的にはIE11が未対応です。

Babel REPLとは
「ES6をES5で書くならこう書くんやで!」って翻訳してくれるREPLです。

REPLとは
「Read-eval-print loop(対話型実行環境)」。
まぁ簡単にプログラムを書いて動かせる環境だと思ってよいかと。
なんて読むんだろう?れぷる?アール イー ピー エル?

翻訳について
ここの「If you need a refresher on Javascrript」の良い訳が思いつかない・・・
これでええんかな・・・

チュートリアルの進め方

-How to Follow Along
2つのやり方があるぜ;気楽にブラウザで書くか、自分の端末に開発環境作るかだ。
気分で決めてくれれば良いぞ。

ブラウザを選んだ場合

-If You Prefer to Write Code in the Browser
こっちならさっさと始められるぞ。

まず この「スタート時点のコード」 を新しいタブで開いてくれ。
空っぽの○×ゲームのフィールドが表示されているだろ?
このコードを改修していく形で、チュートリアルを進めていくぞ。

次の「開発環境を作る場合」はスキップしていいぞ、さっさと 概要 に行きな。

※訳者注※
「概要 -Overview」までスッ飛ばすと、「困ったら -Help, I'm Stuck!」に気づけないという罠。
まぁ飛ばしてもいいような内容だけどさ。

開発環境を作る場合

-If You Prefer to Write Code in Your Editor

※訳者注※
すみません、ここ面倒なのでパスします。(ブラウザでやっつけたので)

困ったら

-Help, I'm Stuck!
もし困ったら、 コミュニティサポート を見てくれ。
特に、 Reactiflux chat は、スピーディに助けてくれるだろうよ。
もしそれでも状況の改善に至らないような時は、課題提起してくれれば、きっと助けてあげるよ。

前置きはここまでだ。さぁ、始めようか。

※訳者注※
どこに課題提起すりゃいいんだ?分からぬ。

概要

-Overview

Reactって何すか?

-What is React?
Reactとは、declarativeでefficientでflexibleな、ユーザーインターフェース(UI)用Javascriptライブラリだ。すげーだろ?

※訳者注※
「declarativeでefficientでflexibleな・・・」
直訳:宣言的で効率的で柔軟な・・・
要素的にはたしかにReactを説明する重要なポイントではあります。
が、チュートリアルやる人にとって重要だとは思えなかったので、カタカナ語でまくし立てている感じにしちゃいました。

Reactはいくつかの異なったコンポーネントを持っているが、このチュートリアルではReact.Componentから始めよう。

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

// Example usage: <ShoppingList name="Mark" />

XMLタグみたいな変なヤツに気づいたろ?
(このタグで)コンポーネントは、アンタがレンダリングしたいモノをReactに伝えるぞ。
だからReactは、アンタがデータを変更したタイミングで、効率的にアップデートしたり、狙ったコンポーネントだけに絞ったレンダリングができるんだ。

この例に示したShoppingListは、React component classもしくは、React component typeと呼ばれるモノだ。
コンポーネントはpropsと呼ばれるパラメータを取り込んで、renderメソッドの戻り値としてビューの階層を返却するぞ。

※訳者注※
「この例に示したShoppingListは・・・」
React.componentをextendsしたから、React component class化しとるぞ。
っていうことか?

「・・・ビューの階層を返却するぞ。」
a hierarchy of viewsってどう訳せばいいんや・・・

renderメソッドは、アンタがレンダリングしたいモノの 記述要素 を返却する。
Reactは返却された記述要素を受け取り、画面にレンダリングする。
具体的には、renderメソッドは React element を返却するのだ。
React elementは、何をレンダリングするのかを定義した軽量な記述要素だ。

大半のReact開発者は、これらの構造を簡単に記述できる JSX と呼ばれる独特な構文を使っている。
(JSX記法で記載されているところの)<div />は、ビルド時にReact.createElement('div')に変換される。
つまり、上に記載したShoppingListのrenderメソッドは以下のようにも書き換えられるんだ。
(一部省略しているから、完全に同じではないぞ。)

return React.createElement('div', {className: 'shopping-list'},
  React.createElement('h1', /* ... h1 children ... */),
  React.createElement('ul', /* ... ul children ... */)
);

省略していないコードを確認したい場合は こっち を見てくれ。

もしアンタが興味あるならで良いんだが、createElement()API reference にもっと詳しく書いてあるから見てくれ。このチュートリアルでは直接的には使わないつもりなので、興味があればで十分だ。
ただし、JSXはめっちゃ使う。めっちゃ使うぞ。

※訳者注※
JSXについて
JSXについては使うって言うくせに、そっちのリファレンスは紹介しないんかーい。
こっちやで
普通にググると「Adobe Illustrator」にもJSXって言うスクリプト言語?があるらしく、そっちがめっちゃヒットします。
QiitaのJSXタグも、同じ名前のせいで混ざっているので注意が必要。。。

ちなみにJSXでも、波括弧{}の中ならJavascriptを使えるぞ。
どのReact elementも、Javascriptオブジェクトだから、プログラムの中で受け渡したり、値を格納したりできるぞ。

※訳者注※
brancesって波括弧{}のことか、へー。

ShoppingListは単に組み込みのDOMコンポーネントをレンダリングするだけだが、<ShoppingList />と書くことで、簡単にcustom React componentsを作ることができる。
(React componentは)いずれのコンポーネントもカプセル化しているから、独立して稼働するぞ。
そのおかげで、単純なコンポーネントで、高度なUIを作ることができる。

※訳者注※
custom React coponentsについて
どう訳したものか・・・
カスタム可能なReactコンポーネント?カスタムしたReactコンポーネント?
単にカスタムReactコンポーネント?

はじめよう

-Getting Started
このコードから始めよう; Start Code

これから作るものの、ガラが入っているぞ。Javascriptだけ意識すればいいように作ってあるから、他のことは気にすんな。

以下の3つのコンポーネントがあることを、意識しておいてくれ。

  • Square
  • Board
  • Game

Squareコンポーネントは、 <button> だけをレンダリングし、
Boardコンポーネントは、9つの square をレンダリングし、
Gameコンポーネントは、 Board と (○と✖︎を入れる)プレースホルダー をレンダリングする。
この時点では、いずれのコンポーネントもインタラクティブではないぞ。

※訳者注※
「In particular,・・・」
原文の作者このイディオム連発するな・・・

propsを用いたデータ授受

-Passing Data Through Props
まずは、いくつかのデータをBoardからSquareへ渡してみるところから始めてみようか。
BoardのrenderSquareメソッドを、「valueを持ったpropをSquareに渡す処理」に修正してみよう。

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

※訳者注※
「まずは、・・・」
原文だと「Just to get our feet wet,・・・」。慣用句なのかな?
「まずは」っていう味も素っ気もない訳し方になってしまったが、良い訳が思いつかない。。。

Squareのrenderメソッドの{/* TODO */}{this.props.value}に置き換えて、渡されたvalueが表示できるようにしよう。

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

(ただの画像であって、Reactで出力されたものではないぞ)
修正前:
空っぽ

修正後:各四角形の中に出力された数字が見えるだろ?
数字が埋まっている状態

現時点のコードは こうなっている はずだ。

インタラクティブなコンポーネント

-An Interactive Component

ここでは、クリックした時にSquareに "X" が入るようにしよう。
まずは、Squareにある render()処理で buttonタグを返しているところを、以下のように変更しようか。

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

もし今Squareをクリックしたなら、アラートが出るだろうよ。(そう実装したからな。)
これは アロー関数 と言うJavascriptの新しい機能を使っているからだ。
onClickと言う prop に関数を渡していることに注目して欲しい。
onClick={() => alert('click')}ではなく、)onClick={alert('click')}と書いたなら、アラートはボタンを押した時ではなく、(読み込まれたタイミングで)すぐに実行されるだろうよ。

※訳者注※
アロー関数
昔で言うところのfunction式みたいなもの。性質が少し違うらしいので、詳細は リファレンス 参照のこと。
function式の例:var x = function(y) {return y * y;};

なぜ onClick={() => alert('click')}onClick={alert('click')} で違いがあるのか
少しずつ分けて文字に起こしていきます。
(あまり自信ないので、間違ってたら教えてくださいー)

なぜ onClick={alert('click')} は読込時即時実行なのか?
そもそもJSXでの波括弧{}はJavascriptの実行を示す。
onClick={alert('click')}だから分かりにくいが、
onClick={ x * y }であれば、読み込まれた時点で一度だけx*yが計算されて、その値がonClickに格納されるのが期待された動きなのは推測がつく。
同じように、onClick={alert('click')}では、Javascriptが読み込まれた時点で、alert('click')が実行される。
この場合、何度も実行される必要はない。

なぜ onClick={() => alert('click')} クリック時に実行されるのか?
onClick={() => alert('click')}自体は、所謂コールバック関数である。
関数そのものの実行結果を引き渡すような格納の仕方ではなく、
関数を指しているポインタを引き渡しているのである。
なので、読込時は、そのポインタが指しているところを把握するところまでで、
propsが呼ばれた段階(クリックされたタイミング)で実行される。
これは読込時とは別に、何度か実行したいタイミングがある時に有用。

※コールバック関数の自体は、他に詳しく記載してる記事も多いと思うので、ここではここまでとさせてもらいます。

Reactコンポーネントはコンストラクタで設定された this.state に state(状態)を保持できる。
stateは、そのコンポーネントだけのプライベートな値である。
では、Squareの現在の値をstateに格納し、Squareをクリックした時にstateが変わるようにしよう。

※訳者メモ※
private to 〜:〜にとってプライベートな

まずは、stateを初期化するために、クラスにコンストラクタを追加しよう。

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

  render() {
    return (
      <button className="square" onClick={() => alert('click')}>
        {this.props.value}
      </button>
    );
  }
}

Javascriptのクラスにおいて、サブクラスのコンストラクタ定義時には、super();を明示的に呼び出す必要がある。

※訳者注※
Javascriptのクラス
ES6から生まれた。昔はなかったよ。
リファレンスはこちら。

よし、それじゃあ、Squareのrenderメソッドを、「クリック時に現在のstateが表示される処理」にしよう。

  • <button> タグの中にあった this.props.valuethis.state.value に置きかえよう。
  • イベントハンドラの、() => alert()() => this.setState({value: 'X'}) に置きかえよう.

そしたら <button> タグは、以下のようになっているはずだ:

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

this.setState が呼ばれた時は必ず、(そのstateを保持している)コンポーネントの更新がスケジュールされる。
スケジュールされる時には、「渡されたstateの更新」と「(子孫を含め)そのコンポーネントの再レンダリング」が、Reactによってスケジュールに混ぜ込まれる。

※訳者注※
this.setStateで意識しておいた方が良いこと(上述の2行では分かりづらいので補足)
3つの処理がスケジュールに追加されることで、単に(そのstateを保持している)コンポーネントの更新だけが行われる訳ではなく、「stateの更新」→「(そのstateを保持している)コンポーネントの更新」→「子孫含めての再レンダリング」が行われる。
そのため、this.setStateは単なるsetterメソッドではなく、最終的に再レンダリングまでやっている、ちょっと特殊な存在なんやで、ってのがポイント。

もしSquareをクリックしたら、Xが表示されるだろうよ。

現時点のコードはこうなってるはずだ。

開発者ツール

-Developer Tools

ChromeFirefoxReact Devtoolsは、ブラウザの開発者ツールを拡張してくれるので、Reactコンポーネントツリーを詳しく見るのに役に立ちます。

(拡張された状態の開発者ツール)
拡張された状態の開発者ツール

※訳者注※
Chrome/Firefoxが、プラグインのリンクだとは気づかなかったよ・・・

そうすれば、ツリーにある全てのコンポーネントの props と state を確認することができるよ。

React Devtoolsをインストールしたら、ページ上の要素を右クリックして、開発者ツールを開くために"Inspect"をクリックしてくれ、そしたらReactタブが一番右端に表示されるはずだ。

ただし、CodePenで動かすためには、以下のステップが残っている。

  1. ログインするか、登録後にメールアドレスの検証をする。(スパム防止のため)
  2. "Fork"ボタンを押下する。
  3. "Change View"をクリックして、"Debug mode"を選んでください。
  4. そうして開いた新しいタブでは、開発者ツールでReactタブが表示できているはずだ。

stateを親に渡す

-Lifting State Up

私たちは○×ゲームのための基本的な構造をもう用意できている。
しかし現時点では、stateは各Squareコンポーネントそれぞれでカプセル化されてしまっている。(≒お互いの状態なんて分かりっこ無い状態にある)
ゲームを完成させるために、私たちは、プレイヤーがゲームに勝ったかどうかチェックしたり、○と×をSquareに交互に置けるようにする必要がある。(≒お互いの状態を意識しないといけない)
勝ったかどうかのチェックをするためには、私たちは9つ全てのsquareの値を一つの場所で保持する必要がある。だが、Squareコンポーネントは分割されている。(それが問題だ)

アンタは「それぞれのSquareの現在値を、Boardが確認するようにすれば良い」と考えたかも知れない。
Reactでそれを実現するのは技術的には可能だが、ソースコードが分かりづらくなるし、とても破綻しやすいし、リファクタリングも難しくので、オススメしない。

その代わり、ベストな解決策は「各Squareではなく、(親である)BoardにStateを保持すること」だ。
そうするれば、Boardが(親として情報を全て保有した状態で)各Squareに何を表示するのかを伝えることができる。
(BoardにStateを持たせるってのは何も新しいことをしようって話ではなくて、)さっき、各Squareにそのインデックスを表示させるように(stateを保持)したのと、似たようなもんだ。

※訳者注※
earlier:以前に、先ほど

(大事なことだから、まとめると、)
複数の子コンポーネントからデータを集めたい、もしくは二つの子コンポーネント同士に連携させたい時は、stateを上位に移動させて親コンポーネントに保持させろ。
そうすれば、親は、propsを介して、stateを子コンポーネント全てに戻すことができ、さらには、子コンポーネントは、子同士そして親と、常に同期することができる。

※訳者注※
The parent can then pass the state canとstateの間にあるthenはなんなんだ・・・

Ractコンポーネントをリファクタリングする時は、このように親等の上位層からstateを取得するようにするのが普通だ。ちょうど良い機会だから、(こういうリファクタリングを)試してみようか。
Boardにコンストラクタを追加して、9つのnull配列を初期stateとして設定してくれ。
なお、この9つのnull配列は9つのsquareに対応しているぞ。

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

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

○×ゲームで実際に値が格納された時には、以下のようになる。

[
  'O', null, 'X',
  'X', 'X', 'O',
  'O', null, null,
]

Boardの'renderSquare'メソッドは現在以下のようになっている:

  renderSquare(i) {
    return <Square value={i} />;
  }

Squareにpropを渡すために、以下のように修正しよう。

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

現在の code はこれじゃ

まず、私たちはSquareがクリックした時の挙動を変える必要がある。
そもそも、Squareに文字が入っているどうかを保持しているのは、Boardである。
そうなると、(Square自身が自分自身の値を保持している訳では無いので、)SquareがBoardのStateを更新する必要があり、そのためには何かしら工夫をこらす必要がある。
(大前提として、)コンポーネントのstateはプライベートであるため、Squareから直接BoardのStateを更新する事はできない。

普通は、以下のようにBoardからSquareにfunciontを引き渡す。(このfunctionはSquareがクリックされた時に呼び出される)
以下のように、BoardのrenderSquareメソッドをもう一度修正しよう。

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

返却されたelement()は読みやすいように複数行に分けている。
加えて、「丸括弧()」を、element()の前後に追加している。
「丸括弧()」を加えることで、Javascriptがreturnの後ろにセミコロンをインサートしなくなり、コードの崩壊を防げるのだ。

※訳者注※
返却されたelementを・・・
文章だけだと唐突感があって、ワケワカメ。ちょっと長めの補足。
なぜ以下のような記載ではなく、上記のようなコードにしたのかを説明しています。

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

ポイントは二つで、「改行」と「丸括弧'()'」です。
「改行」は読みやすくしているだけなので、大した話はないです。
「丸括弧()」は、必須で、これがないと、コードが壊れます。

「丸括弧()」がないと、「ちょーかしこいJavascriptくん」は、returnの直後に勝手にセミコロン';'を付け足します。
つまり以下のようなコードになってしまう訳です。

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

こうなると構文ぐちゃぐちゃになっているので、エラー吐きます。
それを防ぐために、丸括弧を足している訳でございます。

翻訳メモ
parens、parentheses:丸括弧(複数形)
paren、parenthesis:丸括弧(単数形)

じゃぁ、2つのprop(valueとonClick)をBoardからSquareに渡してみよう。
今Squareに渡したfunctionは、Squareも呼ぶことができるfunctionだ。さぁ以下ステップに沿って、Squareを修正しよう。

  • Squareのrenderメソッドの中にある、this.state.valuethis.props.valueに置き換えよう。
  • Squareのrenderメソッドの中にある、this.setState()this.props.onClick()に置き換えよう。
  • もうSquareはstateを保持していないから、Squareからコンストラクタ定義を消そう。

これらの修正が終わったら、Squareは以下のようなコードになっているはずだ。

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

今Squareがクリックされたら、Boardから渡されたonClickfunctionが呼ばれる。(だが、クラッシュするはずだ。以下に説明を記載したから、)順を追って確認してみよう。

  1. 組込式DOMの<button>コンポーネントであるonClickpropは、Reactにclickイベントリスナーをセットアップするよう指示する。
  2. ボタンがクリックされた時、Reactは、(Squareのrender()メソッドに定義された)onClickイベントハンドラを呼び出す。
  3. このイベントハンドラはthis.props.onClick()を呼び出す。(ちなみに)Squareのpropは、Boardで分割されている。
  4. Boardは'onClick={() => this.handleClick(i)}'をSquareに渡してある。なので、(SquareのonClickが)呼ばれたら、Boardのthis.handleClick(i)が実行される。
  5. handleClick()メソッドをBoardではまだ定義していないので、ここでcodeがクラッシュする。

DOMの<button>におけるonClick属性は、Reactでは特別な意味を持っていることに注意して欲しい。
しかし、(コードを見てもらえばわかるが)SquareのpropにはOnclick、BoardのメソッドにはhandleClickと、別々の名前を付けることができる。
慣習的に、Reactアプリでが、「on〜」といった名前は属性使われ、「handle〜」はハンドラーメソッドで利用される。

※訳者注※
「特別な意味(a special meaning)」
文脈や行間からも読み取れないのは自分だけでしょうか・・・?ちなみに自己解釈は以下の通りです。
インタラクティブなコンポーネントで補足していますが、)onClickを期待した動作で使うには、コールバック関数の利用が必須。
reactでコールバック関数を使う場面でも、特にDOMの<button>タグにおけるonClick属性は多用されるため、上記のような慣習的なネーミングルールが存在する。

Squareをクリックしてみろよ。ほら、エラーになっただろ?そりゃまぁ、handleClickを定義してないからな。
よし、BoardにhandleClickを足そうじゃないか。

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

current codeはこれじゃ

すでに存在している配列をいじる(突然変異させる)のではなく、.slice()メソッドを使って、Square配列をコピーしている。
なぜ、immutability(非突然変異性)が重要なのかについては、後ろのセクションで触れる。

これで再び、Squareをクリックすると値が埋まるようになった。
しかし、(前の時と異なる点があり)stateは各Squareの代わりにBoardに格納されているので、(○×の状態を把握することができるようになったので、)ゲーム作りの続き(勝ち負けのチェックや、相互に○×をおくといった機能の実装)ができるようになった。

Boardのstateが変わった時は必ず、Squareは自動的に再レンダリングすることに注意して欲しい。
Squareはもはや自分自身でstateを保持していない
Squareは、親であるBoardから値を受け取り、自分がクリックされたことを親に伝える存在になった。
このようなコンポーネントのことを「controlled components」と呼ぶ。(覚えとけよ。)

なぜImmutabilityが重要か

-Why Immutability Is Important

※訳者注※
「不変性」という直訳で理解しておくと、ちょっとニュアンスが足りない気がする。
ニュアンスで言えば、「非突然変異性」とかな感じかな。
重要な要素なのでImmutabilityという単語として理解しておいた方がいい気がする。

変更を加える前にsquare配列をコピーし、既存配列の突然変異を防いぐために.slice()を使うことを推奨した。
ここでは、コレが何を意味していて、なぜ重要なのかをお話しておこう。
データを変更するには、一般的に2つの方法がある。
1つ目は、値を直接更新することでデータ自体を突然変異させる方法だ。
2つ目は、求められた変更を含んだコピーで置き換える方法だ。

1つ目の方法:突然変異

-Data change with mutation

var player = {score: 1, name: 'Jeff'};
player.score = 2;
// Now player is {score: 2, name: 'Jeff'}

2つ目の方法:コピーで置換(非突然変異)

-Data change without mutation

var player = {score: 1, name: 'Jeff'};

var newPlayer = Object.assign({}, player, {score: 2});
// Now player is unchanged, but newPlayer is {score: 2, name: 'Jeff'}

// Or if you are using object spread syntax proposal, you can write:
// var newPlayer = {...player, score: 2};

最終的な結果は同じだ、しかし突然変異させなかった(データの根本を変えなかった)ことで、
私たちは、新たなメリットを手に入れている。
それは、アプリケーション全体のパフォーマンスとコンポーネントを増やしやすいというメリットだ。

「今のなし!」/「やっぱあり!」の実装

-Easier Undo/Redo and Time Travel

Immutability(非突然変異性)であれば、高度な機能が非常に実装しやすい。
この後、ゲームの進捗自体をタイムトラベルする機能を実装するのだが、これがちょうど良い例になる。
データの突然変異を避けることで、データの過去時点を参照したままにできるし、必要なら各時点を切り替えることもできるようになる。

変更をトラッキングする

-Tracking Changes

突然変異したオブジェクトが変更されたことがあったかどうかの確認は難しい。
なぜなら、それらの変更はオブジェクト自体を直接変容させているからだ。
現在のオブジェクトと過去のコピーとを比較し、さらにオブジェクトツリー全体を見渡し、その上で変数と値をそれぞれ比較する必要がある。
(突然変異したオブジェクトが増えるほど)これはどんどん難しくなっていってしまう。

※訳者注※
variable:変数 <=> constant:定数

immutable(非突然変異)なオブジェクトならが変更されことがあったかどうかの確認は、めっちゃ簡単だ。
もし、その参照されているオブジェクトが以前のモノと違うなら、そのオブジェクトは変わったのだ!
たったこれだけだ!

Reactがいつ再レンダリングするか決める

-Determining When to Re-render in React

Reactにおけるimmutability(非突然変異性)の最大のメリットは、シンプルで純粋なコンポーネントを作る時に感じられるだろう。
immutable(非突然変異な)データは、変更が行われたかどうかを簡単に判別できる。
つまり、コンポーネントを再レンダリングするべきかどうかの判断に役立つのだ。

shouldComponentUpdate()と、純粋なコンポーネントを作る方法を学ぶには、最高の性能(Optimizing Performance)を見てくれ。

Functional Componentsとは

-Functional Components

※訳者注※
「関数コンポーネント」という訳もあるっぽいですが、あまりポピュラーではないので、
こちらも「Functional Components」という単語として理解しておいた方が良さそう。

私たちはコンストラクタを除外したが、Reactは単にそれだけには留まらず、FunctionnalComponentsと呼ばれるシンプルな構文をサポートしてくれるようになる。(FunctionnalComponentsとは、Squareのようなrenderメソッドしか持っていないコンポーネントタイプを指す。)
React.Componentを継承したクラスを定義しましょう、というよりは、シンプルにpropsを取得し何をレンダリングすべきかを返すファンクションを書きましょう。

全てのSquareを以下のファンクションで書き換えて下さい:

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

あなたは、2つのthis.propspropsに変える必要が生まれるでしょう。
あなたのアプリの中にあるたくさんのコンポーネントは、FunctionalComponentsとして書くことができる。
(これらのコンポーネントはより簡単に書けそうなので、将来的にReactはそれらに対して最適化を行う予定だ。)

コードをクリーニングしている間、私たちはonClick={() => props.onClick()}onClick={props.onClick}に変更するのもしておこう、なぜならこのチュートリアルコードにとっては、(アロー関数を使わずとも、props.onClickが示す)関数をそのまま下にパスしてしまうだけでも十分だからだ。
onClick={props.onClick()}では動かない点に注意してほしい、なぜならその書き方だと先ほどの書き方(onClick={props.onClick})とは異なり、props.onClickを即時呼び出ししてしまうからだ。

現在のコード

ターン交代

-Taking Turns

私たちのゲームの明確な欠点は、Xしかプレイできない点だ。そこを直そう。
最初はXから始まるようにしよう。
それじゃぁ、Boardのコンストラクタの中に、私たちの最初のSateを定義しようか。

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

移動するときはいずれも、boolean値を変えてstateを保存することで、xIsNextを切り替えられるようにしよう。
それじゃぁ、xIsNextを変えるために、BoardのhandleClick関数を更新しよう。

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

これで、XとOにターンが回ってくる。続いて、Boardのrenderメソッドの中にある"status"のテキストを変えよう、だって"status"のテキストが誰が次の番なのかを示すのだから。

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

    return (
      // the rest has not changed

これらの変更を加えたら、Boardはこんな感じになっているはずだ。

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

現時点のコード

「あなたのチームの勝ちです」

-Declaring a Winner

ゲームの勝者を表示しよう。
このhelper関数をファイルの最後に追加してくれ。

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

誰がゲームに勝ったのかどうかをチェックし、勝者が決まった時に"status"のテキストを“Winner: [X/O]”にするために、Boardのrender関数からそれを呼び出すことができる。
Boardのrenderメソッドにある"status"宣言を以下のコードで置きかえよう。

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

    return (
      // the rest has not changed

あなたはBoardのhandleClickを、もし誰かがすでに勝っているか、Squareが埋まっているなら、クリックを無視して早くreturnするように、変えられる。

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

Congratulations!
もう動く「○×ゲーム(tic-tac-toe game)」になっているはずだ。
そしてReactの基本は理解できたはずだ。
そう、本当の勝者は君なんだ。

現在のコード

履歴を保持する

-Storing A History

過去のボードの状態に戻れるようにして、今までの操作でどういう状態になっていたのかを確認できるようにしよう。
コードの中では、操作する度に、新しいSquaresを作成していた。これはつまり、過去のボード状態を(新しいボードを作成するのと)同時に、簡単に保存できることを意味している。
こういうオブジェクトをstateに格納しよう:

history = [
  {
    squares: [
      null, null, null,
      null, null, null,
      null, null, null,
    ]
  },
  {
    squares: [
      null, null, null,
      null, 'X', null,
      null, null, null,
    ]
  },
  // ...
]

トップレベルのGameコンポーネントに、移動リストの表示を担当させたい。
そしたら、スクエアからボードにStateを引き上げたように、BoardからGameにstateを引き上げましょう - トップレベルで必要なすべての情報が得られるようにしましょう。
最初に、コンストラクタを追加してGameの初期Stateを設定します。

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

  render() {
    return (
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
        <div className="game-info">
          <div>{/* status */}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}

ボードを変更して、propsを介してSquareを取得し、Gameによって指定された独自のonClickpropsを設定しよう(先ほどSquareに加えた変更のように)。
各Squareの位置をクリックハンドラに渡すことができるので、クリックされたSquareがどれかが判別できる。
必要な手順の一覧は以下の通りだ。

  • Boardのconstructorを削除する
  • Boardにある renderSquare メソッドの this.state.squares[i]this.props.squares[i] に置き換える
  • Boardにある renderSquare メソッドの this.handleClick(i)this.props.onClick(i) に置き換える

Board全体のコンポーネントは次のようになります。

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

Gameのrenderメソッドでは、最新の履歴エントリが表示され、ゲームステータスの計算を引き継ぐことができます。

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

ゲームはもはや、ステータスをレンダリングしているので、「<div className="status">{status}</div>」と「ステータスを計算するコード」をBoardのrenderメソッドから消すことができる。:

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

次に、handleClickメソッド実装をBoardからGameに移す必要があります。Boardから切り取り、Gameに貼り付けることができます。
Gameのstateは構造が異なるため、少し変更する必要があります。
GameのhandleClickは、新しい履歴項目を連結して新しい履歴配列を作成することによって、新しい項目をスタックにプッシュすることができます。

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

現時点では、BoardはrenderSquarerenderだけを必要とします。Stateの初期化とクリックハンドラは両方ともGameに存在する必要があります。

View the current code.

操作を見せる

-Showing the Moves

これまでのゲームで行われた、過去の操作を見てみよう。
React要素はファーストクラスのJSオブジェクトであり、保存したり、渡したりできるということを、先ほど学びました。
Reactで複数の項目をレンダリングするには、React要素の配列を渡します。
その配列を構築する最も一般的な方法は、データの配列をマップすることです。
Gameのrenderメソッドでそれをやってみましょう:

  render() {
    const history = this.state.history;
    const current = history[history.length - 1];
    const winner = calculateWinner(current.squares);

    const moves = history.map((step, move) => {
      const desc = move ?
        'Go to move #' + move :
        'Go to game start';
      return (
        <li>
          <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>
    );
  }

View the current code.

履歴の各ステップにおいて、ボタン(この後実装するクリックハンドラを持つ<button>)を含むリストアイテム(<li>)を作成します。
このコードを使用すると、ゲーム内で行われた操作の一覧と、次の警告が表示されます。

Warning: Each child in an array or iterator should have a unique “key” prop.
Check the render method of “Game”.
(Warning:配列またはイテレータ内の各子要素には、一意の"key"プロパティが必要です。 
 "Game"の"render"メソッドを確認してください。)

この警告が何を意味するのか触れましょうか。

keys

-Keys

※訳者注※
「キー」という訳もあるっぽいですが、単なる用語に近いので、
「keys」という単語として理解しておいた方が良さそう。

アイテムのリストをレンダリングすると、Reactは必ず各アイテムの情報をリスト内に保存する。
もしstateを保持しているコンポーネントをレンダリングする時、そのstateは保存されている必要がある。
そして、コンポーネントがどのように実装されたかに関係なく、Reactは背後にある本来のビューへの参照を保存します。

※訳者注※
「the backing native view」:背後にある本来のビューって訳したけどあってんのか?

リストが更新されると、Reactは何が変わったのか判断する必要がある。
あなたは、アイテムを、リストの中で追加、削除、再編成もしくは更新することができます。

以下の内容から、

<li>Alexa: 7 tasks left</li>
<li>Ben: 5 tasks left</li>

この状態になることを想像して下さい。

<li>Ben: 9 tasks left</li>
<li>Claudia: 8 tasks left</li>
<li>Alexa: 5 tasks left</li>

人間の目には、AlexaとBenの場所が入れ替わって、Claudiaが追加されたように見える。
しかし、Reactはプログラムなので、あなたがしたことの意図を把握することはできない。

結果としてReactは、リスト内の各要素のkeyプロパティ(各コンポーネントと、その兄弟とを区別するための文字列)を明確にするよう要求してくる。
この場合、Alexa、Ben、Claudiaは、別々のkeyであるべきだ。
もし要素がデータベースのオブジェクトと一致するなら、一般的にはデータベースIDが良い選択だ。

<li key={user.id}>{user.name}: {user.taskCount} tasks left</li>

keyはReactによって予約された特別なプロパティである。(refと同じで、より高度な機能)
要素が作成されると、Reactはkeyプロパティを引っ張ってきて、返却する要素に直接keyを格納する。

propsの一部のように見えるが、this.pops.keyでは参照できない。
Reactは、どの子要素を更新するか決める際、自動的にkeyを利用するものであり、コンポーネントが自身のkeyについて問い合わせる方法はない。
リストが再レンダリングされると、Reactは新しいバージョンの中で各要素を取得し、前のリストの中で一致するkeyをもつ要素を探す。

keyがセットに追加されると、コンポーネントが作られる。keyが削除されると、コンポーネントが削除される。
KeyはReactに各コンポーネントのアイデンティティを伝える、だから再レンダリング間でstateを維持することができる。
もしコンポーネントのkeyを変更したら、完全に削除された上で、新しいstateを持って再生成される

あなたが動的リストを構築する時はいつでも、適切なkeyを用意することが、強く推奨される。
もして適切なkeyを手元に持っていないなら、データの再構築を検討して下さい。

もしどのkeyも明示していないなら、Reactは警告をだし、配列のインデックスをkeyとして利用する。
もし配列のインデックスが狂うようなこと(リスト要素の並び替え、もしくはリストの最後以外のどこかの要素を追加/削除)をするなら、「keyを明示しないことで、配列のインデックスをkeyとして利用すること」は正しい選択ではない。

key={i}のように明確に値を格納することで、警告はなくなる。
しかし、(明示的に「配列のインデックスをkeyとして利用すること」を宣言しただけなので、)依然として問題は残ったままなので、大半の場合において推奨されない。

コンポーネントのkeyはグローバルに一意である必要はないが、隣接する兄弟コンポーネント間ではユニークである必要がある。

タイムトラベルの実装

-Implementing Time Travel

For our move list, we already have a unique ID for each step: the number of the move when it happened. In the Game’s render method, add the key as

and the key warning should disappear:
    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>
      );
    });

View the current code.
Clicking any of the move buttons throws an error because jumpTo is undefined. Let’s add a new key to Game’s state to indicate which step we’re currently viewing.
First, add stepNumber: 0 to the initial state in Game’s constructor:

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

Next, we’ll define the jumpTo method in Game to update that state. We also want to update xIsNext. We set xIsNext to true if the index of the move number is an even number.
Add a method called jumpTo to the Game class:

  handleClick(i) {
    // this method has not changed
  }

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

  render() {
    // this method has not changed
  }

Then update stepNumber when a new move is made by adding stepNumber: history.length to the state update in Game’s handleClick. We’ll also update handleClick to be aware of stepNumber when reading the current board state so that you can go back in time then click in the board to create a new entry.:

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

Now you can modify Game’s render to read from that step in the history:

  render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);

    // the rest has not changed

View the current code.
If you click any move button now, the board should immediately update to show what the game looked like at that time.

仕上げ

-Wrapping Up

Now, you’ve made a tic-tac-toe game that:

  • lets you play tic-tac-toe,
  • indicates when one player has won the game,
  • stores the history of moves during the game,
  • allows players to jump back in time to see older versions of the game board.

Nice work! We hope you now feel like you have a decent grasp on how React works.
Check out the final result here: Final Result.
If you have extra time or want to practice your new skills, here are some ideas for improvements you could make, listed in order of increasing difficulty:

  1. Display the location for each move in the format (col, row) in the move history list.
  2. Bold the currently selected item in the move list.
  3. Rewrite Board to use two loops to make the squares instead of hardcoding them.
  4. Add a toggle button that lets you sort the moves in either ascending or descending order.
  5. When someone wins, highlight the three squares that caused the win.

Throughout this tutorial, we have touched on a number of React concepts including elements, components, props, and state. For a more in-depth explanation for each of these topics, check out the rest of the documentation. To learn more about defining components, check out the React.Component API reference.

本記事作成に当たってインプットにした情報

React.js チュートリアル【日本語翻訳】:偉大なる先人1
1日ひとつ強くなる>React チュートリアル 日本語翻訳:偉大なる先人2

23
25
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
23
25