Reactチュートリアル: Intro To React【日本語翻訳】

2016年10月にReactの公式ドキュメントが刷新されました。それに伴いチュートリアルの内容も新しいものとなりました。当ブログでは以前のチュートリアルも日本語に翻訳していましたので、今回も翻訳しました。自分のReactの知識をフル活用して、それなりにわかりやすく訳したつもりです。ぜひ参考にしてください。

reactjs

当記事は以下のReactの公式チュートリアルを日本語に翻訳したものです。

まずこのチュートリアルを進める上で、以下のCodePenのコードを使用します。フォークしてから始めてください。


以下、チュートリアルの日本語訳となります。

What We’re Building – 何を作るのか

Today, we’re going to build an interactive tic-tac-toe game. We’ll assume some familiarity with HTML and JavaScript but you should be able to follow along even if you haven’t used them before.

今回は、インタラクティブなtic-tac-toeゲーム(三目並べ)を作っていきます。ある程度のHTMLとJavaScriptの知識があることを前提としていますが、それらをこれまで使ってこなかった方でも理解できるようになっています。

If you like, you can check out the final result here: Final Result. Try playing the game. You can also click on a link in the move list to go “back in time” and see what the board looked like just after that move was made.

最終的な結果をこちら(tic-tac-toeゲーム)でチェックすることもできますので、まずはゲームで遊んでみてください。Move(動作)リストのリンクをクリックすると「元に戻す」ことができ、その動作の直後にボードがどのようになったかを確認することができます。

What is React? – Reactとは?

React is a declarative, efficient, and flexible JavaScript library for building user interfaces.

Reactはユーザーインターフェースを構築するためのJavaScriptライブラリです。宣言的に書け、効率的に動作し、柔軟性に富んでいます。

React has a few different kinds of components, but we’ll start with React.Component subclasses:

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" />
Code

We’ll get to the funny XML-like tags in a second. Your components tell React what you want to render – then React will efficiently update and render just the right components when your data changes.

すぐにちょっと変わったXML風のタグに目が行ったかと思います。このタグによりコンポーネントはどうレンダリングしたいかをReactに伝えています。これによりReactは、データが変更された時に、効率的にアップデートを行い、正しくコンポーネントをレンダリングしています。

Here, ShoppingList is a React component class, or React component type. A component takes in parameters, called props, and returns a hierarchy of views to display via the render method.

この例のShoppingListはReact component class、すなわちReactのコンポーネントとなります。コンポーネントはpopsと呼ばれるパラメータを内部に取り込んで、renderメソッドの戻り値としてviewの階層を返します。

The render method return a description of what you want to render, and then React takes that description and renders it to the screen. In particular, render returns a React element, which is a lightweight description of what to render. Most React developers use a special syntax called JSX which makes it easier to write these structures. The <div /> syntax is transformed at build time to React.createElement(‘div’). The example above is equivalent to:

renderメソッドは、あなたが何をレンダリングしたいかの記述要素を返します。その時Reactはその記述要素を受け取り、スクリーンにレンダリングします。とりわけrenderメソッドが返しているのは、何をレンダリングすべきか記述するための軽量な記述要素であるReact elementとなります。ほとんどのReactの開発者たちは、これらの構造を容易に記述するために、JSXと呼ばれる特別な構文を使っています。JSXで<div />と書いた構文は、ビルド時にReact.createElement(‘div’)というReact elementに変換されます。上記の例のJSXで書いたものは、以下のように書いても同じものがレンダリングされます。

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

You can put any JavaScript expression within braces inside JSX. Each React element is a real JavaScript object that you can store in a variable or pass around your program.

JavaScriptのコードも波括弧内であればJSXの中に書くことができます。React elementは純粋なJavaScriptのオブジェクトなので、変数の中に保存したり、あなたのプログラムのあちこちに渡すこともできます。

The ShoppingList component only renders built-in DOM components, but you can compose custom React components just as easily, by writing <ShoppingList />. Each component is encapsulated so it can operate independently, which allows you to build complex UIs out of simple components.

ShoppingListコンポーネントは単に組み込みのDOMコンポーネントをレンダリングするだけのものとなっていますが、<ShoppingList />と書くことによって、容易にカスタム可能なReact elementを作ることができるようになります。コンポーネントはカプセル化されているので、それぞれ独立して操作することができます。つまりシンプルなコンポーネントから複雑なUIを構築することが可能となっています。

Getting Started – 始めよう

Start with this example: Starter Code.

こちらのサンプル(Starter Code)を使って進めていきます。

It contains the shell of what we’re building today. We’ve provided the styles so you only need to worry about the JavaScript.

