Open15

Next.js 勉強ログ

NMNM

VueやNuxtは分かるがReactはちょっとしか触ったことないのでNext.jsを通じて1から学ぶ

NMNM

プロジェクトの作成

ひとまず公式通りにインストール。作る予定のアプリがマップに関するものなのでnext-ts-mapappにした。

> npx create-next-app@latest
Need to install the following packages:
  create-next-app@latest
Ok to proceed? (y) y
√ What is your project named? ... next-ts-mapapp
√ Would you like to use TypeScript with this project? ... Yes
√ Would you like to use ESLint with this project? ... Yes
√ Would you like to use Tailwind CSS with this project? ... Yes
√ Would you like to use `src/` directory with this project? ... Yes
√ Use App Router (recommended)? ... Yes
√ Would you like to customize the default import alias? ... No
Creating a new Next.js app in <path>\next-ts-mapapp.

Installing dependencies:
- react
- react-dom
- next
- typescript
- @types/react
- @types/node
- @types/react-dom
- tailwindcss
- postcss
- autoprefixer
- eslint
- eslint-config-next

Success! Created next-ts-mapapp

起動確認
localhost:3000で接続確認

> yarn dev

プロジェクトの作成はnuxtの2系と同じぽい
以下公式チュートリアルをやってみたら結構エラーに遭遇したのでメモ

NMNM

開発環境

version
windows 10
node 16.14.2
yarn 1.22.4
Next 13.4.1

Next.jsの動作範囲がnode16.8以降でギリギリなので、エラー出るようなら上げる

NMNM

チュートリアル

ルーティング

Next.js 13 で追加されたApp Router?により、ルーティング方法が2通りあるらしい。

  1. pagesディレクトリを使う
    チュートリアル記載の方法。試していないのでわからない。
    Nuxtと同じでファイル名=パスになる?

  2. App Routerを使う
    Building Your Application: Routing | Next.js
    例として以下のパスを追加してみる

  • /tutorial
  • /tutorial/setting

src/app以下に設置。
app/tutorial/page.tsxファイルを置くと/tutorialのルーティングが作成される。
app/tutorial/setting/page.tsxファイルを置くと/tutorial/settingのルーティングが作成される。
各ディレクトリごとにlayout.tsxを配置したりerror.tsxを設置できる。
階層的にはこんな感じ、コンポーネントが必要な場合は同じ階層に設置?

├── app
│   ├── layout.tsx
│   ├── page.tsx
│   └── tutorial
│       ├── layout.tsx
│       ├── page.tsx
│       ├── HogehogeComponent.tsx
│       └── setting
│           ├── layout.tsx
│           └── page.tsx
NMNM

親ディレクトリにlayoutを置いて、子に置かない場合、親のlayoutがそのまま適用された。
子に同じくlayoutを置き、内容を変えるとエラーが出た。
Error: Text content does not match server-rendered HTML.
サーバとクライアントで差があると出るエラーらしい?ロケール設定等でも出るらしい。出る場合と出ない場合がある。
本来は親の中に子が表示されるのが正しそう

NMNM

リンク

リンクコンポーネントをimportして使う

