今回はReactのチュートリアルをやっていこうと思います。
そもそもReactって何?
ReactはwebアプリのUI(ユーザーインターフェイス)を効率的に開発できる、Facebookが開発したオープンソースのJavaScriptライブラリ(便利な機能のひとまとまりにしたもの)です。
ReactはJavaScriptの中にHTMLタグをじかに埋め込むことができるJSX記法という独自の書き方を採用しています。
ReactはUIを コンポーネント(部品) 化して、コンポーネントを組み合わせてページを構成することができ、共通のUIを使いたい時などに役立ちます。
開発環境
- ubuntu (20.04)
- node.js (v 16.14.2)
- npm (v 8.5.0)
- React (v 18.0.0)
Reactの開発環境
Reactのチュートリアルでは、CodePenというサービスを使って開発をしているんですが、今回はローカル環境でやってみたいと思います。
そのため、Node.js と VScodeの環境が必要になります。申し訳ないんですが、これらは構築済みとして話を進めていきます。もし環境ができていない人は、下のURLからNodeの環境を構築してみてください。
Ubuntu20.04にnvmをインストールする方法
早速やってみよう!
プロジェクトの作成
まずはReactのプロジェクトを作っていきましょう。ubuntuで、プロジェクトを作成したい任意のディレクトリに移動して
npx create-react-app react-tutorial
として実行しましょう。そうすると、自動的にReactの骨組みが完成します。(少し時間がかかるかもしれないです)
完了すると、現在のディレクトリの直下に react-tutorial のディレクトリができているはずなので、そこに移動しましょう。
cd react-tutorial
ディレクトリに移動出来たら、次のコマンドを実行します。
npm start
そうすると、ローカルサーバーが立ち上がります。
ブラウザでhttp://localhost:3000 とうつか、ターミナル上のLocalの部分にあるリンクをクリックすることで、以下のページに遷移します。
おめでとうございます!Reactプロジェクトの完成です。
ソースコードを見てみよう
いったんCtrl + cをしてローカルサーバーを落とすか、別のターミナルのタブを開いて、先ほどのreact-tutorialのディレクトリに移動します。
code .
これで、vscodeが起動できます。さっそくファイルを編集してみましょう。
src/App.jsの中身をすべて消してください。
そして次のコードを記述します。
const App = () => {
return(
<div>
<h1>Hello, React!</h1>
</div>
)
}
export default App;
記述出来たら、Ctrl + sで保存をして、もう一度http://localhost:3000を見てみましょう。
ローカルサーバーを切っている人は、もう一度 npm startを実行してください。
Hello, React!が表示されれば成功です。
コードの解説
まずはコードを解説していきます。前述したとおり、Reactはコンポーネントを多用します。Reactのコンポーネントの記述のやり方は2通りあって、クラスコンポーネントと関数コンポーネントです。それぞれ書き方が異なります。
//クラスコンポーネント
class App extends React.Component{
render{
return(
<div>
<h1>Hello, React!</h1>
</div>
)
}
}
//関数コンポーネント
const App = () => {
return(
<div>
<h1>Hello, React!</h1>
</div>
)
}
こんな感じです。
関数コンポーネントを使え
現在では、関数コンポーネントでの書き方が主流です。と言うか、クラスコンポーネントの書き方はほぼみません。初めての人は関数コンポーネントの書き方だけで十分です(クラスのほうも読める、書けるに越したことはないけど)。ただ、関数コンポーネントが採用されているのには、 react-hooksという大きな存在があるからです。hooksについては後から出てくるので、その時に紹介します。
続き
コードの解説を続けていきます。関数コンポーネントでは、関数を定義するだけです。関数名がコンポーネント名になります。そしてreturn()の中で、返したいHTMLを埋め込みます。javascriptの関数内で、HTMLを書くことができる、これがJSX記法です。
※注意
return()の中に記述するhtml要素は一つにまとめる必要があります。
//ok
return(
<div>
<h1>Hello</h1>
<h1>World</h1>
</div>
)
//エラーが出ます
return(
<div>
<h1>Hello</h1>
<div>
<div>
<h1>World</h1>
<div>
)
divタグを使いたくないという人もいると思います。そういったときは以下の記述でいけます。
return(
<React.Fragment>
<h1>Hello</h1>
<h1>World</h1>
</React.Fragment>
)
もしくは
return(
<>
<h1>Hello</h1>
<h1>World</h1>
</>
)
export default App;
これはコンポーネントを export(輸出) しています。これをすることによって、別のファイルでもAppコンポーネントが使えるようになります。コンポーネントは基本使いまわすので、export default は忘れないようにしましょう。これが基本的なコンポーネントの書き方です。
チュートリアルを始めよう
やっとチュートリアルに入っていきます。早速ですが srcディレクトリの中のファイルをすべて削除してください。 せっかく作ったのに?と思うかもしれませんが、今すぐ消してください。普通に消してもいいですが、コマンドを使うと楽に消せます。
~/react-tutorial$ cd src
~/react-tutorial/src$ rm -f *
~/react-tutorial/src$ cd ..
次にsrc内にindex.cssを作成して、以下のコードを追記して保存します。
//ターミナル
touch index.css
vscodeで編集してください。(vimでも可)
body {
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;
}
今回cssの詳しい説明は省かせてもらいます。cssはコピペで構いません。css大嫌いです。
書いていくぅ
今回のチュートリアルでは三目並べを作ります。3×3のマス目に×と〇を交互に書いていって、一列並べたほうが勝ちというシンプルなルールのあいつです。
コンポーネントについて
Reactはコンポーネントで分けるといってきました。今回は3つのコンポーネントを作ります。1つ目は3×3の1マス分のSquareコンポーネント,3×3のマス目を並べるBoardコンポーネント, ゲーム自体の盤面であるgameコンポーネントの三つに分けて開発していきます。何いっとんだこいつは、と思うかもしれませんが、開発を進めていくうちに理解できると思います。
コンポーネントは基本的にsrc/componentsというディレクトリを作成してそこに格納していきます。
Squareコンポーネント
それでは、さっそく作っていきましょう。
mkdir src/components
touch src/components/Square.jsx
.jsxはreactの拡張子で、JSX記法であることを示しています。
const Square = () => {
return (
<button className="square">
</button>
)
}
export default Square
今のところはこれでokです。
Boardコンポーネント
touch src/components/Board.jsx
Boardコンポーネントでは、先ほど作ったSquareコンポーネントを呼び出します。exportはしているので次は呼び出したいファイルでimportを行います。
import Square from "./Square"
この一文でSquareをインポートできます。
さらに以下を追記してください。
import Square from "./Square"
const Board = () => {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
</div>
);
}
export default Board
JSX記法では{}で囲えばJavaScriptをHTMLに記述できます。便利ですね。今回では{status}を埋め込んでいますね。statusはreturnの外で、定数として定義されています。
importしたコンポーネントは
<コンポーネント名/>
今回は
<Square/> としてSquareコンポーネントを呼び出している。
Gameコンポーネント
touch components/Game.jsx
import Board from "./Board"
const Game = () => {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
</div>
);
}
export default Game;
index.js
srcの中にindex.jsを作成して以下を追記します。
touch src/index.js
import React from "react";
import ReactDOM from "react-dom";
import Game from "./components/Game";
import "./index.css"
ReactDOM.render(
<Game />,
document.getElementById('root')
);
最終的には、このindex.jsがアプリの大本のjavascriptファイルになります。これは、Gameコンポーネントを表示していると思って大丈夫です。publicディレクトリの中のindex.htmlを見てみると、rootのidを持つタグを見つけられると思います。
Propsを使ってみる。
もしかしたらこれまでの内容は初めてjavascriptを触った人にとって難しく感じたかもしれませんが、これからがとても大事なPorpsというお話になります。
基本的に、コンポーネントを呼び出すコンポーネントを親コンポーネントと言い、呼び出されるほうが子コンポーネントと言われます。
Reactでは、親が子を呼び出すときに情報を与えることができます。これがPropsです。聞いてもわからないかもしれないので、実際にやってみましょう。
propsで情報を渡す
propsは癖があるので、ソースコードを見て理解しましょう。
import Square from "./Square"
const Board = () => {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
<Square value={1} />
<Square value={2} />
<Square value={3} />
</div>
<div className="board-row">
<Square value={4} />
<Square value={5} />
<Square value={6} />
</div>
<div className="board-row">
<Square value={7} />
<Square value={8} />
<Square value={9} />
</div>
</div>
);
}
export default Board
何が変わったかわかりますか?Squareコンポーネントを呼び出す際にvalue={}として数値を入れていますね。これが親から子に渡す情報です。ただ子コンポーネントで情報を受け取る準備ができていないので、やっていきましょう。ここでpropsを用いて情報を取得していきます。
const Square = (props) => {
return (
<button className="square">
{props.value}
</button>
)
}
export default Square
コンポーネントの引数にpropsとすることで、呼び出し元(今回はBoard)からの情報を取得できます。呼び出すときはprops.親の属性名(今回はvalue) で呼び出せます。
それではローカルサーバで見てみましょう。npm startをしてlocalhost:3000にアクセスすると、
こんな感じで表示されて入れば、情報の受け渡しがうまくいっています。最初のほうは苦戦するかもしれませんが、慣れなのでこんなもんがあるんや程度の理解で大丈夫です。
インタラクティブなコンポーネント
次は、Squareコンポーネントをクリックしたときに、Xが表示されるようにしてみましょう。
一つ目のステップとして、Squareコンポーネントを構成しているbuttonタグに、onClick属性を与えていきます。
...
<button className="square" onClick={() => console.log('click')}>
{props.value}
</button>
...
buttonタグを上のように変更しましょう。
console.log()はコンソール上に引数の文字列を表示してくれます。ローカルサーバを立ち上げて、ページに移動し、F12キーを押してみてください。そうすると、DeveloperToolが起動します。開けたら、上のコンソールというタブを開いてください。そうして、今さっき作った数字をマスをどれでもいいのでクリックしてみましょう。
コンソール上でclickと表示されたはずです。このようにonClick属性で関数を渡せば、クリックしたときに処理を行うことができます。
2つ目のステップでは、Squareコンポーネントに自分がクリックされたことを覚えさせて、"X"マークをマスに埋めさせます。ここでstateというものを使っていきます。
stateについて
stateとは、ある状態を保持するものだと思ってください。Reactではstateをよく使います。ただし、先ほど紹介したクラスコンポーネントと関数コンポーネントでは、stateの書き方が違います。今回は、関数コンポーネントで書いているので、関数コンポーネントでのstateの記述方法を見ていきます。クラスのほうは割愛させてもらいます。
ここで登場するのが,React-hooksです。
React-hooks
フック (hook) は React 16.8 で追加された新機能です。 state などの React の機能を、クラスを書かずに使えるようになります。 要は、hook は Reactの state やライフサイクルの機能などを、関数コンポーネント内に使用できるようにするための関数です。これめちゃめちゃ便利です。hooksなくしてReactは語れません。
useState
今回は、stateを管理するhookを使いたいです。それにはuseStateというものがあります。このhookは頻繁に出てくるので、覚えておきましょう。さっそく使い方を見ていきましょう。
import {useState} from "react"
const [state,setState] = useState(0)
すべてのhookはreactからimportする必要があります。useStateはソースコートのように、
const [状態を保持する変数, 状態を更新する関数] = useState(初期値)
といった風な書き方をします。つまり、stateは値を格納する変数、stateの値を更新するのがsetStateという関数になります。stateの名前は何でもいいです。例えばvalueという変数名にするなら、関数のほうはsetValueになります。必ずsetを付けて、キャメルケースで書きましょう。useState()の引数には初期値を入れます。そうすることで、stateに初期値が勝手に代入されます。つまりソースコードだと、stateには0が代入されることになります。
stateの値を更新するには、setState関数を用いてsetState(更新値)とします。例えば、setState(2)とすれば、stateには2が代入されるようになります。文面だけだと難しいかもしれないので、手を動かしながら見ていきましょう。
インタラクティブなコンポーネント(続き)
import { useState } from "react"
const Square = (props) => {
const [value, setValue] = useState({ value: null })
return (
<button className="square" onClick={() => setValue({ value: "X" })}>
{value.value}
</button>
)
}
export default Square
このように変更して、ローカルサーバーを見てみましょう。
クリックするとXがつくようになったと思います。
今回,useStateの初期値にはvalueをキーとするオブジェクトを渡しています。オブジェクトを忘れた人や知らない人は、下のURLからオブジェクトを学びましょう。
ゲームを完成させよう
ここまでで基礎的な部品が整いました。次は、"X"と"O"を交互に置けるようにしていきましょう。そして、最後にどちらのプレーヤが勝利したか判定できるようにすることが必要です。
stateのリフトアップ
現時点では、それぞれの Square コンポーネントがゲームの状態を保持しています。どちらが勝利したかチェックするために、9 個のマス目の値を 1 カ所で管理するようにします。
複数の子要素からデータを集めたい、または 2 つの子コンポーネントに互いにやりとりさせたいと思った場合は、代わりに親コンポーネント内で共有の state を宣言する必要があります。親コンポーネントは props を使うことで子に情報を返すことができます。こうすることで、子コンポーネントが兄弟同士、あるいは親との間で常に同期されるようになります。
import { useState } from "react";
import Square from "./Square"
const Board = () => {
const [square, setSquare] = useState({
squares: Array(9).fill(null)
})
const handleClick = (i) => {
const squares = square.squares.slice()
squares[i] = "X"
setSquare({ squares: squares })
}
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
<Square value={square.squares[0]} onClick={() => { handleClick(0) }} />
<Square value={square.squares[1]} onClick={() => { handleClick(1) }} />
<Square value={square.squares[2]} onClick={() => { handleClick(2) }} />
</div>
<div className="board-row">
<Square value={square.squares[3]} onClick={() => { handleClick(3) }} />
<Square value={square.squares[4]} onClick={() => { handleClick(4) }} />
<Square value={square.squares[5]} onClick={() => { handleClick(5) }} />
</div>
<div className="board-row">
<Square value={square.squares[6]} onClick={() => { handleClick(6) }} />
<Square value={square.squares[7]} onClick={() => { handleClick(7) }} />
<Square value={square.squares[8]} onClick={() => { handleClick(8) }} />
</div>
</div>
);
}
export default Board
Board.jsxを上のように変更します。次にSquare.jsxも変更していきます。
const Square = (props) => {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
)
}
export default Square
コード解説
stateの管理をBoardに任せていますね。新しくsquareというstateをuseStateで定義しています。初期値にはオブジェクトを渡していますが、今回はキーをsquaresとして、長さが9の配列を渡しています。Array(9)とすると、長さが9の配列を勝手に作ってくれます。fill()メソッドを使うことでその配列すべてにnullを代入していますね。nullにしているのは、最初にX,Oを表示させないためですね。
そして、Squareコンポーネントを呼び出すときに、valueとonClickをpropsとして渡しています。valueは先ほど作った配列の中身を渡しています(初期値はnull)。onClickにはhandleClickとして関数を渡していますね。handleClickの中身を見ていきましょう。
handleClickは引数に整数をとっています。まず、square.aquares.slice()として、配列のコピーを取っています。そして、引数で渡された整数番目の配列の要素を"X"としています。最後にsetSquareでsquareを更新しています。
次に、Square.jsxのコードを解説します。今回はpropsとして、valueとonClickを受け取るので、それをそれぞれ渡していますね。onClickの関数のほうはonClick属性を指定して、クリックされた際に実行されるようになっています。
手番の処理
次に手番の処理を行っていきます。デフォルトでは、先手を “X” にします。Board で state の初期値を変えればこのデフォルト値は変更可能です。
import { useState } from "react";
import Square from "./Square"
const Board = () => {
const [square, setSquare] = useState({
squares: Array(9).fill(null),
xIsNext: true,
})
const handleClick = (i) => {
const squares = square.squares.slice()
squares[i] = square.xIsNext ? "X" : "O"
setSquare({
squares: squares,
xIsNext: !square.xIsNext
})
}
const status = `Next player: ${square.xIsNext ? "X" : "O"}`;
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
<Square value={square.squares[0]} onClick={() => { handleClick(0) }} />
<Square value={square.squares[1]} onClick={() => { handleClick(1) }} />
<Square value={square.squares[2]} onClick={() => { handleClick(2) }} />
</div>
<div className="board-row">
<Square value={square.squares[3]} onClick={() => { handleClick(3) }} />
<Square value={square.squares[4]} onClick={() => { handleClick(4) }} />
<Square value={square.squares[5]} onClick={() => { handleClick(5) }} />
</div>
<div className="board-row">
<Square value={square.squares[6]} onClick={() => { handleClick(6) }} />
<Square value={square.squares[7]} onClick={() => { handleClick(7) }} />
<Square value={square.squares[8]} onClick={() => { handleClick(8) }} />
</div>
</div>
);
}
export default Board
以下のように変更してみます。stateに新しくXIsNextを追加しました。これは、つぎの手番がXかOかを判定するためのものです。XIsNextがtrueなら次の手番はXで、falseなら次の手番はOになります。
square.xIsNext ? "X" : "O" このような記述があると思いますが、これは、三項演算子と呼ばれるものです。
(条件式) ? (trueの時の処理) : (falseの時の処理)
このように書きます。わざわざif文を使ってelseとかで処理をする必要がないので、このくらいの処理なら三項演算子を積極的に使うようにしましょう。
handleClick内のsetSquareのなかで、xIsNext: !square.xIsNextという記述があります。これはxIsNextの真偽を逆転させています。javascriptでは!を付けることで、真偽を逆転させることができます。
!(true) ==> false
!(false) ==> true
これを用いて、xIsNextの真偽を書き換えています。つまりtrueだと、falseになるし、falseだとtrueになるということです。
最後に const status = Next player: ${square.xIsNext ? "X" : "O"}
;としてstatusも変えていますね。今回も三項演算子を用いています。今回はテンプレートリテラルというものを使っています。
//テンプレートリテラル
`文字文字文字${javascript}文字文字`
テンプレートリテラルでは、``(バッククォート)で囲んで、${}とすると、文字列の中であってもjavascriptを埋め込めるようになります。なので、今回はstatusを埋め込んでいます。
これでローカルサーバを開いて、クリックしてみましょう。ちゃんとXとOが変わって押せるようになっているはずです。またNext playerのところも変化しているはずです。あとは、ゲームの勝利判定にだけですね。
勝者の判定
どちらの手番なのかを表示できたので、次にやることはゲームが決着して次の手番がなくなった時にそれを表示することです。index.js末尾に以下のヘルパー関数をコピーして貼り付けてください。
...
export default 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;
}
これは勝利を判定してくれる関数です。勝者がいる場合は"X"もしくは"O"を返してくれます。勝者がいない(ゲームが続く場合)はnullを返してくれます。少し難しいロジックなので、完璧に理解できなくても大丈夫です。
次にBoard.jsxを以下のように変更します。
import { useState } from "react";
import Square from "./Square"
import calculateWinner from "..";
const Board = () => {
const [square, setSquare] = useState({
squares: Array(9).fill(null),
xIsNext: true,
})
const handleClick = (i) => {
const squares = square.squares.slice()
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = square.xIsNext ? "X" : "O"
setSquare({
squares: squares,
xIsNext: !square.xIsNext
})
}
const winner = calculateWinner(square.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (square.xIsNext ? 'X' : 'O');
}
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
<Square value={square.squares[0]} onClick={() => { handleClick(0) }} />
<Square value={square.squares[1]} onClick={() => { handleClick(1) }} />
<Square value={square.squares[2]} onClick={() => { handleClick(2) }} />
</div>
<div className="board-row">
<Square value={square.squares[3]} onClick={() => { handleClick(3) }} />
<Square value={square.squares[4]} onClick={() => { handleClick(4) }} />
<Square value={square.squares[5]} onClick={() => { handleClick(5) }} />
</div>
<div className="board-row">
<Square value={square.squares[6]} onClick={() => { handleClick(6) }} />
<Square value={square.squares[7]} onClick={() => { handleClick(7) }} />
<Square value={square.squares[8]} onClick={() => { handleClick(8) }} />
</div>
</div>
);
}
export default Board
calculateWinnerをindex.jsからimportしています。そして、この関数にsquareステートのsquares(配列)を渡していますね。これでもし勝者がいた場合は、"O"もしくは"X"が返ってくるので、if文で、winnerがnullの時とそうではないときで場合分けを行っています。nullじゃなかったときは、勝者を表示します。nullの時は、次の手番を表示します。
handleClick関数にも手を加えています。勝者がいるもしくは置く場所がなくなったときに、その関数を強制的に抜けるようになっています。
これでローカルサーバを立ち上げてみてみましょう。
三目並べが完成しているはずです。おめでとうございます。
終わりに
今回は、reactのチュートリアルを通して、reactの基礎を学習しました。もし初めてjavascriptに触れて全然わからなかったり、javascriptの知識が曖昧で、今回のチュートリアルをよく理解できなかったりした人は、まずjavascriptをしっかり学習することをお勧めします。Reactはjavascriptベースの書き方なので、javascriptの理解がReactの理解に直結します。
また、クラスコンポーネントではなく関数コンポーネントを用いてアプリを作成しました。クラスのほうも気になる人は調べてみてください。たくさん出てきます。
今回は、useStateのhooksしか紹介できなかったのですが、Reactには、もっとたくさんの便利なhooksがあります。hooksを使いこなせると最強なので、hooksについても調べてみてください。それではHappy Hacking