既にReactを用いた開発に慣れた方は、コンポーネント指向は当然!となっているかと思います。今回テーマとしているのは、コンポーネント指向の実装にそれほど慣れていない方にとって、v13で実装することによって、いくらかメリットがあるのでは?と思った話です。
v13のルールに則り実装を進めることで、ある程度コンポーネントを念頭においた開発(コンポーネント指向)に自然となってゆくのでは?
と感じたので、簡単ではありますが、実装を通じてまとめてみました。
v13.4がリリースされてから、2ヶ月近く経過しましたが、ここ1カ月くらいでv13を学習したので、このタイミングでの投稿となりました!
どのような方々に向けた記事?
◎:ReactやNextjsを少し触ったことがある方(useState
やuseEffect
は使ったことある!)
○:ほとんど触ったことがないけど、フロントエンドエンドに興味ある方(少し大変かもしれませんが・・・)
○:実務経験がそこそこある方(へえ~~って感じてもらえたらいいな・・・)
概念周りの話
コンポーネント指向
Reactでは欠かせないところですが、そもそもコンポーネントとは?といったところで参考になる記事です。
記載された文章がシンプルなので、お借りします、以下のように記載されています。
あなたのマークアップと CSS と JavaScript を、独自の「コンポーネント」と呼ばれる、アプリのための再利用可能な UI 要素にまとめることができます。
React コンポーネントとは、マークアップを添えることができる JavaScript 関数です。
レンダリング
コンポーネントを意識した開発、と頭で記載したので、本題とはそれますが、コンポーネントを意識する中でセットで関連する内容かと思います。
パフォーマンス面の話となると、レンダリング、特に再レンダリングの話は欠かせないと思います。
詳細は省きますが、以下のような内容がよく取り上げられます。
-
初期レンダリング
- アクセスして、初めてそのコンポーネントの内容を画面に表示するとき
-
再レンダリング
- そのコンポーネントのstate更新時
- 親、または子コンポーネントの再レンダリング時
-
再レンダリングを防ぐために
- useCallback, useMemo, memoの使用
Nextjs13
この場では細かい説明は省きますが、以下公式ドキュメントのリンクです。
v13のレイアウト実装
React Server Components
2023年の3月に公開された記事の文章を一部お借りします。
React Server Components (or RSC) is a new application architecture designed by the React team.
React teamのよって設計された新しいアプリケーションアーキテクチャー、
とのことです。
Why Server Components?
公式サイトの内容ですが、bundle sizeを抑えられる、という重要な内容が記載されています。
you can move data fetching to the server, closer to your database, and keep large dependencies that previously would impact the client JavaScript bundle size on the server, leading to improved performance.
(中略)
With Server Components, the initial page load is faster, and the client-side JavaScript bundle size is reduced.
下の画像でもわかる通り、client側で定義すべきコンポーネントだけをClient SideのComponentとして、それ以外はServer Side Componentとして定義する形ですね。
v12とv13の違いを実装と通して確認してみよう
作成した画面のコードです。
v12とv13のそれぞれで、以下3つの機能をもつアプリケーションを作成しました。
-
fetch
したデータの表示(https://jsonplaceholder.typicode.com/users/1
からデータを取得) -
useState
で管理するカウントの表示とインクリメントのためのボタン表示 - クリックで、alertを表示するボタン(
onClick
イベント)
nextjs12で実装してみる
作成
過去バージョンを指定してアプリ作成を行います。
npx create-next-app@12 v12-sample --ts && cd v12-sample && npm i next@12
バージョン指定をしてアプリ作成する方法は、以下を参考にしました。
問題点
以下のpages/index.tsx
を確認いただくと分かりますが、1ファイル内で、全ての機能が実現できてしまっていることが分かります。実装規模が小さいのでパフォーマンスへの影響はほぼありません。
ですが、あるテキストフィールドを入力しただけで画面の他の全要素までの再レンダリングが走る可能性があり、パフォーマンス面でよろしくない状態です。
【実装】
import Image from 'next/image'
import { useEffect, useState } from 'react';
import styles from '../styles/Home.module.css'
interface IUser {
name: string;
username: string;
}
export default function Home() {
const [number, setNumber] = useState<number>(0);
const [user, setUser] = useState<IUser>();
// データ取得
useEffect(() => {
function getJson() {
fetch("https://jsonplaceholder.typicode.com/users/1")
.then(response => response.json())
.then(users => {
setUser({ name: users.name, username: users.email })
});
};
getJson();
},[])
// インクリメント
const increment = () => {
setNumber((number) => number + 1);
}
// クリックイベント
const clickEvent = () => {
alert('クリックされました');
}
return (
<main className={styles.main}>
<div className={styles.center}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
<div>{`${user?.name}/${user?.username}`}</div>
<div className={styles.count}>{number}</div>
<button type='button' onClick={() => increment()} className={styles.button}>Increment</button>
<button type='button' onClick={() => clickEvent()} className={styles.button}>Click Event</button>
</main>
)
}
nextjs13で実装してみる
作成
プロジェクト作成をします。
npx create-next-app@latest v13-sample --ts
プロジェクト作成時に何点か質問されるので、適宜y/nを選択します。
基本的に画像の通りですが、App routerは、y
を選択することには注意して、入力します。
The biggest change is that we introduced async / await as the primary way to do data fetching from Server Components.
非同期処理で、Server Componentsを用いるのが最も大きな違いとして挙げられています。
v12と比較した場合の改善点
v13はv12と違い、1ファイルのみでは、全ての機能が実現できないことが分かります。(v12ではできてしまいます)
useState
やonClick
をの機能を含むUIを実装したい場合には、ファイルのトップでuse client
を宣言するルールです。
use client
を宣言せずにコンポーネントでuseState
の実装を試みた場合に、以下が表示されます。
You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
ということで別ファイルへの移動を**余儀なくされます。**appフォルダ直下に、components
フォルダを作成します。
componentsフォルダ内では、各UI実装するためにファイルを作成します。
- 以下実装機能の1に該当:
JsonData
コンポーネント - 以下実装機能の2に該当:
Count
コンポーネント - 以下実装機能の3に該当:
ClickEvent
コンポーネント
【実装機能(再掲)】
fetch
したデータの表示(https://jsonplaceholder.typicode.com/users/1
からデータを取得)useState
で管理するカウントの表示とインクリメントのためのボタン表示- クリックで、alertを表示するボタン(
onClick
イベント)
実装を見てみます。
ルートディレクトリアクセス時に表示するファイル
import Image from 'next/image'
import ClickEvent from './component/clickEvent';
import Count from './component/count';
import JsonData from './component/jsonData';
import styles from './page.module.css'
export default function Home() {
return (
<main className={styles.main}>
<div className={styles.center}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
{/* fetchしたデータの表示 */}
<JsonData />
{/* useStateで管理するカウントの表示とインクリメント */}
<Count />
{/* クリックで、alertを表示するボタン */}
<ClickEvent />
</main>
)
}
【各コンポーネントの実装】
'use client'
import styles from '../page.module.css'
export default function ClickEvent() {
// クリックイベント
const clickEvent = () => {
alert('クリックされました');
}
return (
<>
<button type='button' onClick={() => clickEvent()} className={styles.button}>Click Event</button>
</>)
}
'use client'
import { useState } from "react";
import styles from '../page.module.css'
export default function Count() {
const [number, setNumber] = useState<number>(0);
// インクリメント
const increment = () => {
setNumber((number) => number + 1);
}
return (
<>
<div className={styles.count}>{number}</div>
<button type='button' onClick={() => increment()} className={styles.button}>Increment</button>
</>
);
}
'use client'
import { useEffect, useState } from "react";
import { getData } from "../hook/getData";
interface IUser {
name: string;
username: string;
}
export default function JsonData() {
const [user, setUser] = useState<IUser>();
// データ取得
useEffect(() => {
const data = getData();
data.then((response) => response.json()).then((user) => {
setUser({ name: user.name, username: user.email });
});
},[])
return (
<>{`${user?.name}/${user?.username}`}</>
)
}
// データ取得
export async function getData() {
const data = fetch("https://jsonplaceholder.typicode.com/users/1")
return data
}
実装を通じた気付きTips
v12とv13で同じUIとなるように、v12側で作成したプロジェクトをv13に寄せる形で修正をかけた部分がありました。そこで気付いたことがいくつかありました。(それほどある訳ではないですが・・・)
- v13の場合は、
next/font/google
というライブラリが、デフォルトで使用可能になっています。 - v13のプロジェクトは共通レイアウトのコンポーネントが最初から用意されているので。こちらで作成する必要がありません。
- あるフォルダ名のパスで表示する場合、Pages Routerの場合は、
index.tsx
でしたが、App Router(v13.4)では、page.tsx
に代わっています。(違いは他にもいくつかありますが・・・)
テストコーディングのしやすさ
今回は特に実装していませんが、一つひとつのコンポーネントを小さくすることで、各コンポーネントに対する単体テストを実装しやすくなります。
(一つのコンポーネントが大きいと、考慮する多く事項(ケース)多く、漏れも発生しやすくなってしまいますね・・・そして、UTとITnの境界も曖昧になる可能性が出てくるかもしれない)
テストは実装については、以下あたりが参考になります。
できれば実装をしたいところ(テストの実装方法については気になっている人が多い部分なので。)
書籍もGW前に発売されました!!
まとめ
Nextjs13(App Router)になって、パフォーマンスへの考慮がなされた実装が行いやすくなったと思っています。
そして、タイトルの回収となりますが、Server Componentsにより発生する制約にのっとり実装をすること(だけ)で、多少は意識せずともコンポーネント設計ができるようになった気がします。
もちろん、今回のように簡単な機能ばかりではないので、より高度な実装を考えるとなると、理解すべき点はたくさんあると思います。中盤あたりに記載したレンダリング
がその一例だと思います。
理解すべき点がたくさんあるので、私もまだまだ勉強中です!
ありがとうございました。