Open7

react(Next14)備忘録

新山 慶介新山 慶介

nextjs App setup

$ npx create-next-app {project-name}

カレントディレクトリに作成する場合

$ npx create-next-app .
新山 慶介新山 慶介

App Router

RSCの理解やPage Routerとの違いを理解するときは↓の記事
https://zenn.dev/yamadadayo123/articles/6cb4f586de0183

nextjsにおけるルーティングの仕方(App Router)

.
└── app/
    ├── blog/
    │   ├── [postId]/
    │   │   └── page.tsx
    │   └── page.tsx
    ├── page.tsx
    └── layout.tsx
  • page.tsxを作成すると作成したディレクトリの名前でルーティングされる
  • [id]のようにするとid個別のページが作成される
  • layout.tsxでpage.tsxをラップする

その他、特別な意味を持つディレクトリは冒頭記事参照。

まだ サーバーコンポーネントとか、クライアントコンポーネントとかの理解が追いついていないので、学習できるようなサンプルアプリケーションを作りながら学べればいいなぁ...

新山 慶介新山 慶介

html-react-parser

リッチエディタなどの出力でhtml文を出力する際、パースが必要になる。
そのためのreactライブラリ。

$ npm i html-react-parser --save
page.tsx
import React from 'react'
import parse from 'html-react-parser' //インポート

const blogs = () => {
  return(
    <div>
        {parse(<p>テキストテキストテキスト</p>)}
    </div>
  )
}

export default blogs
新山 慶介新山 慶介

ループ(ブログ一覧取得など)

app/blog/page.tsx
import React from 'react'
import Link from 'next/link';
import { getList } from '@/app/libs/client'
import parse from 'html-react-parser'

const blogs = async () => {
  const { contents } = await getList() //APIを叩いてリスト形式のJSON取得

  return (
    <div>
      <ul>
        {contents.map((post) => { //ループ開始
          return (
            <Link href={`/blog/${post.id}`}>
              <li key={post.id}>
                <h2>{post.title}</h2>
                {parse(post.body)}
              </li>
            </Link>
          )
        }
        )}
      </ul>
    </div>
  )
}

export default blogs

async / awaitで非同期処理。

contentsの中身:

"contents": [
    {
        "id": "vzlgvybt8bia",
        "createdAt": "2024-05-15T05:04:27.827Z",
        "updatedAt": "2024-05-15T05:04:27.827Z",
        "publishedAt": "2024-05-15T05:04:27.827Z",
        "revisedAt": "2024-05-15T05:04:27.827Z",
        "title": "テストタイトル2",
        "body": "<p>テキストテキストテキスト</p>"
    },
    {
        "id": "x6-a050fa",
        "createdAt": "2024-05-14T11:52:33.557Z",
        "updatedAt": "2024-05-14T11:52:33.557Z",
        "publishedAt": "2024-05-14T11:52:33.557Z",
        "revisedAt": "2024-05-14T11:52:33.557Z",
        "title": "テストタイトル1",
        "body": "<p>テストテキストテストテキストテストテキスト</p>"
    },

],

配列で返されるので、map関数で取り出していく。

 <ul>
    {contents.map((post) => { //ループ開始
      return (
        <Link href={`/blog/${post.id}`}>
          <li key={post.id}> //キーをidで指定
            <h2>{post.title}</h2>
            {parse(post.body)} //html形式なのでparse
          </li>
        </Link>
      )
    }
    )}
</ul>
新山 慶介新山 慶介

Next14でsass & css modulesを使うとき

Sassコンパイラのインストール

$ npm i sass --save

CSS Modules使う時用のプラグインインストール

$ npm i typed-scss-modules --save

設定ファイル作成 & グローバル変数使う時用の設定追記(variables.scssがグローバル変数記述ファイル)

export const config = {
  exportType: 'default',
  nameFormat: 'none',
  implementation: 'sass',
  additionalData: `@use "src/styles/variables.scss" as *;`,
  ignore: ['**/variables.scss', '**/variables/**']
}

注意点:
・CSS module内ではimpureなセレクタがトップレベルでは使用できない
html body ul など。

html{
    overflow-x:hidden
}

↑みたいにするとエラー吐くので、idつけるなりクラスつけるなりして対応。
(あっているのか不安)

新山 慶介新山 慶介

↑だけだと足りなかったので追記。

Classの型定義をしてクラスの補完が効くようにする。

$ npm i npm-run-all --save
"scripts": {
    "dev": "run-p dev:next dev:scss",
    "dev:next": "next dev",
    "dev:scss": "typed-scss-modules src --watch",
    "typegen:scss": "typed-scss-modules src"
}

この辺をいじってると、npm run devなどのCLIコマンドで何をしているかがなんとなくわかってくるようになった。

「"script" :{} 内に書いてある"prefix"でnpm run {"prefix"} が決まって、そこでもともとインストールされているCLIコマンドを実行しているんだなぁ」とか。
(言語化むずすぎる)

個人的に、ライブラリやパッケージはできるだけ使いたくない(過去の経験から保守性が低くなる)ので、なんとかしないようにしていたけれど、「ライブラリを使う = 保守性が下がる」ではなく、「ライブラリをインストールする分理解しなければならないことが増える」だけであることに気づいた。

そもそもNextなどのプログラムがどのようにコンパイル or ビルドされているか等の理解度が低ければなんのライブラリ使っても保守が難しくなるのは当たり前で、それを当たり前のように理解して開発していくのがエンジニアと呼ばれる人たちなんだなぁとか思ったり。

新山 慶介新山 慶介

コンポーネント ←→ ページ間の情報をpropsで受け渡し

コンポーネント:

ArticleCard.tsx
import React from 'react'
import Link from 'next/link';
import parse from 'html-react-parser'
import styles from './ArticleCard.module.scss'

type Props = {
  id: string;
  title: string;
  body: string;
}

const ArticleCard = (props: Props) => {
  return (
    <li key={props.id} className={styles['c-articleCard']}>
      <Link href={`/blogs/${props.id}`}>
        <h3 className={styles['c-articleCard--title']}>{props.title}</h3>
        {parse(props.body)}
      </Link>
    </li>
  )
}

export default ArticleCard

コンポーネント側でpropsを引数にもつ
型定義をするために type Props ={}内に定義。 anyは敗者。

ページ:

app/page.tsx
import React from 'react'
import { getList } from '@/app/libs/client'
import ArticleCard from '@/public/components/card/ArticleCard';
import styles from './FrontPage.module.scss'

const FrontPage = async () => {
  const { contents } = await getList()
  return (
    <div className={styles['p-frontPage']}>
      <ul className={styles['p-frontPage__articleList']}>
        {contents.map((post) => {
          return (
            <ArticleCard
              {...post}
            />
          )
        }
        )}
      </ul>
    </div>
  )
}

export default FrontPage

ページ側で渡す値を書いていく。
本来は

<ArticleCard
id ={post.id}
title = {post.title}
body ={post.body}
/>

のように書いていくけれど、情報を限定しない場合は{...post}のように省略できる(スプレッド構文)
参考:ja.react.dev