React tutorial
以下の記事を参考に、Reactのチュートリアルを行います。
Reactは、ユーザーインターフェイスを構築する、効率的で柔軟なJavaScriptライブラリです。
コンポーネントは、何の要素をレンダリングするのかを指示します。
そして、Reactは、データが変更されたときに、正しいコンポーネントだけを効率的に更新、レンダリングします。
コンポーネントは、propsと呼ばれるパラメータを受け取り、renderメソッドを介して表示するビューの階層を返します。
renderメソッドはレンダリングしたいReact要素を返します。そして、Reactはその記述を取り込んで画面にレンダリングします
ほとんどのReact開発者は、JSXという特殊な構文を使用して、これらの構造を簡単に記述することができます。
今回は3つのコンポーネントを作成します。
- Square
- Board
- Game
Squareコンポーネントは単一の<button>をレンダリングします。
Boadコンポーネントは9つの四角形をレンダリングします。
Gameコンポーネントは後で記入するプレースホルダをボードにレンダリングします。現時点では、どのコンポーネントもインタラクティブではありません。
では、実際に進めていきます。
Interactive Component
class Square extends React.Component {
constructor() {
super();
this.state = {
value: null,
};
}render() {
return (
<button className="square" onClick={() => this.setState({value: 'X'})}>
{this.state.value}
</button>
);
}
}
Squareコンポーネント内で、this.stateでstateの設定を行っています。
constructor()の中で、現在の値を設定し、squareがクリックされる度に、stateの値をXに再設定しています。
Lifting State up
以下コードを見ていきます。
class Board extends React.Component {
constructor() {
super();
this.state = {
squares: Array(9).fill(null),
};
}renderSquare(i) {
return <Square value={i} />;
}
1つの場所に9つの正方形のすべての値を設定する場合、suareそれ自身にではなく、Boardコンポーネントにsquareの状態を保存します。
ここでは、親コンポーネントをBoard、子をSquareとしています。
先程square内に設定したconstructorが、board内に設置され、squareとboardコンポーネントを同期させています。
このように上の階層のコンポーネントにstateをセットするのが、common patternになります。
また、以下のコードを
renderSquare(i) { return <Square value={i} />; }下の様に修正し、propにvalueを持たせます。
renderSquare(i) {
return <Square value={this.state.squares[i]} />;
}また、propsにvalueを持たせるのに加えて、squareをクリックすると呼び出される関数をboadからsquareに渡します。
class Board extends React.Component {
constructor() {
super();
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 //squareに以下2つの要素をもつpropsを渡して呼んでいる
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
また、handleClick関数内では既存のthis.state.squares配列をslick()を使ってコピーすることで、もとの配列の不変性を保ってます。
squareがクリックされる度にboardのthis.stateが設定し直され、squareコンポーネントは再度レンダリングされます。
子コンポーネント自体がthis.stateを保持せず、親コンポーネントから値を受取り、クリックされた時に通知をしています。
immutabilityの重要性
ここで、不変性を保つ3つの利点を挙げていきます。
- 不変性をもつと、複雑な機能を実装することを容易にします。
例えば、以前のversionのデータへの参照を保持し、必要に応じて切り替えるなどが可能になります。 - オブジェクトが変更されたかどうかの判断を、より簡単にします。
- 不変データのために、変更がいつ行われたかをより簡単に判断できるので、コンポーネントの再レンダリングがいつ必要になるかをわかりやすくなります。
Storing a history
次に、top level コンポーネントをGameとすることで、動いた偏歴を追えるリストを表示させます。
先程と同様、BoardコンポーネントのconstructorをGameに移します。
class Game extends React.Component {
constructor() {
super();
this.state = {
history: [{
squares: Array(9).fill(null),
}],
xIsNext: true,
};
}ここで、boardコンポーネントに設定された.this.stateはGameコンポーネントに移されました。それに合わせて、Board内のrenderSquareを以下の様に変更します。
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
);
}また、Game内のrender()は、move偏歴データを保持する以下の変数が必要になります。
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');
}
同様に、handleClick関数もboardからgamesに移して、gameコンポーネントに合わせた修正が必要になります。
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,
});
}
React要素はファーストクラスのJSオブジェクトであり、保存したり、渡したりすることができます。
Reactで複数の項目をレンダリングするには、React要素の配列を渡します。
その配列を構築する最も一般的な方法は、データの配列をマップすることです。 以下の様に、作成されます。
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 ?
'Move #' + move :
'Game start'; return (
<li>
<a href="#" onClick={() => this.jumpTo(move)}>{desc}</a>
</li>
);
});
またこの様に、リストの再レンダリングされると、Reactは新しいversionと古いものとを、一つのkeyで認識して結びつけます。
つまり、ある任意のkeyをもとに、どのリストが、変更されたかを判断しています。
リストのレンダリングは常に、各アイテムに関する情報も一緒にリストに保存されます。
this.stateを持つコンポーネントをレンダリングする場合、その状態を保存する必要があります。
そのコンポーネントの実装方法にかかわらず、Reactは、backing native viewへの参照を保存します。
この機能を使って、以前の状態を参照することができるようになります。
まとめ
以上がチュートリアル内容となります。
実際にgithubにあげたものが、こちらです。
まだ、機能を追加するchallengeが5つ残っています。
- “6”ではなく “(1,3)”の形式で移動場所を表示します。
- 移動リストで現在選択されている項目を太字にします。
- 2つのループを使用して、四角を作るボードを書き換えます。
- トグルボタンを追加して、移動を昇順または降順のいずれかで並べ替えることができる機能をもたせます。
- 誰かが勝利したら、その勝利の3つの正方形を強調表示します。
後ほど、実装していきたいと思います。
パット見で、そこまで簡単に作れそうなものは、まだ無さそうです。
