Next.js 勉強ログ
VueやNuxtは分かるがReactはちょっとしか触ったことないのでNext.jsを通じて1から学ぶ
プロジェクトの作成
ひとまず公式通りにインストール。作る予定のアプリがマップに関するものなので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系と同じぽい
以下公式チュートリアルをやってみたら結構エラーに遭遇したのでメモ
開発環境
version | |
---|---|
windows | 10 |
node | 16.14.2 |
yarn | 1.22.4 |
Next | 13.4.1 |
Next.jsの動作範囲がnode16.8以降でギリギリなので、エラー出るようなら上げる
チュートリアル
ルーティング
Next.js 13 で追加されたApp Router?により、ルーティング方法が2通りあるらしい。
-
pagesディレクトリを使う
チュートリアル記載の方法。試していないのでわからない。
Nuxtと同じでファイル名=パスになる? -
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
親ディレクトリにlayoutを置いて、子に置かない場合、親のlayoutがそのまま適用された。
子に同じくlayoutを置き、内容を変えるとエラーが出た。
Error: Text content does not match server-rendered HTML.
サーバとクライアントで差があると出るエラーらしい?ロケール設定等でも出るらしい。出る場合と出ない場合がある。
本来は親の中に子が表示されるのが正しそう
リンク
リンクコンポーネントを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">
で遷移できた。
アセット、メタデータ、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になったとの事なので、クライアントにロードされた後の処理は書けないという事?
TailwindCSS
CSSコンポーネントのFlowbiteのReact版をインストールInstall Flowbite React
参照
各コンポーネントのドキュメントにimportするクラスが記載されている。
アイコン
react-icons
が様々なソースからアイコンが使用出来て良さそう
> yarn add react-icons
css.ggでのインポートはこうなる
CgExtension
はアイコン名。ここで名前は検索する。
import { CgExtension } from "react-icons/cg";
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>
))}
)
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>
</>
)
参考
<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.
button配列のkey
を削除して以下の様にするのが良さそう
<CustomButton key={button.label} {...button} />