はじめに
Reactの勉強を始めようとする
↓
公式のチュートリアルを見に行ったら、「Reactを使うなら、何かフレームワークを使うのがオススメだよ!」と言われる
↓
じゃあNext.jsの勉強するか、ということでチュートリアルを見に行く
↓
「Next.js以前に、そもそもReactをよく知らない人は、まずReactの基礎から勉強するのがオススメだよ!」と言われる <- 今ココ
ということで、Next.jsへ入門するためにReact基礎のドキュメントを読みました。その自分なりの要約を書いていきます。
構成
- Chapter 1 〜 3: 前提知識
- ReactとNext.jsそれぞれの特徴や、必要な前提知識(DOMなど)を学ぶ
- Chapter 4 〜 7: React基礎
- 簡単なサンプルアプリの構築を通して、ReactによるUI構築の基礎を学ぶ
- Chapter 8 〜 11: Next.jsへの導入
- ReactのサンプルアプリをNext.jsに置き換え、Next.jsの基礎の基礎を学ぶ
Chapter 1: React と Next.js について
React
ReactはJavaScriptのライブラリであり、インタラクティブなUIを提供するためのもの。
ReactはUI構築のための便利な機能を数多く提供するが、それらの機能をアプリケーションのどこで使うかは開発者に委ねられている。そのためライブラリと呼んでいる。
Next.js
Next.jsはWebアプリケーションを構築するためのReact用フレームワーク。
Reactを使うために必要なツールや設定が一通り揃っており、さらに便利な機能や構造、アプリケーションの性能を良くするための仕組みも提供する。これらの特徴からフレームワークと呼ばれる。
Reactを使ってUIを構築し、その上でNext.jsの機能を利用することで、多くのアプリケーションに必要な要件を実現できる。
Chapter 2: UIのレンダリング
Reactの仕組みを理解するには、DOMについて知る必要がある。
DOMはHTML要素のオブジェクト表現であり、ツリーのような構造を持っている。JavaScriptからDOMを操作することで、特定の要素を増やしたり、レイアウトを変更したりすることができる。
Chapter 3: JavaScriptによるUIの更新
JavaScriptによるDOM操作の例をみていく。
下記のコードは、app
というidを持つ要素に、子要素として「テキスト付きの<h1>
要素」を追加するもの。
<script type="text/javascript">
const app = document.getElementById('app');
const header = document.createElement('h1');
const text = 'Develop. Preview. Ship.';
const headerContent = document.createTextNode(text);
header.appendChild(headerContent);
app.appendChild(header);
</script>
このようなコードは「命令型プログラミング」と呼ばれるもので、UIがどのように更新されるかの具体的な手順を記載している。
具体的な手順を示すのではなく、表示したいものを宣言するだけで済むのなら、その方が便利。この手法は「宣言型プログラミング」と呼ばれる。UIの構築ではこちらの方が好ましい。
言い換えれば、シェフにピザの作り方を1工程ずつ指示するのではなく、単にピザをくださいと注文するようなもの。
Reactは宣言型のライブラリであり、開発者はUIに対して求めることだけを記載すれば良い。その実現方法はReactが考えてくれる。
Chapter 4: React入門
Reactを使うには、react
とreact-dom
という2つのライブラリが必要。
Chapter 3
のDOM操作をReactで書き換えると、こんなに簡潔になる。
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script>
const app = document.getElementById('app');
const root = ReactDOM.createRoot(app);
root.render(<h1>Develop. Preview. Ship.</h1>);
</script>
ただし、このままでは文法エラーが発生してしまう。
Uncaught SyntaxError: expected expression, got '<'
原因は、render
に渡した<h1>...</h1>
がJSXであり、正しいJavascriptではないから。
JSXとは?
JSXはJavaScriptの構文を拡張したもので、HTMLのような構文でUIを記述できることが特徴。HTMLとJavaScriptの記法をそのまま使えるのが美点だが、3つのJSXルール1には従わなければいけない。
JSXはJavaScriptを拡張したものであり、JavaScriptとは違うもの。そのため、JSXのままではブラウザが理解できない。先ほどの文法エラーはこれが原因。エラーを解消するには、BabelのようなJavaScriptコンパイラを使って、JSXを通常のJavaScriptに変換する必要がある。
プロジェクトにBabelを加える
エラー解消のためには、以下の2点が必要
-
babel.min.js
の読み込みを追加 -
<script>
に種類(type)としてtext/jsx
を追記
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- Babel Script -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/jsx">
const domNode = document.getElementById('app');
const root = ReactDOM.createRoot(domNode);
root.render(<h1>Develop. Preview. Ship.</h1>);
</script>
Chapter 5: コンポーネントによるUI構築
Reactの中核となるコンセプトは3つある。
- Components(コンポーネント)
- Props(プロップス = プロパティの意味)
- State(ステート)
Chapter 5
では、Componentsを取り扱う。なお、PropsはChapter 6
、StateはChapter 7
で説明する。
Components(コンポーネント)
UIは、コンポーネント
と呼ばれる小さな部品に分解することができる。コンポーネントはレゴブロックのようなもので、部品を組み合わせることでより大きな構造を作り出すことができる。
あるコンポーネントを変更しても他のコンポーネントには影響しないため、UIのコードが大きくなっても保守がしやすい。
コンポーネントの作成
Reactでは、コンポーネントはUI要素を返す関数。
コンポーネントの関数(ここではHeader
)を作り、retuen文にJSXを記載する。コンポーネント名は先頭を大文字にすることで、プレーンなHTMLやJavaScriptと区別する。
コンポーネントはroot.render()
に渡してレンダリングする。コンポーネントを渡す際は、通常のHTMLタグと同じように角括弧<>
で囲う。
<script type="text/jsx">
const app = document.getElementById("app")
function Header() {
return <h1>Develop. Preview. Ship.</h1>;
}
const root = ReactDOM.createRoot(app);
root.render(<Header />);
</script>
コンポーネントの入れ子
通常のHTML要素と同様に、Reactコンポーネント同士をネスト(入れ子)にすることができる。
下記の例では、HomePage
というコンポーネントの中にHeader
というコンポーネントが含まれる。
function Header() {
return <h1>Develop. Preview. Ship.</h1>;
}
function HomePage() {
return (
<div>
{/* Nesting the Header component */}
<Header />
</div>
);
}
const root = ReactDOM.createRoot(app);
root.render(<Header />);
コンポーネントツリー
HomePage
の子としてHeader
,Article
,Footer
があり、Header
の子としてLogo
,Title
,Nav
がある、というようにネストを繰り返すことで、コンポーネントはツリー構造を形成することができる。
Chapter 6: Propsを使ったデータ表示
通常のHTML要素には属性があり、その値により要素の動作や見た目を変えられるが、Reactのコンポーネントでも同じことができる。コンポーネントに渡すプロパティはprops
と呼ばれる。(単数ならprop
)
JavaScriptの関数に引数を渡せるのと同様に、レンダリング時の表示内容をpropsで渡すことができる。これらのpropsは親コンポーネントから子コンポーネントへ渡される。
注:Reactでは、データはコンポーネントツリーを流れていく。これは一方向データフローと呼ばれる。Chapter 7で説明するステートは、propsとして親コンポーネントから子コンポーネントに渡される。
propsの使用
下記のHomePage
コンポーネントでは、title
というプロパティをHeader
コンポーネントに渡している。
function HomePage() {
return (
<div>
<Header title="React" />
</div>
);
}
そして、子コンポーネントであるHeader
は、その最初の関数パラメータとしてpropsを受け取る。
propsの内容をconsole.log()
で出力すると、{ title: "React" }
という形式になっており、title
というプロパティを持つオブジェクトであることがわかる。
function Header(props) {
console.log(props); // { title: "React" }
return <h1>Develop. Preview. Ship.</h1>;
}
オブジェクトの分割を使用して、関数のパラメータ内でpropsの値を明示的に指定すれば、<h1>
タグの内容をtitle
変数に置き換えることができる。
function Header({ title }) {
console.log(title); // "React"
return <h1>title</h1>;
}
これを実行すると、そのまま"title"と表示されてしまう。これは、<h1>title</h1>
では、ただ単にプレーンな文字列をレンダリングしていると捉えられる(title
が変数だと見なされない)ため。
JSXで変数を使用する
title
をJavaScriptの変数と認識させるには、中括弧{}
を追加する。
これらは特別なJSX構文で、JSXマークアップの中に通常のJavaScriptを直接書くことができる。
function Header({ title }) {
console.log(title);
return <h1>{title}</h1>;
}
中括弧{}
の中には、任意のJavaScript式(1つの値として評価されるもの)を追加できる。例えば:
1.ドット表記を用いたオブジェクトプロパティの参照
function Header(props) {
return <h1>{props.title}</h1>;
}
2.テンプレートリテラル
function Header({ title }) {
return <h1>{`Cool ${title}`}</h1>;
}
3.関数の戻り値
function createTitle(title) {
if (title) {
return title;
} else {
return 'Default title';
}
}
function Header({ title }) {
return <h1>{createTitle(title)}</h1>;
}
4.三項演算子
function Header({ title }) {
return <h1>{title ? title : 'Default Title'}</h1>;
}
三項演算子を使用する場合、デフォルト値が設定されているため、親コンポーネントからtitle
が渡されなくても構わない。
function Header({ title }) {
return <h1>{title ? title : 'Default title'}</h1>;
}
function HomePage() {
return (
<div>
<Header />
</div>
);
}
リストの繰り返し処理
データをリストで表示したい場面はよくある。
配列メソッドを使用して、データ操作や、同じスタイルだが情報が異なるUI要素を生成することができる。
以下は、HomePage
コンポーネントに追加された配列をarray.map()
で繰り返し処理し、アロー関数でリスト項目にマッピングする例。
function HomePage() {
const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
return (
<div>
<Header title="Develop. Preview. Ship." />
<ul>
{names.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
</div>
);
}
Chapter 7: ステートによるインタラクティブ性の追加
このChapterでは、Reactでステートとイベントハンドラーを使って、インタラクティビティーを追加する方法を見ていく。
例として、HomePage
コンポーネントの中に"Like"ボタンを加えてみる。
まずreturn
文にボタンを追加する。
function HomePage() {
// ...
return (
<div>
{/* ... */}
<button>Like</button>
</div>
);
}
イベントの呼び出し
ボタンがクリックされたときに何らかの動作をさせるには、onClick
イベントを使用する。Reactでは、イベント名はキャメルケースで表す。
function HomePage() {
// ...
return (
<div>
{/* ... */}
<button onClick={}>Like</button>
</div>
);
}
イベントのハンドリング
イベントが呼び出された際のハンドリング関数を作成できる。handleClick()
という関数を作成しておき、onClick
イベントが発生したらその関数を呼び出す。
function HomePage() {
// ...
function handleClick() {
console.log('increment like count');
}
return (
<div>
{/* ... */}
<button onClick={handleClick}>Like</button>
</div>
);
}
ステート(状態)とフック
Reactにはフック(hook
)と呼ばれる特別な関数がある。
フックを使えば、ステート(状態)のような追加ロジックをコンポーネントに追加できる。ステートとは状況に応じて変化するUIの情報のこと。通常はユーザーの操作によって変化する。
例えば、ステートを使って、ユーザーが「いいね」ボタンをクリックした回数を記憶することができる。
ステートを管理するためのフックの名前はuseState()
。
useState()
は配列を返す。
最初の項目は"値"で、配列の2番目の項目は"値を更新する関数"。
どちらも名前は自由に付けられるが、値を更新する関数はset
を使うのが一般的。例えば値がlikes
であれば、それを更新する関数はsetLikes
とする。
function HomePage() {
// ...
const [likes, setLikes] = React.useState();
// ...
}
likes
ステートの初期値を設定することもできる:
function HomePage() {
// ...
const [likes, setLikes] = React.useState(0);
}
ここまでの要素を、「いいね」ボタンに反映させるとこうなる。
handleClick
関数には「いいね」のカウントを1増やす処理を持たせ、ボタンには現在のいいねの数が表示される。
function HomePage() {
// ...
const [likes, setLikes] = React.useState(0);
function handleClick() {
setLikes(likes + 1);
}
return (
<div>
{/* ... */}
<button onClick={handleClick}>Likes ({likes})</button>
</div>
);
}
ここまでの情報はStateの基礎なので、より深く理解したければ、Reactのドキュメントを参照する2。
Chapter 8: ReactからNext.jsへ
ここまでの章でReactについて学んできた。
ReactはUIの構築に秀でたライブラリだが、アプリケーションを構築するためにはある程度の作業が必要になる。また、新しいReactの機能の一部には、フレームワークを必要とするものもある。
Next.jsを使うことで、Reactを使う上での手間を減らし、さらに便利な機能を利用できる。
次のChapterからはNext.jsについて説明する。
Chapter 9: Next.jsのインストール
Next.jsを使用するには、Node.jsの18.17.0
以上が必要となる。
Next.jsでは、Chapter 4
で説明した、unpkg.com
からのreact
とreact-dom
の読み込みは必要ない。
代わりに、npmなどのパッケージマネージャを使用して、next
react
react-dom
をインストールする。
これにより、Reactの説明で作成したサンプルをシンプルにすることができる。
JSXだけが残るので、拡張子も.html
ではなく.js
(または.jsx
)に変えられる。
import { useState } from 'react';
function Header({ title }) {
return <h1>{title ? title : 'Default title'}</h1>;
}
function HomePage() {
const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
const [likes, setLikes] = useState(0);
function handleClick() {
setLikes(likes + 1);
}
return (
<div>
<Header title="Develop. Preview. Ship." />
<ul>
{names.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
<button onClick={handleClick}>Like ({likes})</button>
</div>
);
}
最初のページを作成
Next.jsは、フォルダやファイルの構造や名称がそのままURLになる「ファイルシステムルーティング」を使用しており、ルーティングがシンプルである。
Next.jsで最初のページを作成する方法は下記の通り
-
app
という新しいフォルダを作成し、index.js
ファイルをその中に移動する - ファイル名を
page.js
にリネームする。これがアプリケーションのメインページとなる - Next.jsがページのメインコンポーネントを区別できるよう、
<HomePage>
コンポーネントにexport default
を付与する
これらを反映させると下記の通りになる
import { useState } from 'react';
function Header({ title }) {
return <h1>{title ? title : 'Default title'}</h1>;
}
export default function HomePage() {
// ...
}
開発サーバーの実行
開発サーバーを動かしてみよう。
開発中にページの新しい変更を確認できるようにするため、package.json
ファイルに"next dev"スクリプトを追加する。
{
"scripts": {
"dev": "next dev"
},
"dependencies": {
...
}
}
ターミナルでnpm run dev
を実行すると、下記の2つのことがわかる。
(1) localhost:3000
にアクセスすると、次のエラーが表示される。
これは、Reactのコンポーネントをサーバー上でレンダリングできるようにする「サーバーコンポーネント」をNext.jsが使用しているため。
サーバーコンポーネントはuseState
をサポートしていないため、エラーになっている。
これについては次のChapter 10で説明する。
(2) layout.js
という新しいファイルがapp
フォルダ内に自動的に作成されている。
これはアプリケーションのメインレイアウトを管理するもの。すべてのページで共有されるUI要素(ナビゲーション、フッターなど)を追加するために使用する。
export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Chapter 10: サーバーコンポーネントとクライアントコンポーネント
ネットワーク境界
Next.jsではReactのサーバーコンポーネントとクライアントコンポーネントをサポートしている。
それぞれの違いは追って説明するが、ネットワーク境界
は、特にNext.js
などのフレームワークにおいて、設計時にサーバーコンポーネントとクライアントコンポーネントを分離する際の概念的な境界線を指す。
ネットワーク境界を設定することで、重い処理はサーバーで効率的に行い、クライアントには軽量なデータのみを渡すなど、パフォーマンスやユーザー体験の最適化が行える。
それぞれのコンポーネントについて説明する。
クライアントコンポーネントは、従来のReactコンポーネント。
ブラウザ上でJavaScriptを実行して、ユーザーの操作に応じたリアルタイムでのUI更新など、動的な(インタラクティブな)UIを提供する。
サーバーコンポーネントはサーバーサイドで(Node.jsなどで)実行されるコンポーネント。React 18から導入されたもので、扱うには基本的にNext.jsなどのフレームワークによるサポートが必要。
サーバーサイドで実行できるため、データベースやファイルシステムに直接アクセスできるのが特徴。
動的な要素を持たない静的なコンテンツや、サーバーサイドでの重い処理が必要なコンポーネントをサーバーコンポーネントで扱う。
サーバーコンポーネントはJavaScriptのバンドルに含まれないためネットワーク負荷が軽減されることや、ページの初期表示に使用されるためSEOが改善されることもメリット。
サーバーコンポーネントがレンダリングされると、React Server Component Payload(RSC ペイロード)と呼ばれる特殊なデータがクライアントに送られる。
RSCペイロードには、主に次の2つの情報が含まれる:
- サーバーコンポーネントのレンダリング結果
- クライアントコンポーネントがレンダリングされる箇所のプレースホルダと、それに対応するJavaScriptファイルへの参照
Reactはこれらの情報を使ってサーバーコンポーネントとクライアントコンポーネントを統合し、クライアントのDOMを更新する。
クライアントコンポーネントの使用
Chapter 9でも触れたが、Next.jsはデフォルトでサーバーコンポーネントを使用する。これはアプリケーションのパフォーマンスを向上させるため。
Chapter 9で発生していたエラーを振り返ると、サーバーコンポーネント内でuseState
を使用しようとしていることが原因だった。「いいね」ボタンにはインタラクティブ性が必要なため、クライアントコンポーネントに移動する必要がある。
まずapp
フォルダ内にlike-button.js
という新しいファイルを作成し、LikeButton
コンポーネントをエクスポートする:
export default function LikeButton() {}
<button>
要素とhandleClick()
関数をpage.js
から新しいLikeButton
コンポーネントに移動する:
export default function LikeButton() {
function handleClick() {
setLikes(likes + 1);
}
return <button onClick={handleClick}>Like ({likes})</button>;
}
次に、likes
のステートとインポート文を移動させる:
import { useState } from 'react';
export default function LikeButton() {
const [likes, setLikes] = useState(0);
function handleClick() {
setLikes(likes + 1);
}
return <button onClick={handleClick}>Like ({likes})</button>;
}
そして、LikeButton
をクライアントコンポーネントにするには、ファイルの先頭にReactの'use client'ディレクティブを追加する。
'use client';
import { useState } from 'react';
export default function LikeButton() {
const [likes, setLikes] = useState(0);
function handleClick() {
setLikes(likes + 1);
}
return <button onClick={handleClick}>Like ({likes})</button>;
}
page.jsファイルに戻り、LikeButton
コンポーネントをページでインポートする:
import LikeButton from './like-button';
function Header({ title }) {
return <h1>{title ? title : 'Default title'}</h1>;
}
export default function HomePage() {
const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
return (
<div>
<Header title="Develop. Preview. Ship." />
<ul>
{names.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
<LikeButton />
</div>
);
}
両方のファイルを保存し、ブラウザでアプリを表示する。
エラーがなくなったので、変更を加えて保存すると、変更を反映するためにブラウザが自動的に更新されることに気づくはず。
これはFast Refreshと呼ばれる機能で、Next.jsにあらかじめ設定されているもの。
Chapter 11: 次のステップ
ここまでで、このチュートリアルは終わり。
Reactの学習を継続したければ、世の中には多くの優良なコンテンツがあるが、React Documentationもその1つ。インタラクティブなサンドボックスで練習しながら学ぶことができる。
ダッシュボードアプリを作りながらNext.jsについて学びたければ、Next.jsのチュートリアルに移ろう。このチュートリアルよりも複雑なプロジェクトを作りながら、Next.jsの主な機能を学ぶことができる。
-
3つのJSXルールの詳細はこちら: https://react.dev/learn/writing-markup-with-jsx#the-rules-of-jsx ↩
-
Adding Interactivity (https://react.dev/learn/adding-interactivity) や Managing State (https://react.dev/learn/managing-state) を参照 ↩