このコードは今回我々が作るものの骨組みとなっています。styleについてはすでに実装済みとなっていますので、JavaScript以外は特に気にする必要はありません。

In particular, we have three components:

  • ・Square
  • ・Board
  • ・Game

今回は、以下の3つのコンポーネントを使います。

  • Square
  • Board
  • Game

The Square component renders a single <div>, the Board renders 9 squares, and the Game component renders a board with some placeholders that we’ll fill in later. None of the components are interactive at this point.

Squareコンポーネントはたったひとつの<div>をレンダリングします。Boardコンポーネントは9つの四角いマスをレンダリングします。Gameコンポーネントは後ほどOとXを埋めることになるプレースホルダーを持ったゲームボードをレンダリングします。この時点では、どのコンポーネントもインタラクティブにはなっていません。

(The end of the JS file also defines a helper function calculateWinner that we’ll use later.)

(また、JSファイルの最後の部分には、後ほど使うことになるcalculateWinner関数が定義されています。)

Passing Data Through Props – propsを通してデータを渡す

Just to get our feet wet, let’s try passing some data from the Board component to the Square component. In Board’s renderSquare method, change the code to return <Square value={i} /> then change Square’s render method to show that value by replacing {/* TODO */} with {this.props.value}.

手始めに、いくつかのデータをBoardコンポーネントからSquareコンポーネントに渡してみましょう。BoardコンポーネントのrenderSquareメソッドのreturnの値を<Square value={i} />に変更し、Squareコンポーネントのrenderメソッドの{/* TODO */}の部分を{this.props.value}に置き換えてみましょう。

Before:

変更前

React Devtools: tictac-empty

After: You should see a number in each square in the rendered output.

変更後: レンダリングされたアウトプットの中のそれぞれのマスの中に数字が表示されます。

React Devtools: tictac-numbers

An Interactive Component – インタラクティブなコンポーネント

Let’s make the Square component fill in an “X” when you click it. Try changing the tag returned in the render() function of the Square class to:

Squareコンポーネントにおいて、マスをクリックした時に「X」が表示されるようにしてみましょう。Squareクラスのrender()関数の戻り値として返されるタグを以下に変更してみましょう。

<button className="square" onClick={() => alert('click')}>
Code

This uses the new JavaScript arrow function syntax. If you click on a square now, you should get an alert in your browser.

ここでは新しいJavaScript(ES2015)のアローファンクション構文を使っています。マスをクリックすると、ブラウザにアラートが表示されるようになったはずです。

React components can have state by setting this.state in the constructor, which should be considered private to the component. Let’s store the current value of the square in state, and change it when the square is clicked. First, add a constructor to the class to initialize the state:

Reactのコンポーネントはコンストラクタにthis.stateをセットすることによって状態を持つことができます。状態は、コンポーネントにとってプライベートな値となります。現在のマス上の値を状態として保存して、マスがクリックされた時にそれを変更するようにしてみましょう。まず、状態を初期化するためにクラスにコンストラクタを追加します。

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

In JavaScript classes, you need to explicitly call super(); when defining the constructor of a subclass.

JavaScriptのクラスでは、サブクラスのコンストラクタを定義する際は、明示的にsuper();メソッドを呼び出す必要があります。

Now change the render method to display this.state.value instead of this.props.value, and change the event handler to be () => this.setState({value: ‘X’}) instead of the alert:

それでは、renderメソッドのthis.props.valueの部分をthis.state.valueに変更し、イベントハンドラのalertの部分を() => this.setState({value: ‘X’})に変更してみましょう。

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

Whenever this.setState is called, an update to the component is scheduled, causing React to merge in the passed state update and rerender the component along with its descendants. When the component rerenders, this.state.value will be ‘X’ so you’ll see an X in the grid.

this.setStateが呼ばれるたびに、コンポーネントに対する更新が予定され、Reactは渡された状態(state)をマージし、子や孫のコンポーネントと一緒にコンポーネントを再レンダリングします。コンポーネントがレンダリングを行うと、this.state.valueの値は「X」となり、グリッド上にXが表示されます。

If you click on any square, an X should show up in it.

どのマスをクリックしても、Xが表示されるようになったはずです。

Developer Tools – デベロッパーツール

The React Devtools extension for Chrome and Firefox lets you inspect a React component tree in your browser devtools.

ChromeFirefox向けの機能拡張「React Devtools」を入れておくと、ブラウザのデベロッパーツール上でReactのコンポーネントのツリーを調査することができるようになります。

react devtools extensions

It lets you inspect the props and state of any of the components in your tree.

ツリー内のすべてのコンポーネントのpropsやstateの値を確認することができます。

