LoginSignup
11
2
お題は不問!Qiita Engineer Festa 2023で記事投稿!

NextjsのAppRouterは、Component指向のサポーター、な気がしてる

Last updated at Posted at 2023-07-12

既にReactを用いた開発に慣れた方は、コンポーネント指向は当然!となっているかと思います。今回テーマとしているのは、コンポーネント指向の実装にそれほど慣れていない方にとって、v13で実装することによって、いくらかメリットがあるのでは?と思った話です。
v13のルールに則り実装を進めることで、ある程度コンポーネントを念頭においた開発(コンポーネント指向)に自然となってゆくのでは?
と感じたので、簡単ではありますが、実装を通じてまとめてみました。

v13.4がリリースされてから、2ヶ月近く経過しましたが、ここ1カ月くらいでv13を学習したので、このタイミングでの投稿となりました!

どのような方々に向けた記事?

◎:ReactやNextjsを少し触ったことがある方(useStateuseEffectは使ったことある!)
○:ほとんど触ったことがないけど、フロントエンドエンドに興味ある方(少し大変かもしれませんが・・・)
○:実務経験がそこそこある方(へえ~~って感じてもらえたらいいな・・・)

概念周りの話

コンポーネント指向

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として定義する形ですね。
image.png

v12とv13の違いを実装と通して確認してみよう

作成した画面のコードです。

v12とv13のそれぞれで、以下3つの機能をもつアプリケーションを作成しました。

  1. fetchしたデータの表示(https://jsonplaceholder.typicode.com/users/1からデータを取得)
  2. useStateで管理するカウントの表示とインクリメントのためのボタン表示
  3. クリックで、alertを表示するボタン(onClickイベント)

nextjs12で実装してみる

作成

過去バージョンを指定してアプリ作成を行います。

npx create-next-app@12 v12-sample --ts && cd v12-sample && npm i next@12

バージョン指定をしてアプリ作成する方法は、以下を参考にしました。

以下の画像のようなアプリケーションが完成しました。
image.png

問題点

以下のpages/index.tsxを確認いただくと分かりますが、1ファイル内で、全ての機能が実現できてしまっていることが分かります。実装規模が小さいのでパフォーマンスへの影響はほぼありません。

ですが、あるテキストフィールドを入力しただけで画面の他の全要素までの再レンダリングが走る可能性があり、パフォーマンス面でよろしくない状態です。

【実装】

pages/index.tsx
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を選択することには注意して、入力します。
image.png

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ではできてしまいます
useStateonClickをの機能を含む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コンポーネント

【実装機能(再掲)】

  1. fetchしたデータの表示(https://jsonplaceholder.typicode.com/users/1からデータを取得)
  2. useStateで管理するカウントの表示とインクリメントのためのボタン表示
  3. クリックで、alertを表示するボタン(onClickイベント)

v12と見た目が同じアプリケーションを作成しました。
image.png

実装を見てみます。
ルートディレクトリアクセス時に表示するファイル

src/app/page.tsx
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>
  )
}

各コンポーネントの実装は以下をご確認ください。
【各コンポーネントの実装】
src/app/component/clickEvent.tsx
'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>
        </>)
}
src/app/component/count.tsx
'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>
        </>
    );
}
src/app/component/jsonData.tsx
'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}`}</>
    )
}
src/app/hook/getData.ts
// データ取得
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により発生する制約にのっとり実装をすること(だけ)で、多少は意識せずともコンポーネント設計ができるようになった気がします。

もちろん、今回のように簡単な機能ばかりではないので、より高度な実装を考えるとなると、理解すべき点はたくさんあると思います。中盤あたりに記載したレンダリングがその一例だと思います。

理解すべき点がたくさんあるので、私もまだまだ勉強中です!
ありがとうございました。

11
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
2