HTML5テクニカルノート
React + ES6 入門 01: ゲームの盤面をつくる
- ID: FN1704005
- Technique: HTML5 / JavaScript
- Library: React 15.5.0
ReactはFacebook社が開発している、今もっとも注目されているJavaScriptフレームワークです。HTMLの要素を動的につくり上げて、ページに表示することができます。ただ、JSXという独特な構文を使い、公式サイトの解説にはECMAScript 6(ECMAScript 2015)が用いられるようになりました。「React + ES6 入門」では「TUTORIAL」をもとに、それらの構文も含めてご説明します。Reactをはじめて使う方は、先に「React + ES6: まずは動かしてみる」を読まれるとよいでしょう。
01 コードを書く前に素材を整える
「TUTORIAL」でつくるのは、マルバツゲームです。できあがりは、CodePenに掲げられています(サンプル001)。JavaScriptコードを見ると、HTMLのタグ<>
がスクリプトの中に書き込まれています。このReact特有の構文が「JSX」です(「いまさら聞けないReact、Virtual DOM、JSX超入門」の「JSXとBabelの使い方」参照)。もっとも、JSXはブラウザがわかるように変換(コンパイル)しなければなりません。CodePenのJavaScriptコードの上に「Babel」というボタンがあります。このBabelがコンパイラなのです。ただし、ダウンロードして試すには、手もとにJSXのコンパイル環境を整えなければなりません。また、学習に必ずしもつながらないコードも含まれているようです。そこで、できるだけ簡素な素材を揃えつつ進めることにします。
サンプル001■Tic Tac Toe
See the Pen Tic Tac Toe by Eric Nakagawa (@ericnakagawa) on CodePen.
HTMLドキュメントはつぎのとおりです。はじめのふたつの<script>
要素が、ReactのJavaScriptライブラリを読み込みます。CDNを使っていますので、ローカルにダウンロードしなくて構いません。3つめの<script>
要素には、babel-standaloneを読み込んでいます。JSXをブラウザ上でコンパイルするBabelのJavaScriptライブラリです。ReactのJSXコードを書くJavaScriptファイル(script.js)は別につくって、フォルダ(src)に納めることにします。このファイルを読み込む<script>
要素には、type
属性に"text/babel"を与えてください。<body>
には<div>
要素をひとつ加え、JavaScriptコードから参照するid
属性(container)を与えました。
sample.html<!doctype html> <html> <head> <meta charset="UTF-8"> <title>Sample</title> <link rel="stylesheet" href="css/style.css"> <script src="https://unpkg.com/react@15/dist/react.min.js"></script> <script src="https://unpkg.com/react-dom@15/dist/react-dom.min.js"></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> </head> <body> <div id="container"></div> <script src="src/script.js" type="text/babel"></script> </body> </html>
ランタイムのコンパイルはコードが手軽に試せるものの、ブラウザに負荷をかけるので効率はよくありません。公開のコンテンツでは、あらかじめ静的にコンパイルしておくのがよいでしょう(「React入門 06: ローカルサーバーの立ち上げとJSXのコンパイル」02「BebelのReact presetでJSXを標準のJavaScriptコードにコンパイルする」)。その場合、babel-standaloneは要らなくなります。
スタイルシートのCSSファイル(style.css)は、前掲サンプル001のCodePenからそのまま使います(フォルダcssに納めます)。つぎのコードをコピーしても構いません。
style.cssbody { font: 14px "Century Gothic", Futura, sans-serif; margin: 20px; } ol, ul { padding-left: 30px; } .board-row:after { clear: both; content: ""; display: table; } .status { margin-bottom: 10px; } .square { background: #fff; border: 1px solid #999; float: left; font-size: 24px; font-weight: bold; line-height: 34px; height: 34px; margin-right: -1px; margin-top: -1px; padding: 0; text-align: center; width: 34px; } .square:focus { outline: none; } .kbd-navigation .square:focus { background: #ddd; } .game { display: flex; flex-direction: row; } .game-info { margin-left: 20px; }
02 マス目の要素をつくってページに差し込む
JavaScriptファイル(script.js)に、JSX構文でReactのコードを書きます。要素をHTMLドキュメントに差し込むのはReactDOM.render()
メソッドです。第1引数がつくる要素、第2引数は差し込む先の親要素です
ReactDOM.render(つくる要素, 差し込み先親要素)
第1引数のつくる要素は、JSXでタグをじかに書き込んでも構いません。けれど、コンポーネントとしてクラスで定めることもできます。ECMAScript 6(ECMAScript 2015)には、クラス構文が採り入れられました。コンポーネントは、class
キーワードを用いてつぎのように定義します。コンポーネントのクラスは、extends
キーワードでReact.Component
を基本クラスとして継承しなければなりません。そして、render()
メソッドからJSXで書いた要素の定めを返します[*1]。クラスに備えるメソッドは、関数と同じかたちです。ただし、キーワードfunction
は添えません。
class クラス名 extends React.Component { render() { return ( 要素の定め ); } }
以下のコード001は、マス目の要素(<button>)をひとつつくって、ページの親要素(id
属性container)に差し込みます。ReactDOM.render()
メソッドの第1引数に渡すJSXのタグには、クラス名(Square)を与えました。なお、JSXのタグに定めるクラスは、オブジェクトとしての要素のclassName
プロパティに加えます。class
属性ではありませんのでご注意ください。
図001■ページに描かれたマス目
コード001■ページにマス目の要素をひとつ表示する
script.js
class Square extends React.Component {
render() {
return (
<button className="square">
</button>
);
}
}
ReactDOM.render(
<Square />,
document.getElementById('container')
);
[*1] React.Component
は抽象クラスです。継承したサブクラスは、render()
メソッドを備えなければなりません(「React.Component」の「Overview」参照)。
03 9マスの盤面を組み立てる
ゲームの盤面はマス目9つで組み立てます。盤面はつぎのように新たなクラス(Board)で定めました。JSXの構文の中に波かっこ{}
を使うと、JavaScriptの式が書けます。つまり、変数(プロパティ)を参照したり、関数(メソッド)が呼び出せるのです。render()
メソッドは、const
宣言した定数(status)の値を<div>
要素のテキストとして差し込み、3コマから1行をつくる<div>
要素の中からメソッド(renderSquare())を呼び出します。そして、メソッドからマス目(Square)の要素をひとつ受け取って加えているのです。
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> ); } }
マス目(Square)の要素を返すメソッド(renderSquare())は、引数(i)の値を属性(value)に与えています。属性は変数としてコンポーネントのprops
プロパティに納められます。すると、モジュール(Square)の要素の記述から、つぎのように波かっこ{}
でプロパティを参照すれば、変数値が取り出せるのです。
class Square extends React.Component { render() { return ( <button className="square"> {this.props.value} </button> ); } }
ReactDOM.render()
メソッドの呼び出しをつぎのように書き換えれば、組み立てられた9つのマス目それぞれに0からはじまる連番整数が示されます(図002)。
ReactDOM.render( // <Square />, <Board />, document.getElementById('container') );
図002■9つのマス目に連番整数が示された
04 クリックしたマス目に印をつける
ECMAScript 6には、アロー関数式=>
が採り入れられました。名前のない関数がつぎのように書き替えられます。
// 名前のない関数 function(引数, ..., 引数) {return 式;} // アロー関数式 (引数, ..., 引数) => 式
マス目のコンポーネント(Square)の要素に、onClick
イベントのハンドラをアロー関数式で定めてみましょう[*2]。JSXのタグにイベントハンドラをつぎのように書き加えれば、クリックしたマス目の番号が警告ダイアログに示されます。
class Square extends React.Component { render() { return ( <button className="square" onClick={() => alert(this.props.value)}> {this.props.value} </button> ); } }
コンポーネントのインスタンスにプロパティを定めたいときは、setState()
メソッドを用います。呼び出すとき引数に渡すオブジェクトは、インスタンスのstate
プロパティに納められるのです。マス目のコンポーネント(Square)の要素に加えたonClick
イベントのハンドラを、つぎのように書き替えます。すると、クリックしたマス目の要素に「X」がつきます(図003)。state
プロパティの値ははじめはnull
です。その場合、オブジェクトとして参照できません。そのため、論理演算子&&
で存在をたしかめました。
class Square extends React.Component { render() { return ( <button className="square" onClick={() => this.setState({value: 'X'})}> {this.state && this.state.value} </button> ); } }
図003■クリックしたマス目にXがつく
これで9マスのゲーム盤面ができ上がり、クリックしたマス目に「X」がつくようになりました。ここまでのスクリプトを、つぎのコード002にまとめます。合わせて以下のサンプル002にjsdo.itのコードを掲げました。JSXを使ったECMAScript 6のJavaScriptコードは[ES6]タブに書いています。
コード002■9マスのゲーム盤面をつくる
script.js
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => this.setState({value: 'X'})}>
{this.state && this.state.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>
);
}
}
ReactDOM.render(
<Board />,
document.getElementById('container')
);
[*2] アロー関数式は、本体のthis
が関数の定められたオブジォクトを参照します。名前のない関数ではthis
を束縛しないため、つぎのように書かなければなりません。
render() { let _this = this; return ( <button className="square" onClick={function() {alert(_this.props.value)}}> </button> ); }
サンプル002■React 15.5.3 + ES6: Board with nine squares
- React + ES6: まずは動かしてみる
- React + ES6 入門 02: マルバツゲームの勝ちを決める
- React + ES6 入門 03: ゲーム管理のコンポーネントを分ける
- React + ES6 入門 04: ゲームの履歴をさかのぼる
作成者: 野中文雄
作成日: 2017年4月9日
Copyright © 2001-2016 Fumio Nonaka. All rights reserved.