It doesn’t work great on CodePen because of the multiple frames, but if you log in to CodePen and confirm your email (for spam prevention), you can go to Change View > Debug to open your code in a new tab, then the devtools will work. It’s fine if you don’t want to do this now, but it’s good to know that it exists.

なお、CodePen上では複数のフレームを使っているため、うまく動きません。ただし、CodePenにログインし、Email認証(スパム防止のため)を行っていれば、「Change View > Debug」から新しいタブでコードを開くことができ、デベロッパーツールを問題なく使えるようになります。今すぐそれをする必要はないですが、その存在を知っておくとよいでしょう。

Lifting State Up – 状態を上に渡す

We now have the basic building blocks for a tic-tac-toe game. But right now, the state is encapsulated in each Square component. To make a fully-working game, we now need to check if one player has won the game, and alternate placing X and O in the squares. To check if someone has won, we’ll need to have the value of all 9 squares in one place, rather than split up across the Square components.

すでにtic-tac-toeゲームの基本的な構成要素はできあがっています。しかし現状では状態はそれぞれのSquareコンポーネントにカプセル化されています。完全に動くゲームを作るためには、ゲームに勝つプレイヤーをチェックする必要があり、OとXを交互にマス上に配置させる必要があります。どちらが勝ったかをチェックするために、9つのマスのすべての値を、分割してSquareコンポーネントに持たせるのではなく、1か所にまとめて持たせるようにします。

You might think that Board should just inquire what the current state of each Square is. Although it is technically possible to do this in React, it is discouraged because it tends to make code difficult to understand, more brittle, and harder to refactor.

ここで、Boardコンポーネントにそれぞれのマスの現在の状態が何か問い合わせさせればよいと考えることもできます。しかしReactでは、技術的にそうさせることも可能ですが、そうさせないようにしています。というのもそれを行うとコードは理解しづらいものなり、破綻しやすくなり、リファクタリングしづらくなってしまうからです。

Instead, the best solution here is to store this state in the Board component instead of in each Square – and the Board component can tell each Square what to display, like how we made each square display its index earlier.

その代わりに、それぞれのマスにではなく、Boardコンポーネントに状態を保持させることがここでの最良の解決方法となります。そして上記でそれぞれのマスにそのindexを表示させた方法と同じように、Boardコンポーネントにそれぞれのマスに何を表示させるかを伝えるようにさせます。

When you want to aggregate data from multiple children or to have two child components communicate with each other, move the state upwards so that it lives in the parent component. The parent can then pass the state back down to the children via props, so that the child components are always in sync with each other and with the parent.

複数の子コンポーネントからデータを集めたい時や、2つの子コンポーネント同士でやりとりを行わせたい時は、状態(state)を親コンポーネントまで渡すようにします。親コンポーネントはその後、状態(state)をpropsを通して子コンポーネントに渡します。そのため子コンポーネントは常に互いに同期するようになっています。また同様に親コンポーネントとも同期するようになっています。

Pulling state upwards like this is common when refactoring React components, so let’s take this opportunity to try it out. Add an initial state for Board containing an array with 9 nulls, corresponding to the 9 squares:

Reactコンポーネントをリファクタリングする時は、このように親コンポーネントに値を持ってくるようにすることが一般的です。従って、この機会に試してみましょう。9つのマスに対応する9つのnullを持った配列をBoardコンポーネントの初期stateの値としてセットしましょう。

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

We’ll fill it in later so that a board looks something like

セットした配列は、ゲームボードの状況に合わせて以下のように値を埋めていくことになります。

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

Pass the value of each square down:

マスの値を子コンポーネントに渡すようにしましょう。

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

And change Square to use this.props.value again. Now we need to change what happens when a square is clicked. The Board component now stores which squares are filled, which means we need some way for Square to update the state of Board. Since component state is considered private, we can’t update Board’s state directly from Square. The usual pattern here is pass down a function from Board to Square that gets called when the square is clicked. Change renderSquare again so that it reads:

そしてthis.props.valueを使用するために、再度Squareコンポーネントを変更しましょう。マスをクリックした時に発生させる内容を変更します。Boardコンポーネントはすでにどのマスが埋められているか記憶しています。つまりこれは、Boardの状態(state)を更新するために、Squareコンポーネントに何らかの方法を持たせる必要があるということを意味しています。コンポーネントの状態はprivateであるとみなされているので、Squareコンポーネントから直接Boardコンポーネントの状態(state)を更新することができません。ここでの一般的なパターンはBoardコンポーネントからSquareコンポーネントにマスがクリックされた時に呼び出される関数を渡すこととなります。renderSquareを再度以下のように変更しましょう。

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

