React初心者がReactを学ぶ上で、まず最初にやるといいことはReactの公式チュートリアルです。
しかし、いきなり挑戦してもわからないことが多々あると思います。
なので、今回はつまずきやすそうなところを想定した上での解説記事を書きました。
公式のチュートリアルを進めつつ、わからないことがあったらこちらに戻ってくる。
という感じで進めていただければと思います。
Reactエンジニアを目指している方へ、
知識0からReactエンジニアになるまでのロードマップをまとめました。
こちらも記事もぜひ参考にどうぞ。
>>【完全無料】Reactエンジニアへなるための学習ロードマップ
スクールでReactを深く学びたい方へ、
Reactを学べるおすすめのプログラミングスクールもまとめてますので、こちらもどうぞ。
>>【最新】Reactを学べるおすすめプログラミングスクール3選
Reactのおすすめ教材が知りたい方へ、
現役のReactエンジニアがReactのおすすめ教材をまとめました。
こちらも記事もぜひ参考にどうぞ。
>>【入門】React初心者が0から知識を身につける勉強法・教材のまとめ
- 文系学部(体育学部)卒だけど、Webエンジニアに転職成功
- 現役のReactエンジニア
- 実務10ヶ月でフリーランスとして独立
- 元々勉強が苦手で、大学の偏差値は50ほど
- 血液型はO型、千葉県出身の神奈川県在住、8月生まれの現在26歳
Reactチュートリアルとは
Reactチュートリアルとは、こちらのReactの公式ページにあるチュートリアルのことです。
かなり丁寧にコンポーネントや、props、stateなどの基本を1つずつ説明してくれてるので、良下げなチュートリアルとなってます。
今回は現役のReactエンジニアの立場から、これらを解説していきます。
Reactの開発環境を作ってみよう
今回自分は、ローカル環境で進めていきます。
ただ、公式サイトを利用すれば、ブラウザ上でチュートリアルを進めていくことができます。
「開発環境の作り方がよくわからない、、」
という方は、無理せずブラウザ上で実行でOKかと思います。
作成するもの
今回作成するのは、このような三目並べ(OXゲーム)となります。
シンプルにどちらが勝利したかを判定する機能や、何手か前の盤面に戻すことができる機能などがあります。
このチュートリアルは、HTMLとJavaScriptに多少慣れていることを想定して作られています。
なので、それらに不安のある方は、まずそちらから学習することをオススメします。
学習サイトなどは、この記事を参考にどうぞ。
Reactチュートリアルをどこよりも分かりやすく解説
では、実際に順々にReactチュートリアルを進めていきましょう。
概要
Reactとは?
Reactを使うファイルは、「.jsx(jsでも可)」という拡張子を使用します。
class ShoppingList extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<Instagram />
<WhatsApp />
<Oculus />
</ul>
</div>
);
}
Reactの基本は、このようなHTMLタグのような形をした、「コンポーネント」と呼ばれるものです。
このコンポーネントに「props」というパラメータを渡し、「Renderメソッド」を通して画面にその要素を描画します。
また、jsx内では{}で囲めば普通のJavaScriptの機能も使用することができます。
今はとりあえず、
コンポーネントとpropsの組み合わせで要素が画面に表示される
ということだけを覚えておけばOKです。
1、スターターコードの準備
まずは公式ページのこちらから、初期のコードをコピペします。
初期のコードは以下の通りです。
class Square extends React.Component {
render() {
return (
<button className="square">
{/* TODO */}
</button>
);
}
}
class Board extends React.Component {
renderSquare(i) {
return <Square />;
}
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>
);
}
}
class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
そして、画面にこのように描画されていればOKです。
2、データをprops経由で渡す
まずは、一番の子コンポーネントである「Square」から編集していきます。
「i」という値を「props」として、子の「Square」に渡すようにしてみましょう。
書き方は、
<Square props名={渡す値} />
です。
そして、親からprops経由で渡ってきた値を表示するようにしましょう。
今回のようにクラスコンポーネントの場合は、{this.props.props名}で参照できます。
なので、「Squareコンポーネント」とその親の「Boardコンポーネント」のコードは以下のようになります。
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.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>
);
}
}
表示が以下のようになっていたらOKです。
3、インタラクティブなコンポーネントを作る
次に、より三目並べに近づけるために、クリックされた場合に”X”と表示されるようにします。
そのためには、「State」というコンポーネントの状態を持たせる必要があります。
そして、それは「constructor」というものを定義し、その中にtihs.state ={持たせたいstateのキー名: それに対応する値}と書けばOK。
「State」の値を変更したい場合は、this.setState(変更したい値)というようにすれば変更でき、「State」の値を参照したい場合はthis.state.参照したいstateの値で参照可能です。
なので、「State」を持たせた「Square」コンポーネントのコードはこのようになります。
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return (
<button
className="square"
onClick={() => this.setState({value: 'X'})}
>
{this.state.value}
</button>
);
}
}
このように盤面がクリックされた場合に状態が変更され、Xを表示するようになっていればOK。
ゲームを完成させる
5、Stateのリフトアップ
前回までで、「Square」が自分の状態をそれぞれ持つことができるようになりました。
ただ、これだと1つ問題があります。
それは全ての「Squareコンポーネント」を見て勝敗を判断するのが難しくなってしまっていることです。
基本的に複数の子コンポーネントからデータを集めたい場合や、2つの子コンポーネントにやりとりをさせたい場合は、親コンポーネント内で共有の「state」を宣言しておくことがベストプラクティスとされています。
なので、今回は全ての「Square」の状態を1つ親のBoardコンポーネントで扱うようにリファクタリングします。
次に、「Square」がクリックされた時に「Board」の持っている「state」の値を変更できるようにしたいのですが、他のコンポーネントから他のコンポーネントの「state」を変更することはできません。
なので、クリックされた時に「square」の値を変更する関数を「props」として子コンポーネントに渡すようにします。
そうすることで、子コンポーネントから親コンポーネントの「state」の値を変更することが可能になります。
まとめますとやることは、次の2つ。
- 「Square」で定義されていた「state」を「Board」にリフトアップする
- 「state」の値を変更するための関数を定義し、「Squareコンポーネント」に「props」として渡すようにする
こうすることで、「squareコンポーネント」で書かれていた「state」の内容を「boardコンポーネント」にリフトアップが完了しました。
コードは以下のようになります。
class Square extends React.Component {
render() {
return (
<button
className="square"
onClick={() => this.props.onClick()}
>
{this.props.value}
</button>
);
}
}
class Board extends React.Component {
constructor(props) {
super(props);
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
value={this.state.squares[i]}
onClick={() => this.handleClick(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>
);
}
}
関数コンポーネント
今は全てのコンポーネントがclassコンポーネントで書かれています。
けれど、Reactにはもう1つ関数コンポーネントというものがあります。
これは状態を持たないコンポーネントのことです。
リファクタリングしたことで、squareコンポーネントは状態を持たなくなりました。
なので、この関数コンポーネントに書き換えることが可能です。
関数コンポーネントはクラスコンポーネントとして簡潔に書けるため、関数コンポーネントで書けるものは関数コンポーネントで書いた方がいいです。
書き変えるとこのようになります。
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
関数コンポーネントを”状態を持たないコンポーネント”と説明しましたが、hooksという機能を使えば状態を持たせられます。
今回のチュートリアルとは関係ないので、詳しく知りたい方はこの記事を参考にどうぞ。
6、手番の処理
今の状態だと、どちらのプレイヤーもXになってしまっているため不完全です。
なので、最初のプレイヤーはXを使い、あとのプレイヤーはOになるようにしましょう。
これも「state」を使えば実現することができます。
「xIsNext」という「stateを」追加して、XとOが交互に表示されるようにし、Next Playerの文言も切り替わるようにしましょう。
コードは以下の通りになります。
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
};
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
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>
);
}
}
7、ゲームの勝者の判定
では、最後にゲームの勝者を判定する機能を追加します。
まずは、このゲームの勝敗が付いたかどうかの判断をするための関数を追加します。
そして、これを画面描画時の「render関数」に追加します。
最後にゲームの決着がついている場合は、盤面を押しても何も起こらないように「handleClick関数」を変更します。
また、今の状態だとすでに埋まっている盤面を押しても、表示が切り替わってしまうバグがあるので同時に修正します。
これでOXゲーム自体は完成しました。
コードは以下の通りです。
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
};
}
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
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>
);
}
}
class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
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;
}
タイムトラベル機能の追加
最後に応用として、以前の着手まで時間を巻き戻す機能を追加します。
完成形は、最初に見せたGifの画像になります。
着手の履歴の保存
まずは過去の「squares」の配列を、「history」という名前の配列に保存するようにします。
なので、まずこの「history」という状態を、どのコンポーネントに持たせるべきかを考える必要があります。
8、Stateのリフトアップ、再び
機能を実現する上で、過去の履歴を「Gameコンポーネント」から呼び出せるようにしたいです。
なので、前回「Boardコンポーネント」にリフトアップしたコンポーネントを、次は「Gameコンポーネント」にリフトアップしていきます。
また同じように、「state」を変更するための「handleClick関数」も一緒に「Gameコンポーネント」に移動させます。
さらに、「history」という変数を追加して、勝敗の判断も「Gameコンポーネント」でできるようにします。
ここまでのコードはこんな感じになります。
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
);
}
render() {
return (
<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>
);
}
}
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [{
squares: Array(9).fill(null)
}],
xIsNext: true
};
}
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,
});
}
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');
}
return (
<div className="game">
<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>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
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;
}
9、過去の着手の表示
次に、「hisotry」を元に過去の盤面に戻るためのボタンを実装します。
しかし、ただ普通にボタンを実装しても、コンソール上にはこのような警告が出てるかと思います。
Warning: Each child in an array or iterator should have a unique “key” prop. Check the render method of “Game”.
Reactはリストが変更になった場合、どのアイテムが変更になったかを知る必要があります。
どのアイテムが追加、削除、並び替えされたのかをReactに伝えなければReactは正しく動かない場合があるのです。
なので、Reactにおいてはリストのアイテムにそれぞれ一意のものを「Key」として設定する必要があります。
なので今回は「move」をリストの「key」として使用します。
10、タイムトラベルの実装
最後に、きちんと過去の盤面に戻れるようなタイムトラベルの機能を実装していきます。
なので、まずは「state」に「stepNumber」という、今何手目の状態を見ているのかを表す状態を追加します。
次に、過去の盤面に戻るための「jumpTo関数」を追加し、「stepNumber」を切り替えられるようにします。
また、偶数盤目の手の時は「xIsNext」をtrueにする必要があるので、その機能も実装します。
最後、「stepNumber」の「hisotry」を表示するように修正すれば完了です。
お疲れ様です。
これでチュートリアルは完璧に終わりました。
完成のコードをこちらと見比べて見てください。
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
);
}
render() {
return (
<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>
);
}
}
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [
{
squares: Array(9).fill(null)
}
],
stepNumber: 0,
xIsNext: true
};
}
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
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
}
]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext
});
}
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0
});
}
render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (this.state.xIsNext ? "X" : "O");
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={i => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(<Game />, document.getElementById("root"));
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;
}
【発展編】Reactチュートリアルの次のステップ
Reactチュートリアルを終えたものの、まだReactに入門した段階です。
なるべく早く次のことを学んで、Reactのスキルを高めていきましょう。
僕のブログでは様々な発展編のコンテンツを用意してあるので、そちらの記事も合わせて学習を進めてみてください。
まとめ 【最新】Reactチュートリアル徹底解説【超わかりやすく】
お疲れ様でした。
これで、Reactの基礎は理解することができたかと思います。
今後はこのチュートリアルを元にした応用の記事も書いていきたいと考えていますので楽しみにしていてください。
まだ理解できたか不安な場合は何度も繰り返して理解できるようにしましょう。
基本的に何事も1度やっただけでは理解できないものなので。
また、僕のブログではReactエンジニアを目指す上でおすすめできるプログラミングスクールについてをまとめた記事があります。
Reactエンジニアになりたい人はこちらからどうぞ
スクールでReactを深く学びたい人はこの記事をどうぞ。
Reactのおすすめ教材を知りたい人はこの記事をどうぞ。
「プログラミングスクールが高くて通えない。。」
といった悩みがこのサービスで解決します。
それは、次世代型のサブスクプログラミングスクールになります。
具体的に、このスクールは以下のことが可能です。
- 講師とのマンツーマンレッスン
- 質の高いかなりボリュームのある教材
- 講師に質問し放題
そして、お値段はたったの1,980円から。
これで高いお金を払わずに、エンジニアになることが可能です。
今なら、全額返金保証もあります。
エンジニアを目指す人も年々増えているで、お早めにどうぞ。
当サイト限定の、初月50%OFFクーポン(HINOSHIN)あり
>>侍テラコヤの評判・口コミ|現役エンジニアが実際に使ってみた感想