import Link from 'next/link'
export default function Home() {
  return (
    <Link href="/tutorial/setting">settings</Link>
  )

/turorial/settingからLinkコンポーネントhref="/"で遷移すると以下のエラーが出た。
NotFoundError: Node.removeChild: The node to be removed is not a child of this node
<a>タグで同じくhref="/"は問題が無い。
ルートのページで自身にLinkで遷移しても問題ない。指定方法が違う?
<Link href="/home">で遷移できた。

NMNM

アセット、メタデータ、CSS

画像

public以下がルート。
public/images/profile.jpgを設置した場合、以下Imageコンポーネントで描画が可能。
Imageコンポーネントを使用することで画像のサイズ変換や最適化が行われる。

import Image from 'next/image'
export default function Home() {
  return (
    <Image src="/images/profile.jpg">settings</Link>
  )

最適化はビルド時に行われず、実際に表示域にスクロールなどで到達した時に行われる。

メタデータ

チュートリアルの指定方法
各コンポーネントでHardコンポーネントを呼び出す。

import Head from 'next/head'
export default function Home() {
  return (
    <Head><title>App Home</title></Head>
  )

App Routerの場合
layout.tsxのmetadataで指定する。

export const metadata = {
  title: 'App Home'
}

サードパーティ製JSの追加

各コンポーネントでHardコンポーネントを呼び出す。

import Head from 'next/head'
export default function Home() {
  return (
    <Head>
      <script src="https://www.google-analytics.com/analytics.js"></script>
    </Head>
  )

またはscriptコンポーネントを呼び出す

import Script from "next/script"
export default function Home() {
  return (
        <Script
          src="https://www.google-analytics.com/analytics.js"
          strategy="lazyOnload"
          onLoad={() =>
            console.log("load comp")
          }
        />
  )

上記onload処理を追加したらError: Event handlers cannot be passed to Client Component props.が出た。
参考:NextJS 13 <button onClick={}> Event handlers cannot be passed to Client Component props
'use client'をファイル先頭に追加することでエラーが出なくなった。
13以降、デフォルトのコンポーネントはすべてServer Componentになったとの事なので、クライアントにロードされた後の処理は書けないという事?

NMNM

React Server Componentとはを少し調べた。
データのfetch等を行うコンポーネントはサーバコンポーネントで、fetchされたデータを元にユーザが操作等をして表示を切り替える部分はクライアントコンポーネント(use client)ととして使い分けろという事か
pagesを使ったルーティングの記事が多いのでまだまだ浸透していないイメージ

NMNM

use clientpage.tsxにあればよい。layout.tsxやその階層で使用されるコンポーネントには要らない。多分

NMNM

TailwindCSS

CSSコンポーネントのFlowbiteのReact版をインストール
https://www.flowbite-react.com/docs/getting-started/introduction
Install Flowbite React参照
各コンポーネントのドキュメントにimportするクラスが記載されている。

アイコン

react-iconsが様々なソースからアイコンが使用出来て良さそう

> yarn add react-icons

css.ggでのインポートはこうなる
CgExtensionはアイコン名。ここで名前は検索する。

import { CgExtension } from "react-icons/cg"; 
NMNM

reactで書くv-for

下記の配列を用いたボタンを作成する。

import { Button } from 'flowbite-react'
let buttons= ['info','failure','success','warning']

vueならこうかく

<Button v-for="button in buttons" :key="button" :color="button">
  {{button}}
</Button>

reactならこう

return (
{buttons.map((button) => (
  <Button key={button} color={button}>
    {button}
  </Button>
))}
)
NMNM

reactで書くv-ifについて

表示データ

type button = {
  key:string,
  isActive:boolean
}
let buttons: button[] = [
  {key: 'info', isActive: true },
  {key: 'failure', isActive: true },
  {key: 'success', isActive: false },
  {key: 'warning', isActive: false },
  {key: 'purple', isActive: true },
]

vueならこうかく※単純なものならここまで分ける必要無いけどサンプルとして

<Button v-if="button.isActive" :key="button.key" :color="button.key">
  {{button.key}} active
</Button>
<Button v-else :key="button.key" :color="button.key">
  {{button.key}}
</Button>

reactだと書き方が複数ある様子

jsx中に入れたmap関数等で分岐させる場合

returnを書かないとエラーになる
Type 'void[]' is not assignable to type 'ReactNode'
エラーになるコード

export default function Page() {
  return (
{buttons.map((button) => {
  if (button.isActive) { // 例えば有効かどうかを判定するような処理
    <Button key={button.key} color={button.key} />
  } else {
    <Button key={button.key} color={button.key} />
  }
})})

return をつけると動く

{buttons.map((button) => {
  if (button.isActive) {
    return <Button key={button.key} color={button.key} />
  } else {
    return <Button key={button.key} color={button.key} />
  }
})}

基本直接jsx(tsx)内でif文は書けない

forも含めexpressions(式)じゃないので書けないとの事
こういうの

<Button.Group>
  {if (buttons.length >= 0) {
      {buttons.map((button) => (
        <Button key={button.key} color={button.key}>{button.key}</Button>
      ))}
  }}
</Button.Group>

解決策1:即時関数でifの結果をreturnさせる

!function() や Arrow関数 () =>

<Button.Group>
  {(() => {
    if (buttons.length >= 0) {
      return (
        buttons.map((button) => (
          <Button key={button.key} color={button.key}>{button.key}</Button>
        ))
      )
    }
  })()}
</Button.Group>

解決策2:三項演算子を使う

三項演算子は式だから書ける理論

<Button.Group>
  {buttons.length >= 0 ?
    (
      buttons.map((button) => (
        <Button key={button.key} color={button.key}>{button.key}</Button>
      ))
    ): null
  }
</Button.Group>

解決策3:Function Component を呼び出す

多分こうするものだと思う。他にもあるだろうか?

type button = {
  label: string,
  key:string,
  isActive:boolean
}
let buttons: button[] = [
  { key: 'info', label: 'info', isActive: true },
  { key: 'failure', label: 'failure', isActive: true },
  { key: 'success', label: 'success', isActive: false },
  { key: 'warning', label: 'warning', isActive: false },
  { key: 'purple', label: 'purple', isActive: true },
]
function CustomButton(button:button) {
  if (button.isActive) {
    return (<Button key={button.label} color={button.label}>{button.label} active</Button>)
  }
  return (<Button key={button.label} color={button.label}>{button.label}</Button>)
}

export default function Page() {
  return (
    <>
      <Button.Group>
        {buttons.map((button) => (
          <CustomButton {...button} />
        ))}
    </Button.Group>
  </>
)

参考
https://chaika.hatenablog.com/entry/2019/05/16/083000

NMNM
<CustomButton {...button} />

これは分解すると、以下の様になるらしい

<CustomButton key={button.key} label={button.label} isActive={button.isActive} />

この状態でkeyのパラメータを削除すると、CustomButtonの型指定とmap関数のエラーが出る。

  • Property 'key' is missing in type '{ label: string; isActive: boolean; }' but required in type 'xxxxx'.ts
  • 'key' is declared here.
NMNM

button配列のkeyを削除して以下の様にするのが良さそう
<CustomButton key={button.label} {...button} />