Now we’re passing down two props from Board to Square: value and onClick. The latter is a function that Square can call. So let’s do that by changing render in Square to have:

これで2つのprops(valueとonClick)をBoardコンポーネントからSquareコンポーネントに渡すことができるようになりました。後者(onClick)はSquareコンポーネントが呼び出すことのできる関数です。Squareコンポーネントのrenderを変更し、関数を呼び出せるようにしましょう。

<button className="square" onClick={() => this.props.onClick()}>
Code

This means that when the square is clicked, it calls the onClick function that was passed by the parent. The onClick doesn’t have any special meaning here, but it’s popular to name handler props starting with on and their implementations with handle. Try clicking a square – you should get an error because we haven’t defined handleClick yet. Add it to the Board class:

これは、マスがクリックされた時に、親コンポーネントから渡されたonClick()関数を呼び出すということを意味しています。propsのonClickという名前はここでは特別な意味を持っていませんが、handlerのpropsに名前をつける時に、onから始め、それにhandle名を繋げるのはよくやる方法となっています。マスをクリックしてみましょう。handleClickをまだ定義していないので、おそらくエラーとなるでしょう。Boardクラスに以下を追加しましょう。

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

We call .slice() to copy the squares array instead of mutating the existing array. Jump ahead a section to learn why immutability is important.

現在の配列を変更するのではなく、.slice()を呼び出して、squares配列をコピーします。不変性(イミュータビリティ)がなぜ重要なのかは、この後のセクションで詳しく説明します。

Now you should be able to click in squares to fill them again, but the state is stored in the Board component instead of in each Square, which lets us continue building the game. Note how whenever Board’s state changes, the Square components rerender automatically.

すでにあなたはマスをクリックすることで、再度マスを埋めることができるようになっているはずです。しかし、状態(state)はそれぞれのマスにではなく、Boardコンポーネントに保存されているので、まだゲームの構築は続きます。なお、Boardコンポーネントの状態(state)が変更されるたびに、Squareコンポーネントは自動的に再レンダリングを行うということに注意してください。

Square no longer keeps its own state; it receives its value from its parent Board and informs its parent when it’s clicked. We call components like this controlled components.

Squareコンポーネントはもはや自分自身の状態を保持しません。親コンポーネントであるBoardから値を受け取り、マスがクリックされた時にそれを親コンポーネントに知らせるようになっています。このようなコンポーネントをcontrolled componentsと呼んでいます。

Why Immutability Is Important – なぜ不変性が重要なのか

In the previous code example, I suggest using the .slice() operator to copy the squares array prior to making changes and to prevent mutating the existing array. Let’s talk about what this means and why it an important concept to learn.

上記のコードにおいて、私は.slice()演算子を使って、変更を加えるのではなくsquares配列をコピーし、現在の配列の変更を防止するよう提案しました。これが何を意味し、なぜこれが学ぶべき重要なコンセプトなのか説明します。

There are generally two ways for changing data. The first, and most common method in past, has been to mutate the data by directly changing the values of a variable. The second method is to replace the data with a new copy of the object that also includes desired changes.

一般的にデータの変更方法には2つの方法があります。1つ目は従来から最もよく使われる方法で、変数の値を直接変更するメソッドとなります。2つ目は希望する変更を含んでいるオブジェクトの新たなコピーと元のデータを置き換えるメソッドとなります。

Data change with mutation - mutation(直接変更)を伴うデータの変更
var player = {score:  1}
player.score = 2 // same object mutated {score: 2}
Code
Data change without mutation - mutation(直接変更)を伴わないデータの変更
var player = {score: 1}
player = {...player, score: 2} // new object not mutated {score: 2}
Code

The end result is the same but by not mutating (or changing the underlying data) directly we now have an added benefit that can help us increase component and overall application performance.

最終的な結果は同じものとなっていますが、直接データを変更しないこと(また根本的なデータを変更すること)により、コンポーネントやアプリケーション全体のパフォーマンスを向上させることができます。

Tracking Changes - 変更を追う

Determining if a mutated object has changed is complex because changes are made directly to the object. This then requires comparing the current object to a previous copy, traversing the entire object tree, and comparing each variable and value. This process can become increasingly complex.

mutationによって変更されたオブジェクトが変更されたかどうか見極めることは複雑です。なぜなら直接オブジェクトに対して変更がなされているからです。これだと後に現在のオブジェクトを事前にコピーされたものと比較する際に、全体のオブジェクトツリーをスキャンし、それぞれの変数と値を比較する必要が出てきます。このプロセスはただただ複雑さを増やすだけとなります。

Determining how an immutable object has changed is considerably easier. If the object being referenced is different from before, then the object has changed. That's it.

