HTML5テクニカルノート
Create React App 入門 01: 3×3マスのゲーム盤をつくる
- ID: FN2101001
- Technique: ECMAScript 2015
- Library: React 17.0.1
ReactはFacebook社が開発している、今もっとも注目されているJavaScriptフレームワークです(「JavaScript: フレームワーク React/Vue/Angularについて」参照)。DOM(Document Object Model)の要素をデータと関連づけて(データバインディング)、データの変化に応じてページを動的に構成します。ただ、なじみにくい構文の知識や準備が求められるため、はじめての方には少し始めにくいかもしれません。公式Reactサイトには「チュートリアル:React の導入」がありますので、これを読むのがひとつの手です。CodePenにでき上がりのコードも掲げられています(サンプル001)。
もっとも、コードはコンポーネント分けされてはいるものの、ひとつのJavaScriptファイルです。「Create React App」を使えば、モジュール分けされたシングルページアプリケーション(SPA)のひな形が簡単につくれます。そのひな形に手を加えてチュートリアルと同じマルバツゲームに仕上げようというのが、この「Create React App 入門」シリーズです。
サンプル001■Tic Tac Toe
See the Pen Tic Tac Toe by Dan Abramov (@gaearon) on CodePen.
01 Reactアプリケーションのひな形をつくる
まずは、Node.jsのインストールです。予めNode.js(npm)が入っていなければなりません。安定したサポート(long-term support)が提供される「推奨版」をインストールしてください。これでnpm(Node Package Manager)が使えるようになります。
図001■Node.js公式ページ
そうしたら、Create React AppでReactのひな型アプリケーションをつくりましょう。npx create-react-app
コマンドにつづけてアプリケーション名(react-tic-tac-toe
としました)を入力すると、その名前でフォルダとアプリケーションのひな形がつくられます。
npx create-react-app react-tic-tac-toe
ノート01■Create React Appとnpxコマンド
npxは npm5.2から使えるようになったパッケージランナーツールです。
少し古いドキュメントや記事では、Create React Appのグローバルインストール(npm install -g create-react-app
)を求める記述があるかもしれません。けれど、npx
コマンドを使えば、それが要らなくなるのです。
ひな形がつくられたら、アプリケーションのディレクトリに移って、npm start
とコマンドを打てば、ローカルサーバーでひな形のページが開きます(図002)。ローカルサーバーを閉じたいときは[control]/[Ctrl] + [C]、再開には改めてnpm start
を入力してください。
cd react-tic-tac-toe npm start
図002■ひな形のReactアプリケーションのページ
02 マス目のコンポーネントをつくってアプリケーションに差し込む
src/App.js
は大もとになるコンポーネントです。ひな形アプリケーションには、ほかのコンポーネントはまだありません。コンポーネントは新たにsrc/components
というフォルダにまとめましょう。src/App.js
はsrc/App.css
とともに、このフォルダに移してください。
src/App.css
→src/components/App.css
src/App.js
→src/components/App.js
また、つぎの4つのファイルは使わないので、削除してかまいません。
×src/App.test.js
×src/index.css
×src/logo.svg
×src/setupTests.js
アプリケーションのスタイルシートsrc/components/App.css
は、前掲サンプル001のCodePenのCSSコードをそのまま使います。つぎのコードからコピーしてもよいでしょう。
src/components/App.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; }
つぎの抜き書きが、モジュールsrc/index.js
で変更するコードです。HTMLのタグ<>
がスクリプトの中に直に書き込まれています。この特有の構文が「JSX」です。ReactDOM.render()
メソッドはReactの要素をHTMLドキュメントに差し込んで描画します。第1引数が要素をもつコンポーネント(App
)、第2引数は差し込む先の親要素です。コンポーネントはフォルダを移しましたので、import
のパスは書き替えました。なお、React.StrictMode
は、開発時にアプリケーションに問題がないか検査してくれるツールです。
src/index.js//import App from './App'; import App from './components/App'; // import './index.css'; // 変更なし ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
ノート02■StrictModeの役割
StrictMode
は、アプリケーションに潜んでいる問題がないかを洗い出すツールです。<React.StrictMode>
の子孫要素に対して検査を行い、問題があれば警告を示します。アプリケーションのページに描画されるものではなく、開発モードでのみ働きます。
src/index.js
にはもう手を加えませんので、コード001に掲げます。
コード001■ページにReactのルートモジュールを加える
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
ルートの関数コンポーネントsrc/components/App.js
は、つぎのように大幅に書き替えます。import
に加えたSquare
は、このあと定める新たな子コンポーネントです。コンポーネントの関数(App()
)は、JSXで書いた要素の定めを返します。この戻り値がコンポーネントとして描画される要素です。つまり、子コンポーネントが入れ子でページに差し込まれます。なお、class
属性はclassName
に替えなければなりません。class
がECMAScriptの予約語になっているためでしょう。また、テンプレートは必ずひとつのルート要素にまとめてください。
src/components/App.jsimport Square from './Square'; // import logo from '../logo.svg'; import './App.css'; function App() { return ( /* <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> */ <div className="game"> <Square /> </div> ); } export default App;
新しい子の関数コンポーネントsrc/components/Square.js
の定めはつぎのとおりで、<button>
要素をひとつもつだけです。ここではアロー関数式=>
を用いました。function
による定義と基本的に違いはありません。関数は引数(props
)をひとつ受け取ります。今はまだ使いません。動きを確かめると、src/components/App.css
のスタイルにより、ページにはマス目がひとつ描かれます(図003)。
src/components/Square.jsconst Square = () => ( <button type="button" className="square"> </button> ); export default Square;
図003■ページに描かれたマス目
03 9マスの盤面を組み立てる
マルバツゲームの盤面は3×3の9マスです。つぎのコード002の新たなコンポーネントsrc/components/Board.js
で、マス目の子コンポーネント(Square
)を使って組み立てました。JSXの構文の中に波かっこ{}
を使うと、JavaScriptの式が書けます。つまり、変数(プロパティ)を参照したり、関数(メソッド)が呼び出せるのです。関数コンポーネントが返すテンプレートでは、3コマから1行をつくる<div>
要素の中から関数(renderSquare()
)を呼び出しています。そして、マス目(Square
)の子コンポーネントをそれぞれ受け取って加えているのです。関数の引数(i
)が子コンポーネントに、波かっこ{}
で属性(value
)の値として与えられていることにご注目ください。この値が、マス目を識別する役目を果たすのです。
コード002■3×3マスのゲーム盤面をつくる
src/components/Board.js
import Square from './Square';
const Board = () => {
const renderSquare = (i) =>
<Square value={i} />;
return (
<div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
);
};
export default Board;
ノート03■JSXとReactのimport
前掲コード002のモジュールsrc/components/Board.js
は、React
をimport
していません。これまでは、React
がimport
されていないと、JSXをコンパイルできませんでした。React 17から、この制約を外す機能が備わりました(「新しいトランスフォームは何が違うのか?」参照) 。そして、Create React Appでつくったアプリケーションには、自動的にこの機能が設定されます。
ただし、React
の関数などを用いる場合には、import
しなければなりません。前掲コード001のモジュールsrc/index.js
は、React.StrictMode
を使うため、React
をimport
しました。また、この新たな機能が設定されていない環境では、React
のimport
が必要になります(たとえば、後掲サンプル002のCodeSandbox)。
テンプレートでコンポーネントの属性に渡された値を受け取るのが、関数コンポーネントの引数(props
)です(「コンポーネントとprops」参照)。属性をプロパティとして参照すれば、値が取り出せます。子コンポーネントsrc/components/Square.js
は、つぎのようにマス目の中に属性(value
)の値をテキストとして示すようにしてみました。
src/components/Square.js// const Square = () => ( const Square = (props) => { return ( <button type="button" className="square"> {props.value} </button> ); };
ルートコンポーネント
src/components/App.js
は、差し込む子コンポーネントをマス目(Square
)から盤面(Board
)に替えます。これで、3×3の9つのマス目が描かれ、それぞれに連番整数が示されるはずです(図004)。
src/components/App.js// import Square from './Square'; import Board from './Board'; function App() { return ( <div className="game"> {/* <Square /> */} <Board /> </div> ); }
図004■9つのマス目に連番整数が示された
書き上がったマス目(src/components/Square.js
)とルート(src/components/App.js
)のコンポーネントは、つぎのコード003のとおりです。併せて、動きとそれぞれのモジュールの中身が確かめられるように、以下のサンプル002をCodeSandboxに掲げました。
コード003■3×3のマス目でゲーム盤面をつくる
src/components/Square.js
const Square = (props) => (
<button type="button" className="square">
{props.value}
</button>
);
export default Square;
import Board from './Board';
import './App.css';
function App() {
return (
<div className="game">
<Board />
</div>
);
}
export default App;
サンプル002■Create React App: Tic Tac Toe 01
Create React App 入門
- Create React App 入門 01: 3×3マスのゲーム盤をつくる
- Create React App 入門 02: クリックしたマス目にXをつける
- Create React App 入門 03: マルバツで勝ち負けを決める
- Create React App 入門 04: クラスのコンポーネントをuseState()で関数に書き替える
- Create React App 入門 05: useContextで状態をコンポーネントツリー内に共有する
- Create React App 入門 06: アプリケーションのロジックをコンテクストに切り出す
- Create React App 入門 07: ゲームの履歴をさかのぼる
- Create React App 入門 08: useMemoフックで無駄な再計算を省く
- Create React App 入門 09: useCallbackフックで無駄な処理を省く
- Create React App 入門 10: 条件によってハンドラは無効にする ー useMemoを使って
作成者: 野中文雄
作成日: 2021年01月20日 FN1912002「Create React App 入門 01: 3×3のマス目をつくる」を大幅に改訂。
Copyright © 2001-2021 Fumio Nonaka. All rights reserved.