React × Next.js 実践開発入門 #3 Reactを最短で学ぼう
この講座はYouTubeで動画形式でも用意しています。合わせてご覧ください。
はじめに
前回は、ReactとNext.jsによる開発を便利にする開発ツールを確認しました。今回は、Next.jsの基礎となるReactを最短で学習していきます。
まず、特に重要な次の4つの概念に絞って解説します。
そして最後に、実践として簡単なアプリの実装を例にReactの使い方を説明します。
コンポーネント
コンポーネントとは、UI(ユーザーインタフェース)を再利用可能な部品にする機能です。例えば、ボタンや画像のコンポーネントを作成すれば、必要な時にいつでもこのコンポーネントを利用してUIを組み立てることができます。実装例を見ていきましょう。
コンポーネントの書き方は主に2種類あります。1つは、ES6のclass構文を用いたクラスコンポーネントです。
class HelloMessageClass extends React.component {
render() {
return <div>Hello redimpulz</div>
}
}
クラスコンポーネントでは、React.Component
を継承したクラスを作成します。render
メソッドでJSXによって書かれた要素を返却します。
もう1つの書き方は、関数コンポーネントです。ここではアロー関数を用いています。
class HelloMessageFunction: React.FC = () => <div>Hello redimpulz</div>
関数コンポーネントでは、要素は関数の返り値として記述します。どちらも<div>Hello redimpulz</div>
を返しており、機能の差異はありません。
関数コンポーネントの方が新しい記法であり、ドキュメントにおいても主流になっていることから、特段の理由がない限りは関数コンポーネントを用いると良いでしょう。
これらのコンポーネントは、HTMLタグと同じように書くことで利用できます。
<div>
<HelloMessageClass />
<HelloMessageFunction />
</div>
今回はdiv要素が1つだけのコンポーネントでしたが、より複雑な構造であってもコンポーネントとして再利用できます。また、複数箇所で使われているコンポーネントの全てに修正をかけたい時も、コンポーネントの定義を修正するだけで良いため、UI部品の維持管理が楽になります。
Props
Propsを用いるとコンポーネントに動的に値を渡すことができます。コンポーネントは再利用可能という特徴がありますが、毎回全く同じ形で利用されるとは限りません。例えば、ボタンのコンポーネントはラベルが「次へ」「OK」「許可」などのバリエーションがあったり、プロフィール画像のコンポーネントではアカウントによって異なる画像を表示する必要があります。このような場合に、コンポーネントに値を渡すことによって動的に表示を変更できます。
先ほどの例を見てみましょう。この渡される値を受け取るために関数コンポーネントの定義を修正しましょう。
type Props = {
name: string;
}
class HelloMessageFunction: React.FC<Props> = (props) => <div>Hello {props.name}</div>
props
という引数を定義しますが、このprops
の型情報がありませんので、type
キーワードを用いてProps
という型エイリアスを定義しておきます。今回はname
が文字列であるという情報だけを持ちます。これをReact.FC<Props>
というようにジェネリクスとして指定することでprops
の型を表現できます。
JSXの中に中括弧{}
を用いて変数や式を埋め込むことができます。{props.name}
とすることで、div要素中に渡されたname
の文字列が含まれるようになります。
Propsを用いて値を渡すときは、HTMLの属性のような記述方法を用いることができます。
<HelloMessageFunction name="redimpulz" />
ここではname
にredimpulz
という文字列を渡しています。結果はdiv要素の中にHello redimpulz
と表示されます。さて、name
の値を変えてみるとどうなるでしょうか。
<HelloMessageFunction name="blueimpulz" />
blueimpluz
という文字がPropsによって渡され、結果はdiv要素の中にHello blueimpulz
と表示されます。
このように、Propsを用いることでコンポーネントに動的に値を渡すことができます。また受け取った値を単に表示するだけでなく、値に応じて条件分岐するなどのさまざまな利用ができるため、表現の幅が広がります。
State
Stateは、コンポーネントに「状態」を持たせる仕組みです。関数コンポーネントではuseStateというReact Hooksを使って利用できます。「状態」とは何を意味するのか、ここではカウントアプリを例に見てみましょう。
プラスボタンを押すと表示されている数が1つ増え、マイナスボタンを押すと数が1つ減るようなカウントアプリを考えます。このアプリは、最初に0が表示されている状態です。プラスボタンを押すことで、数字の1が表示される状態になります。このように、アプリを使用している過程で変化していく状態を管理できるのがuseStateです。
useStateを用いてカウントアプリのコンポーネントを作ると次のようになります。
const HelloCount: React.FC = () => {
const [count, setCount] = React.useState(0);
return (
<div>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
)
}
さて、1つづつ見ていきましょう。状態を表すにはReact.useState()
という関数を用います。
const [count, setCount] = React.useState(0);
useState()
関数の引数には初期値を渡します。カウントアプリの最初は0が表示されている状態なので、0
を渡しています。
さて、左辺には2つの変数count
とsetCount
に同時に代入しています。値を取得するにはcount
変数にアクセスすれば良いです。
<div>{count}</div>
中括弧{}
で変数を展開する記法先ほどpropsでやりましたね。さて、状態を更新する際はsetCount
関数を呼び出します。
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
プラスボタンはsetCount
関数を用いてcount
の値を「現在の値 + 1」に更新しています。マイナスボタンも同様に「現在の値 - 1」に更新しています。
このように、useStateの代入式の左辺は、1つ目は状態を表す変数で、2つ目は状態を更新するための関数となります。わかりやすさのために、count
とsetCount
のように、2つ目の変数名は1つ目の変数名の先頭にset
をつけた形にします。
副作用フック
関数コンポーネント内では、useEffectというReact hooksを用いて、副作用フックを扱うことができます。
マウント時
useEffectは、コンポーネントで副作用を実行できます。では、「副作用」とはなんでしょうか。こちらも具体例で学習していきます。
// 副作用フック (マウント時)
React.useEffect(() => {
alert('mounted');
}, [])
これは、コンポーネントがマウントされた時、つまり初めて表示された瞬間に実行されます。この例では、mounted
という文字が表示されます。
コンポーネントのアンマウント時の処理も追加できます。アンマウント時に呼び出す関数は、useEffectの返り値に指定します。この関数をクリーンアップ関数と呼ぶこともあります。
// 副作用フック (マウント時)
React.useEffect(() => {
alert('mounted');
// 副作用フック (アンマウント時)
return () => alert('cleanup');;
}, [])
この例ではアンマウント時にcleanup
と表示されます。他のページに遷移するときなど、画面から消える瞬間に実行したい処理を指定できます。
依存の指定
useEffectの第二引数に、空の配列[]
を渡していることに注目してください。第二引数の配列にはエフェクトを呼び出すトリガーとなる変数を指定します。そうすることで、指定した変数に変化があった際にもエフェクトが呼び出されるようになります。空の配列を渡すと、今見たようにコンポーネントのマウント時のみエフェクトが呼ばれます。
この配列には依存する変数を指定することで、変数の値に変化があったしにもエフェクトを実行されることができます。今回はカウントアプリで用いたcount
変数を第二引数の配列に入れてみましょう。
React.useEffect(() => {
alert('counted');
}, [count])
コンポーネントがマウントされた際に、ステートが初期化されるのでまず1回counted
が表示されます。続いて、count
変数が増減するたびにもcounted
が呼ばれます。
実践編: Reactでアプリを実装
さて、ここまでuseEffect
やuseState
といったReactの基礎を学んできました。これらを実際に活用することで、どのような場面で使える機能なのかを学習していきましょう。
ここからの流れは次のようになります。
- ページアクセス時にAPIからデータを取得
- 取得結果を画面に反映する
- ボタンをクリックして再取得
- もっと便利に
今回作成するページは次のようなものです。
- ページを開くたびに犬の画像をランダムに表示
- fetchボタンを押すと、ランダムに他の犬の画像に切り替え
- clearボタンを押すと、画像を非表示にする
動画の17:00から完成デモを見ることができます。
まずは、Reactをインポートし、HelloFetchImage
というコンポーネントを作成します。
import React from 'react';
const HelloFetchImage: React.FC<Props> = () => {
// 中身はこれから実装
}
では、この中に画像取得の処理を追加します。コードを見てみましょう。
import React from 'react';
type Data = {
message: string;
status: string;
}
const HelloFetchImage: React.FC<Props> = () => {
const [imageUrl, setImageUrl] = React.useState('');
// データ取得とステートの保持
const fetchData = async () => {
try {
const url = 'https://dog.ceo/api/breeds/image/random';
const res = await fetch(url);
const data: Data = await res.json()
setImageUrl(data.message)
} catch error {
console.log(error);
}
}
// 副作用フック(マウント時)
React.useEffect(() => {
fetchData();
}, []);
return (
<div>
{imageUrl && <img src={imageUrl} />}
<div>
<button onClick={fetchData}>fetch</button>
<button onClick={() => setImageUrl('')}>clear</button>
</div>
</div>
)
}
まず、データを取得するfetchData
関数です。API経由のデータ取得は非同期処理ですので、async/awaitを利用した非同期関数とします。
// データ取得とステートの保持
const fetchData = async () => {
try {
const url = 'https://dog.ceo/api/breeds/image/random';
const res = await fetch(url);
const data: Data = await res.json()
setImageUrl(data.message)
} catch error {
console.log(error);
}
}
ここで出てくるData
は、今回使用しているAPIが返却する型です。
type Data = {
message: string;
status: string;
}
このfetchData
関数は定義しているだけですので、useEffect
によってコンポーネントのマウント時に呼び出します。
// 副作用フック(マウント時)
React.useEffect(() => {
fetchData();
}, []);
さて、画像の状態を管理するために、useState
が登場しています。
const [imageUrl, setImageUrl] = React.useState('');
setImageUrl
は既にfetchData
関数で呼ばれていますね。<img>
要素の部分では、imageUrl
が空文字でない時のみ画像を表示させるために、判定条件を入れています。
{imageUrl && <img src={imageUrl} />}
2つのボタンにonClick
で処理を追加します。setImageUrl
に空文字を渡すことで初期化しています。
<button onClick={fetchData}>fetch</button>
<button onClick={() => setImageUrl('')}>clear</button>
これらの例で、useEffect
やuseState
をどのように使うかを理解が深まったでしょう。
では、もっと便利にするため、Propsを用いた例を紹介します。
import React from 'react';
type Data = {
message: string;
status: string;
}
type Props = {
imageNums: number;
}
const HelloFetchImage: React.FC<Props> = (props) => {
const [imageUrls, setImageUrls] = React.useState<string[]>([]);
const [loading, setLoading] = React.useState(false);
// データ取得とステートの保持
const fetchData = async () => {
setLoading(true)
try {
const url = `https://dog.ceo/api/breeds/image/random/${props.imageNums}`;
const res = await fetch(url);
const data: Data = await res.json()
setImageUrls(data.message)
} catch error {
console.log(error);
}
setLoading(false);
}
// 副作用フック(マウント時)
React.useEffect(() => {
fetchData();
}, []);
return (
<div>
{loading
? 'loadinng...'
: imageUrls.map((x) => (
<img key={x} src={x} style={{ width:200, height:200 }} />
))}
<div>
<button onClick={fetchData}>fetch</button>
<button onClick={() => setImageUrl('')}>clear</button>
</div>
</div>
);
};
export default function Index() {
return (
<div>
<HelloFetchImages imageNums={20}>
</div>
)
}
この例では、複数の画像を表示させています。APIの取得中は「loading...」という文字を表示させるために、loading
というステートを追加しています。
また、画像が複数になるので画像URLはstring[]
(文字列の配列)で管理しています。配列になったのでmap()
関数を用いることで<img>
要素を繰り返し出力しています。
Propsで画像の数を指定できるようになったので、<HelloFetchImages imageNums={20}>
というように画像の枚数を渡すことができます。
まとめ
Reactの基礎と開発の際の活用方法を学びました。次回はNext.jsでのページの作り方やナビゲーションについて解説します。