イミュータブルなオブジェクトがどう変更されたかを見極めることはかなり容易です。参照されているオブジェクトが以前のものと異なっていた場合、その時オブジェクトは変更されたとみなされます。ただそれだけです。

Determining When To Re-render in React - いつ再レンダリングするか判断する

The biggest benefit of immutability in React comes when you build simple pure components. Since immutable data can more easily determine if changes have been made it also helps to determine when a component requires being re-rendered.

Reactでは、シンプルなpure component(純粋なコンポーネント)を構築する際に、イミュータビリティの最大の恩恵を受けることができます。イミュータブルなデータは、より簡単に変更されたかどうかを見極めることができるので、コンポーネントが再レンダリングする必要があるかどうかの見極めにも一役買ってくれます。

To learn how you can build pure components take a look at shouldComponentUpdate(). Also, take a look at the Immutable.js library to strictly enforce immutable data.

どのように純粋なコンポーネントを構築するか学ぶ際は、shouldComponentUpdate()をちょっと見てみてください。また、厳密にイミュータブルなデータを扱うためのライブラリImmutable.jsも見てみてください。

Functional Components - 関数コンポーネント

Back to our project, you can now delete the constructor from Square; we won't need it any more. In fact, React supports a simpler syntax called stateless functional components for component types like Square that only consist of a render method. Rather than define a class extending React.Component, simply write a function that takes props and returns what should be rendered:

プロジェクトの方に話を戻しましょう。ここで、Squareコンポーネントからconstructorを削除します。コンストラクタはこれ以上必要としません。実際、Reactは、このSquareコンポーネントのようなrenderメソッドのみからなるコンポーネントタイプであるstateless functional componentsと言うより簡易的な構文をサポートしています。propsを受け取り、何をレンダリングすべきかを返す関数を、React.Componentを継承するクラスを定義するよりもシンプルに書けます。

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

You'll need to change this.props to props both times it appears. Many components in your apps will be able to written as functional components: these components tend to be easier to write and React will optimize them more in the future.

this.propsがあればpropsに変更する必要があります。実際あなたのアプリの多くのコンポーネントはfunctional componentとして書くことが可能です。これらのコンポーネントはより簡単に書けることもあり、Reactでは今後より最適化を図っていくことになっています。

Taking Turns - 交替させる

An obvious defect in our game is that only X can play. Let's fix that.

この時点での明らかな欠陥は、Xのみしかプレイできないことです。これを修正していきましょう。

Let's default the first move to be by 'X'. Modify our starting state in our Board constructor.

最初の操作を「X」から始めるようにデフォルト値を設定しましょう。開始用の状態(state)をBoardコンポーネントのコンストラクタに追加します。

