React/Next.js
はじめに
このページは、SPA(Single Page Application)の続きのページです。
ここではReactとNext.jsにスポットを当てて記載していきます。
一応このページから読み進めても問題ないように構成したつもりですが、先のページを見てからの方が、ReactとNext.jsについて理解が深まるかもしれません。
React
Reactとは、WebサイトやWebアプリのUI部分を開発する際に活用するJavaScriptライブラリです。ReactはMeta (旧Facebook社)が開発し、2013年にオープンソース化されました。
リアクティブ・プログラミングを特徴としています。リアクティブとは、『何かの値が変化するとそれに連動して表示内容が自動で変化する仕組み』のことです。
ReactはJavaScript用のフレームワークと勘違いされることがありますが、実際には上述の通りライブラリとなります。CDN(Contents Delivery Network)経由でhtmlファイル内でReactのライブラリを使用することができます。
以下は実際にReactのライブラリを使用して書いたものです。
↓クリックでcounter増加
上記のソースコード(抜粋)
react_next.html
<head>
<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>
</head>
<style>
#root {
cursor: pointer;
font-size: 20pt;
background-color: lightblue;
color: black;
padding: 10px 1px 5px 1px;
}
</style>
<div id="root" onClick="doCount();">wait....</div>
<script>
let counter = 0;
let dom = document.querySelector('#root');
doCount();
function doCount() {
counter++
let element = React.createElement(
'p', {}, "counter: " + counter
)
ReactDOM.render(element, dom)
}
</script>
上記はCDNを使用していますが、これは主に学習目的または小規模のアプリケーション目的での使用が適しています。
大規模な開発をする場合、Reactで重要な概念として以下3つがあります。
JSX(JavaScript XML)
JSXはJavaScriptの構文拡張で、JavaScriptファイル内にHTMLのようなコードを記述できるようにするものです。Meta(旧Facebook社)によって開発されました。たとえば、次のようなコードを書くことが出来ます。
const heading = <h1>Hello, JSX!</h1>;
このコードはHTMLのようにも見えますが、JavaScriptのコードです。headingという定数を作り、その定数にHTMLの要素(React要素)を代入しています。
JSXを記述する場合、ファイルの拡張子は.jsx
とします。これにTypeScriptを適用する場合、拡張子は.tsx
とします。
JSXでは中括弧 { } を使うことで、以下のようにReact要素内にJavaScriptの値を埋め込むことができます。
let name = 'React';
const heading = <h1>Hello, {name}</h1>;
コンポーネント
Reactでは、自分が書いたマークアップ、CSS、JavaScript を、アプリのための再利用可能な UI 要素にまとめることができます。これを『コンポーネント』と呼びます。
以下コンポーネントの例です。 CommonFooter
という名前のコンポーネントを作っています。
common_footer.tsx
export default function CommonFooter(labelKey: any): JSX.Element {
return (
<div>
<footer>
...
</footer>
</div>
);
}
このようにして作成したコンポーネントを組み合わせてシステムを構築していくのがReactの考え方です。
React Hooks
React Hooksはフック(Hook)によって関数コンポーネント内で状態やライフサイクルを扱うための機能です。
公式が提供しているフックは10種類あり、さらにフックを組み合わせてカスタムフックを独自に実装できます。
ReactではこのReact Hooksを利用してリアクティブなシステムを構築していきます。
例えば状態のフックであるuseStateなら以下のようなコーディングをすることでリアクティブにすることができます。
const [状態, 更新関数] = useState(初期状態)
const [count, setCount] = useState(0);
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
Next.js
ここではNext.jsの機能について記載していきます。
レンダリング
Nextで使えるレンダリング手法とその概要はこちらに記載しているので、どのようにしてそれを実現するかを記載します。
Nextでは、所定の関数を使用することでレンダリングモードを制御します。レンダリングモードと対応する関数は以下の通り。
レンダリング手法 | 関数名 |
---|---|
SSR(Server-Side Rendering) | getServerSideProps |
CSR(Client-Side Rendering) | -(デフォルトがCSRなので使用する関数は無し) |
SSG(Static Site Generation) | getStaticProps |
ISG(Incremental Static Generation) | getStaticPaths |
ISR(Incremental Static Regeneration) | revalidateを返すgetStaticProps |
On Demand ISR (On Demand Incremental Static Regeneration) | revalidateを返すgetStaticPropsとunstable_revalidateの組み合わせ |
各関数の細かい使い方に関してはここでは記載しません(各関数の使い方まで記載するスキルが今の自分にはありません)。
使い方に関しては以下のサイトがわかりやすい気がしたのでリンクを張っておきます。
-
SSR, CSR, SSG, ISG, ISRの違いと使い分け方。それぞれNext.jsでTodoアプリ作ってみた。
様々な手法のコード例が載っているが、 On Demand ISR についての記載なし。 -
Zenn On-demand ISRの機能を試してみた
On Demand ISR についての記事。
ファイルベースルーティング
Next.jsの「ファイルベースルーティング」は、pagesディレクトリ内のファイル構成がそのままアプリケーションのページ構成になるという機能です。pages ディレクトリ配下にスクリプトファイルを配置すると、それを使って画面表示を行います。
例えば以下の画像のように配置した場合、 http://[ホスト名]/saga1/ability/ability_detail
というurlで ability_detail.tsx
の内容が表示されます。
Next.js では、すべてのページの初期化に App というコンポーネントを使用しています。pages/_app.tsx
を作成することで、Appコンポーネントをカスタマイズできます。全ページに共通のcssを適応するなど、すべてのページで共通な処理などを書くことができます。
以下のコードは_app.tsx
の中身です。1行目でimport '../styles/style.css'
を記述することで、pages配下のすべてのページに style.css
が適応されます。
_app.tsx
import '../styles/style.css'
function MyApp({ Component, pageProps }) {
return
}
export default MyApp
作った物
ゲームのサガシリーズが好きなので、初代サガである魔界塔士サガに関するものを作ってみました。
とりあえず作った後に勉強したので、今見ると作り直したくなる感じはあるが、一旦現時点(2024年5月)のものとして公開してみます。
ソースコードを全量載せると結構な量になるので、だいぶ抜粋して紹介します。
各ページのレイアウトを統一するために、共通部分を CommonBody, CommonHeader, CommonFooter に分けてそれぞれコンポーネント化し、ページ毎に異なる部分を別途作成してそれを埋め込む形で作成しています。
以下はWebサイトに表示するページのフッター部分を CommonFooter
という名前でコンポーネント化したものです。
common_footer.tsx
export default function CommonFooter(labelKey: any): JSX.Element {
return (
<div>
<footer>
共通フッターの内容をここに書く
</footer>
</div>
);
}
コンポーネント化したフッターを呼び出す際は <CommonFooter />
と記述します。
以下は同じくコンポーネント化した CommonBody から CommonFooter を呼び出しているサンプルです。
また、 CommonBody の呼び出し元から引数で渡された内容をbody部に設定しています({body.message}
の部分)。
common_body.tsx
import CommonFooter from "./common_footer";
export default function CommonBody(body: any): JSX.Element {
<body>
<section className="box">
{body.message} // 引数(body)から渡された値(message)を設定
...
</section>
<CommonFooter /> // CommonFooter コンポーネント呼び出し
</body>
}
共通のコンポーネントはこんな感じで、あとは各ページ個別の内容を pages ディレクトリ配下に配置し、作成したコンポーネントをその中で呼び出して使うようにしていました。
今回は pages 直下に monster_trans_mechanism.tsx というファイル名で配置しているので、サーバ起動後、 http://ホスト名:3000/monster_trans_mechanism でアクセスできます。
以下が monster_trans_mechanism.tsx の中身です。<CommonBody message={this.CreateBody()} />
の部分で CommonBody コンポーネントを呼び出し、 message という名前の変数に CreateBody() 関数の戻り値を設定しています。
pages/monster_trans_mechanism.tsx
import CommonBody from '../common/common_body';
export default class MonsterTransform extends Component {
constructor(props: {} | Readonly<{}>) {
super(props)
}
render() {
return (
<>
// CommonBodyコンポーネント呼び出し。
// CreateBody()の戻り値を引数(messageという名前の変数)で渡している。
<CommonBody message={this.CreateBody()} />
</>
)
}
// body部の生成
CreateBody() {
let body =
<>
各ページの内容をここに書く
</>
return body
}
};
基本的に上記のような構成で全てのページを作成しています。
実際に作った物は大まかに以下。
- 仲間モンスター一覧
- 変身後モンスター算出
- 能力一覧
魔界塔士サガはモンスターを仲間にすることができ、そのモンスターは敵が落とす肉を食べることでより強力なモンスターに変身し、成長するというシステムになっています。何に変身するかは(普通は)分からないようになっています。
仲間になるモンスターを一覧化したものが仲間モンスター一覧で、変身後に何のモンスターになるかを算出してくれるのが変身後モンスター算出ツールです。
※変身後モンスター算出ツールの初期表示が遅いが、おそらく適切なレンダリング手法になっていないからだと思われる。今後改善予定。
変身後モンスター算出ツールは、画面上段のプルダウンを選択すると、それに合わせて画面下の変身後の部分がリアクティブに切り替わるようになっています。
下段の変身後の部分だけがレンダリングされるようになっています。
仲間モンスター一覧の方は、テーブルのヘッダー部分を操作することで表示対象を絞ったり、並び替えができるようになっており、その度に一覧の部分だけが再レンダリングされるようになっています。
また、一覧のモンスター名がリンクになっていて、そこから当該モンスターの詳細情報を確認できる画面に遷移できるようになっています。
このモンスターの詳細情報は、変身後モンスター算出画面でも同じ内容が出力されるようになっています。ソースコードに同じ内容を2回書きたくないので、各モンスターの詳細情報部分をbody部として切り出し、それを詳細画面と変身後モンスター算出の詳細欄の両方で流用する形にしている。
変身後モンスター算出の詳細欄
モンスター一覧の詳細欄。パンくずリスト以外は同じ内容になっていることがわかる。
あと作ったのは、モンスターが使用する能力の詳細。
画面のレイアウトをReactで記載し、表示する情報自体はDBに格納。
URLからIDを取得してそれをもとにDBから情報を取得して画面をレンダリングするようになっています。
リンクは↓こちら。URL の ability_id を変えることで内容が変わることが確認できると思います。
能力詳細
React/Next.jsを使って作ってみたものの紹介は以上です。
参考書籍
参考サイト
- kinsta Reactで使用するJSXの基本をわかりやすく解説
- Qiita Next.jsの_documment..tsxと_app.tsxについてメモ
- WESEEK Tech Blog 煩わしい設定は一切なし!Next.jsでCSS を使う方法
- Tyotto good! Next.jsの_documment.js(tsx)と_app.js(tsx)について爆速で理解しよう
- Qiita Next.jsの_app.jsと_document.js
- deve.K's Programming Primer - プログラミング初心者のための入門ブログ
- Zenn React Hooks 再入門
- WESEEK Tech Blog 煩わしい設定は一切なし!Next.jsでCSS を使う方法
- DevelopersIO Next.js 12.1の新機能オンデマンド ISRでページを手動再検証させてみた
- Zenn On-demand ISRの機能を試してみた
- kk-web Next.jsのOn-demand Revalidationの簡単な説明
- teamlab-frontend SSR, CSR, SSG, ISG, ISRの違いと使い分け方。それぞれNext.jsでTodoアプリ作ってみた。
- note Next.jsのIncremental Static Regenerationが凄い
- Zenn Next.js + TypeScript | CSR・SSR・SG・ISG・ISR・On Demand ISR の違いをコード付きで解説