class Board extends React.Component {
  constructor() {
    super();
    this.state = {
      ...
      xIsNext: true,
    };
  }
Code

Each time we move we shall toggle xIsNext by flipping the boolean value and saving the state. Now update our handleClick function to flip the value of xIsNext.

操作するたびに、boolian値を切り替えることによってxIsNextをトグルし、状態をセーブさせます。ここでxIsNextの値を切り替えるためにhandleClick関数の中身を以下に置き換えましょう。

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

Now X and O take turns. Next, change the "status" text in Board's render so that it also displays who is next.

これでXとOが交互に表示されるようになりました。次に、Boardコンポーネントのrenderのstatusのテキストを変更し、次は誰の番なのか表示させるようにしていきます。

Declaring a Winner - 勝者を宣言する

Let's show when the game is won. A calculateWinner(squares) helper function that takes the list of 9 values has been provided for you at the bottom of the file. You can call it in Board's render function to check if anyone has won the game and make the status text show "Winner: [X/O]" when someone wins:

ゲームの勝負がついた時の表示の実装をしましょう。9つの値のリストを受け取るヘルパー関数のcalculateWinner(squares)がファイルの一番下の部分に用意されています。ゲームに誰が勝ったかチェックし、誰が勝ったかわかるようにstatusのテキストを「Winner: [X/O]」と表示させるために、この関数をBoardコンポーネントのrender関数の中で呼び出すようにします。

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

You can now change handleClick to return early and ignore the click if someone has already won the game or if a square is already filled:

どちらかがすでにゲームに勝っていた場合やマスがすでに埋められていた場合、すぐにreturnし、その後の処理を無視するためにhandleClickを変更しましょう。

handleClick(i) {
  const squares = this.state.squares.slice();
  if (calculateWinner(squares) || squares[i]) {
    return;
  }
  ...
}
Code

Congratulations! You now have a working tic-tac-toe game. And now you know the basics of React. So you're probably the real winner here.

おめでとうございます!これでちゃんと動くtic-tac-toeゲームができました。これでReactの基礎も覚えました。従って、あなたはこの点においては本当の勝者と言えるでしょう。

Storing a History - 履歴を記録する

Let's make it possible to revisit old states of the board so we can see what it looked like after any of the previous moves. We're already creating a new squares array each time a move is made, which means we can easily store the past board states simultaneously.

ゲーム中の各動作の後どのような状態だったかわかるように、ゲームボードを過去の状態に戻れるようにしてみましょう。我々はすでに新しいsquares配列を、動作の度に作っています。つまりこれは同時に過去のボードの状態を簡単に記録できるということを意味しています。

Let's plan to store an object like this in state:

状態(state)の中に以下のようなオブジェクトを記録するように設定してみましょう。

history = [
  {
    squares: [null x 9]
  },
  {
    squares: [... x 9]
  },
  ...
]
Code

We'll want the top-level Game component to be responsible for displaying the list of moves. So just as we pulled the state up before from Square into Board, let's now pull it up again from Board into Game – so that we have all the information we need at the top level.

トップレベルに動作リストを表示することを担うGameコンポーネントを設けます。そして、我々が必要とする全ての情報をトップレベルで持つために、上記でSquareコンポーネントからBoardコンポーネントに状態(state)を引き上げたように、再度BoardコンポーネントからGameコンポーネントに状態を引き上げるようにします。

First, set up the initial state for Game:

まず、Gameコンポーネントの初期状態(state)を設定します。

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

Then remove the constructor and change Board so that it takes squares via props and has its own onClick prop specified by Game, like the transformation we made for Square and Board earlier. You can pass the location of each square into the click handler so that we still know which square was clicked:

次に、propsを通してsquaresを取得し、Gameコンポーネントによって指定されたonClick propを持つようにするために、コンストラクタを削除して、Boardコンポーネントを変更します(上記でSquareコンポーネントやBoardコンポーネントを速くするために行った変更と同じように)。今まで通りどのマスがクリックされたかわかるようにするために、clickイベントのハンドラーの中にそれぞれのマスの場所を渡すようにします。

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

Game's render should look at the most recent history entry and can take over calculating the game status:

Gameコンポーネントの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');
}
...
<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>
Code

Its handleClick can push a new entry onto the stack by concatenating the new history entry to make a new history array:

handleClickにおいては、新しい履歴の配列を作るために、新しい履歴のエントリをconcatすることによって新しいエントリを履歴のスタックに追加するようにします。

handleClick(i) {
  var history = this.state.history;
  var 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,
  });
}
Code

At this point, Board only needs renderSquare and render; the state initialization and click handler should both live in Game.

この時点で、Boardコンポーネントが必要とするのはrenderSquarerenderのみとなります。状態の初期化とclickイベントのハンドラーはGameコンポーネントの中に移りました。

Showing the Moves - 動作を表示する

Let's show the previous moves made in the game so far. We learned earlier that React elements are first-class JS objects and we can store them or pass them around. To render multiple items in React, we pass an array of React elements. The most common way to build that array is to map over your array of data. Let's do that in the render method of Game:

ゲーム中の動作を一つ一つ表示してみましよう。ReactエレメントはJavaScriptの第一級オブジェクトであり、アプリケーションのあちこちで保存したり、渡したりできると上記で学びました。Reactで複数のアイテムをレンダリングするには、Reactエレメントの配列を渡します。この配列を作るための最も一般的な方法は、配列のデータをmapすることです。Gameコンポーネントのrender内でそれをやってみましょう。

const moves = history.map((step, move) => {
  const desc = move ?
    'Move #' + move :
    'Game start';
  return (
    <li>
      <a href="#" onClick={() => this.jumpTo(move)}>{desc}</a>
    </li>
  );
});
...
<ol>{moves}</ol>
Code

For each step in the history, we create a list item <li> with a link <a> inside it that goes nowhere (href="#") but has a click handler which we'll implement shortly. With this code, you should see a list of the moves that have been made in the game, along with a warning that says

履歴のそれぞれのアイテムごとに、a要素を持ったli要素のリストアイテムを作ります。このリストには飛び先はありませんが(href="#")、この後実装するclickイベントのハンドラーを持たせます。このコードにより、ゲームでなされた動作のリストが見られるようになるはずですが、以下の警告も同時に出るようになります。

Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of "Game".

警告: 配列やイテレータの各子要素はユニークな「key」propを持つべきです。「Game」コンポーネントのrenderメソッドを見直しましょう。

Let's talk about what that warning means.

この警告が何を意味しているか以下で説明します。

Keys

When you render a list of items, React always stores some info about each item in the list. If you render a component that has state, that state needs to be stored – and regardless of how you implement your components, React stores a reference to the backing native views.

Reactは、アイテムのリストをレンダリングする時に、常にリスト内の各アイテムの情報を記録するようになっています。もし状態を持ったコンポーネントをレンダリングするとしたら、その状態は記録されることを必要とします。Reactは、あなたがどのようにコンポーネントを実装したかなどお構いなしに、ネイティブviewへの参照を記録します。

When you update that list, React needs to determine what has changed. You could've added, removed, rearranged, or updated items in the list.

あなたがリストを更新する時に、Reactに何が変更されたかを判断させる必要があります。あなたはリストのアイテムを追加していたかもしれないし、削除していたかもしれないし、再配置していたかもしれないし、更新していたかもしれないので。

Imagine transitioning from

想像してみてください。リストを以下から

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

to

以下に変更したとします。

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

To a human eye, it looks likely that Alexa and Ben swapped places and Claudia was added – but React is just a computer program and doesn't know what you intended it to do. As a result, React asks you to specify a key property on each element in a list, a string to differentiate each component from its siblings. In this case, alexa, ben, claudia might be sensible keys; if the items correspond to objects in a database, the database ID is usually a good choice:

人間の目では、AlexaとBenは場所を入れ替えられ、Claudiaは追加されたとすぐにわかります。しかし、Reactはコンピュータプログラムなので、あなたがそのようにした意図がわかりません。結果として、Reactはリスト内の各要素に、各コンポーネントとその兄弟コンポーネントを識別するための文字列であるkeyプロパティを指定することを求めます。今回のケースでは、alexabenclaudiaをkeyとして使うのが賢明でしょう。もしアイテムがデータベース内のオブジェクトと一致する場合は、データーベースのIDをkeyとして使うのが良い選択です。

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

key is a special property that's reserved by React (along with ref, a more advanced feature). When an element is created, React pulls off the key property and stores the key directly on the returned element. Even though it may look like it is part of props, it cannot be referenced with this.props.key. React uses the key automatically while deciding which children to update; there is no way for a component to inquire about its own key.

keyは(refなどの高度な機能と同じく)Reactの予約語となっている特別なプロパティです。Reactは、要素が作られた時、keyプロパティを抜き取り、returnされる要素に直接そのkeyを記録します。keyはpropsの一部のように思うかもしれませんが、this.props.key で参照することはできません。Reactは、どの子コンポーネントが更新されたか判断する際に、自動的にkeyを使用します。コンポーネントが自分のkeyを問い合わせる方法はありません。

When a list is rerendered, React takes each element in the new version and looks for one with a matching key in the previous list. When a key is added to the set, a component is created; when a key is removed, a component is destroyed. Keys tell React about the identity of each component, so that it can maintain the state across rerenders. If you change the key of a component, it will be completely destroyed and recreated with a new state.

Reactは、リストが再レンダリングされた時に、新しいバージョンの全要素を取得し、直前のリストのkeyにマッチするものを探します。keyが追加されていると、コンポーネントが作られ、keyが削除されていると、コンポーネントは破棄されます。keyは、再レンダリング時においてもその状態を維持させるために、コンポーネントの同一性をReactに保証しています。もしコンポーネントのkeyを変更したとしたら、そのコンポーネントは完全に破棄され、新しい状態を持ったコンポーネントが再度作られます。

It's strongly recommended that you assign proper keys whenever you build dynamic lists. If you don't have an appropriate key handy, you may want to consider restructuring your data so that you do.

動的なリストを作る時は常に、適切なkeyを割り当てることが強く推奨されています。もし手元のコードに適切なkeyが割り当てられていないのであれば、そうするようにデータの再構築を考えてもよいでしょう。

If you don't specify any key, React will warn you and fall back to using the array index as a key – which is not the correct choice if you ever reorder elements in the list or add/remove items anywhere but the bottom of the list. Explicitly passing key={i} silences the warning but has the same problem so isn't recommended in most cases.

Reactは、keyを設定していなかった場合、警告を出し、配列のindexをkeyとして使うことでフォールバックします。もしリストの要素を再配列したり、リストの下部以外の部分にアイテムを追加/削除することがあったら、これは良い選択ではありません。明示的にkey={i}を渡すことで警告を出さないようにできますが、同様の問題は残ったままなので、これも多くのケースで推奨されません。

Component keys don't need to be globally unique, only unique relative to the immediate siblings.

コンポーネントの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. Add the key as <li key={move}> and the key warning should disappear.

動作リストのためのユニークなIDは、すでにその動作が起きた時に動作の数だけ一つ一つ持っていることになります。そのkeyを<li key={move}>として追加しましょう。keyの警告が出なくなるでしょう。

Clicking any of the move links 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, then have jumpTo update that state.

動作リストのリンクをクリックするとエラーが投げられます。なぜならjumpToが定義されていないからです。今見ている動作がどの段階のものか示すために、新しいkeyをGameコンポーネントのstateに追加しましょう。まず、stepNumber: 0を初期stateに追加し、その後にjumpToにstateを更新させましょう。

We also want to update xIsNext. We set xIsNext to true if the index of the move number is an even number.

また、xIsNextも更新しましょう。もし動作のindexが偶数であったら、xIsNextにtrueをセットします。

jumpTo(step) {
  this.setState({
    stepNumber: step,
    xIsNext: (step % 2) ? false : true,
  });
}
Code

Then update stepNumber when a new move is made by adding stepNumber: history.length to the state update in handleClick. Now you can modify render to read from that step in the history:

その後、handleClickの状態の更新部分にstepNumber: history.lengthを追加することによって新しい動作アイテムが作られた時にstepNumberを更新するようにしましょう。そうしたら履歴の一つ一つを読み込むためにrenderを修正していきます。

const current = history[this.state.stepNumber];
Code

If you click any move link now, the board should immediately update to show what the game looked like at that time. You may also want to 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. (Hint: It's easiest to .slice() off the extra elements from history at the very top of handleClick.)

今動作リストのどれかリンクをクリックすると、ボードはその時ゲームがどうだったかを表示するために瞬時に更新されるようになったはずです。さらに現在のボードの状態を読み込んだ時に、stepNumberを認識させるためにhandleClickを更新してもよいでしょう。そうすることで、過去に遡って、新しいエントリーを作るためにボード内をクリックできるようになります。(ヒント: handleClickの処理の最上部でhistoryから余計な要素を.slice()することはとても簡単です。)

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.

さて、あなたはまさに今以下の機能を持つtic-tac-toeゲームを作りました。

  • tic-tac-toeゲームとして遊べる
  • 勝負が決まった時に勝者を示す
  • ゲーム中の動作の履歴を記録する
  • 前にさかのぼってゲームの展開を確認できる

Nice work! We hope you now feel like you have a decent grasp on how React works.

よくがんばりました!Reactの仕組みについてしっかりと理解されたことを願っています。

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. 1. Display the move locations in the format "(1, 3)" instead of "6".
  2. 2. Bold the currently-selected item in the move list.
  3. 3. Rewrite Board to use two loops to make the squares instead of hardcoding them.
  4. 4. Add a toggle button that lets you sort the moves in either ascending or descending order.
  5. 5. When someone wins, highlight the three squares that caused the win.

時間に余裕があったり、さらに新しい技術を学びたい場合は、以下にいくつかの改善案をだんだん難しくなる順番で挙げてみましたのでお試しください。

  1. 動作の場所を「6」ではなく「(1, 3)」というフォーマットで表示させてみましょう。
  2. 動作リストで現在選択されているアイテムを太字にしてみましょう。
  3. マスをハードコーディングする代わりに2つのループを使って書き直してみましょう。
  4. 動作リストを昇順、降順で並び替えるトグルボタンを追加してみましょう。
  5. どちらかが勝った時に、どの手で勝ったか3つのマスをハイライトさせてみましょう。

いかがでしたでしょうか。Reactのチュートリアルの翻訳はここまでです。以前のチュートリアルはコメントリストというWebサービスの開発者にとっては実践に近いものでしたが、今回のチュートリアルは三目並べというゲームアプリの実装ということで、あまり馴染みがない方もいたのではないでしょうか。とはいえ、今回のチュートリアルもしっかりReactの基礎を学べるようになっていると思います。一つ一つ確認しながら学んでみてください。

なお、以前のチュートリアルを日本語に翻訳したものは以下となります。こちらもまだ通用するものとなっていますので、興味があったらこちらも参考にしてみてください。

また当ブログでは環境構築方法を始めとして、いくつかReactに関する記事を書いています。Reactタグをつけてまとめていますので、よかったらこちらもお読みください。

Reactビギナーズガイド ―コンポーネントベースのフロントエンド開発入門
  • 『Reactビギナーズガイド ―コンポーネントベースのフロントエンド開発入門』
  • 著者: Stoyan Stefanov, 牧野聡(翻訳)
  • 出版社: オライリージャパン
  • 発売日: 2017年3月11日

コメント一覧

  1. ピンバック: Windows 環境だけど React 入門(三目並べ)してみた【たぶん最新環境 | "油売り"と呼ばれた男

     

  • 必須

コメント