react入門9 with Nextjs
プロジェクトの作成
sample-appというプロジェクトを作る際には
npx create-next-app sample-app --use-npm --ts
npm はnode package manager、つまりパッケージの管理ツールです。
npx はnode package executer、つまりパッケージの実行を行うツールです。すでにnextはインストールしているので、npxで実行しています。
コマンド詳細は下記です。主なオプションとしては --ts(タイプスクリプト利用),--example "url" (urlテンプレダウンロード)です。
脱線ですが、パッケージ管理ツールは、npm(マイクロソフト)とyarn(facebook)、どちらでも良いと思います。react自体facebookのため、nextもyarn 推しのようです。ただ、マイナーライブラリを検索すると公式ツールであるnpmで引っかかることが多い印象です。--use-npmオプションで、npmでも快適?に使えるようようです。
また--tsオプションは今後つけていこうと思います。タイプスクリプトについては下記がまとまっています。
目的
公式チュートリアルでは、サーバーサイドレンダリングの例として、自分のディレクトリにあるmdファイルを読み込んでいましたが、別の例としてjsonplaceholderのJSONオブジェクトを読み込むように変更して復習します。公式チュートリアルにざっと目を通していることを前提としています。
作成するサンプルの仕様、設計は以下です。
トップページにはイラストがある(next/imageを利用)
別の静的ページがある(pagesディレクトリの理解)
各ページはリンクする(next/linkを利用)
各ページは独自のタイトルをもつ(next/headを使用)
RESTAPIで取得したToDo毎のページを動的につくる(getStaticPaths,getStaticPropsを利用)
装飾はしない(今後MUI導入やnext/layoutsの記事を書く予定)
単純な状態からスタート
sample-appのディレクトリを次のようにします。public内のファイルを削除します。pages内のapiフォルダ、__appを削除します。typesフォルダを削除します。index.tsxを以下で置き換えます。本当にまっさらです。関数コンポーネントに相当する関数がNext Pageで型定義されています。
![](https://assets.st-note.com/img/1664523154602-mfa07wNpGU.png?width=800)
この状態でnpm run dev すると、ブラウザに表示されます。開発者ツールで見ると、勝手にHTMLが生成されています。create-react-appでは、index.htmlくらい書いていたのに、今は裏でやってくれています。
![](https://assets.st-note.com/img/1664515662772-QbHmzDfdCP.png?width=800)
変換についてカスタマイズしたい場合は、nextjsが決めているdocumentコンポーネントを上書きする方法があります。またその上にはappコンポーネントが存在します。
1. next/image
トップページに画像を貼り付けます。まずpublicフォルダ内にimagesフォルダを作成し、その中に適当な画像test.pngを格納し、下記のようにindex.tsxにImageコンポーネントを変更します。
import type { NextPage } from 'next'
import Image from 'next/image'
const Home: NextPage = () => {
return (
<>
<Image
src="/images/test.png"
alt="test img"
width={100}
height={100}
/>
<p>hello</p>
</>
)
}
export default Home
![](https://assets.st-note.com/img/1664519278172-uuuwrIEGIP.png)
2.静的なページを追加
pagesディレクトリにstatic-page.tsxファイルを追加します。
import { NextPage } from "next";
const StaticPage:NextPage = () =>{
return(
<p>静的な別のページ</p>
)
}
export default StaticPage
ファイル名をURLに付加する形でアクセスすると表示されます。
![](https://assets.st-note.com/img/1664520471931-It1YLge9uF.png?width=800)
pagesフォルダ外にstatic-page.tsxを移動すると、404エラーとなります。
3.next/link
トップページから静的ページへ、その逆へリンクを貼るには、次のようにします。</>の直前にLinkタグを入れていいます。hrefはホームを”/”とします。また<Link>タグ内に<a>タグの中に文字などリンクになる要素を指定します。
//index.tsx
import type { NextPage } from 'next'
import Image from 'next/image'
import Link from 'next/link'
const Home: NextPage = () => {
return (
<>
<Image .....
/>
<p>hello</p>
<Link href="/static-page"><a>別ページへ</a></Link>
</>
)
}
export default Home
![](https://assets.st-note.com/img/1664522614772-ZzjHFi4sHS.png)
//static-page.tsx
import { NextPage } from "next";
import Link from "next/link";
const StaticPage:NextPage = () =>{
return(
<>
<p>静的な別のページ</p>
<Link href="/"><a>ホームへ</a></Link>
</>
)
}
export default StaticPage
![](https://assets.st-note.com/img/1664522638789-l0XcEGCHTc.png?width=800)
4.next/head
各ページは、それぞれのHeadタグをもち、その中にページのタイトルなどを個別にもつことができます。ここでは下記のようにそれぞれのページのタイトルを追加しています。「Headに何を書くべきか」は、ググってみてください。Nextjsでデフォルトで適用される、HTML変換のベースになっているdocumentコンポーネント、全てのコンポーネントに適用されるAppコンポーネントもHeadを持っていますので、ユーザーはアプリケーションドメインに特化した内容を記載することになります。
//index.tsx
...
import Head from 'next/head'
const Home: NextPage = () => {
return (
<>
<Head><title>ホーム</title></Head>
....
</>
)
}
export default Home
//static-page.tsx
・・・
import Head from "next/head";
const StaticPage:NextPage = () =>{
return(
<>
<Head><title>別ページ</title></Head>
<p>静的な別のページ</p>
<Link href="/"><a>ホームへ</a></Link>
</>
)
}
export default StaticPage
5 REST APIで取得したToDo毎のページを動的につくる
全体の流れを確認しておきます。
ページを動的につくるには、pageディレクトリ配下に[id].js(or tsx)を配置します。
[id].jsの中では、任意の idが取りうる範囲のリストを返す、getStaticPathsという関数を定義する必要があります。
[id].jsの中では、任意の idを引数として、データをpropsとして返す、getStaticPropsという関数を定義する必要があります。
[id].jsの中では、getStaticPropsを受け取りコンテンツを表示するリアクトコンポーネントが(当然)必要です。
ブラウザからpageディレクトリ配下の配置に従いURLを入力すると、Nextjsがページを生成してれます。
5.1 番外:typescriptでgetStaticPropsを定義する練習
まずは、練習としてトップページにをgetStaticPropsで読み込んだpropsを表示してみます。一旦現在のindex.tsxのバックアアップをとり、新規にindex.tsxでgetStaticPropsを定義し、JSONデータを読み込み、Homeコンポーネントに渡して動作を確認します。
//index.tsx
import type { NextPage } from 'next'
import { InferGetStaticPropsType } from 'next'
type Props = InferGetStaticPropsType<typeof getStaticProps>;//キモ
type people = {
id:number;
name:string;
}
const Home: NextPage<Props> = ({data}) => {
return (
<>
<p>id:{data.id}</p>
<p>name:{data.name}</p>
</>
)
}
export const getStaticProps = async () => {
const data:people ={
id:1,
name:"tom"
}
return{
props:{
data,
},
revalidate:1
}
}
export default Home
typescriptを使っているのでgetStaticPropsが返すpropsの中身peopleの型を定義する必要があります。またgetStaticPropsはasync関数なので、返すpropsはpromiseですが、InferGetStaticPropsTypeというツールで意識せずにすみます。ただ、公式サイト通りにやってpropsの型推論がうまくいかず、下記が参考になりました。
5.2 pages配下に動的ページのテンプレートを定義する
page/todoフォルダを生成し、[todoId].tsxを配置しました。
// /pages/todo/[todoId].tsx
import { NextPage } from "next";
import { InferGetStaticPropsType, GetStaticPropsContext } from "next";
type Props = InferGetStaticPropsType<typeof getStaticProps>;
const todoPage:NextPage<Props> = ({data})=>{
return(
<>
</>
)
}
export const getStaticProps = async ({params}:GetStaticPropsContext)=>
{
const data = {}//params.todoIdを引数としてデータを受け取る
return{
props:{
data,
},
revalidate:1
}
}
export const getStaticPaths = async () =>
{
const paths = {} //pathの型は{params{todoId:string}}
return {
paths,
fallback: false
}
}
export default todoPage
重要な点は、下記です。
[ページ名].tsxのページ名がgetStaticPropsが受け取るpramsのid変数として設定されていること。ここではgetStaticPropsにはtodoIdが含まれ、todoIdがページ名になる。
getStaticPropsはparams.todoIdを受け取り、データを選択的に取得できる。
params.todoIdはgetStaticPathsが返すリストに含まれること
URLが文字列であるためparams.todoIdはString文字列であること。
/pages/todo/"params.todoId"が取りうる文字列のどれか”でアクセスすると、param.todoIdにその文字列が設定され、getStaticPropsに渡る。
getStaticPropsの引数には{params}:GetStaticPropsContextを記載する。
getStaticPathsは指定されたURLが有効なものかを判定するツールとも言えます。
5.3 動的ページのgetStaticPaths,getStaticPropsの中身(ロジック)を別のファイルに書く
今回もjsonplaceholderを使用させてもらいます。jsonplaceholderのtodoは1から199までの数字を最後に付けると数字に応じたJSONを返します。その型は以下のTodoDataです。
以下のコードを、新たに作成したdomain/interfaceフォルダ内に配置しました。JSXを使っていないので、ただのts拡張子です。
// /domain/interface/getTodoJson.ts
export type TodoData = {
userId:number,
id:number,
title:string,
completed:boolean,
}
export type Path = {
params:{
todoId:string
}
}
export class GetTodoJson{
base_url:string
constructor(){
this.base_url = "https://jsonplaceholder.typicode.com/todos/"
}
async getData(num:number){
const response = await fetch(this.base_url + String(num))
const data = await response.json()
return data
}
}
export const getPathList=()=>{
const num1to199:number[] = [...Array(199)].map((_,i)=> i+1)
const pathlist:Path[] = num1to199.map((num,idx) =>{
return {
params:{
todoId: num.toString()
}
}
})
return pathlist
}
getPathListは、Path型のオブジェクトのリストで、1から199までの数字を文字としてtodoIdに格納します。
GetTodoJsonクラスは、数字を引数として受けとりTodoData型のJSONを返すgetData関数を提供します。
5.4 動的ページから上記のファイルが提供する中身をimportして、動的ページを完成させる。
先ほどのテンプレートに、上記の関数などを使って動的ページを完成させます。
// /pages/todo/[todo-id].tsx
import { NextPage } from "next";
import Head from "next/head";
import Link from "next/link";
import { InferGetStaticPropsType, GetStaticPropsContext } from "next";
import {GetTodoJson,TodoData,getPathList} from '../../domain/interface/getTodoJson'
type Props = InferGetStaticPropsType<typeof getStaticProps>;
const todoPage:NextPage<Props> = ({data,id})=>{
return(
<>
<Head><title>{id}番目</title></Head>
<p>user id: {data.userId}</p>
<p>task id: {data.id}</p>
<p>task title: {data.title}</p>
{
data.completed
? <p>タスク完了</p>
: <p>未完了</p>
}
<Link href="/"><a>ホームへ</a></Link>
</>
)
}
まず、表示のためのコンポーネントですが、受け取ったdata,idを上記のように表示しているだけです。
export const getStaticProps = async ({params}:GetStaticPropsContext)=>
{
let id:string
if (params === undefined){
id = "0"
}else{
id = params.todoId as string
}
const getTodo = new GetTodoJson()
const num = parseInt(id)
const data:TodoData = await getTodo.getData(num)//1 to 200
return{
props:{
data,
id
},
revalidate:1
}
}
getStaticPropsへのGetTodoJsonの組み込みですが、paramsがundefinedの時があるとタイプスクリプトから怒られたので、最初にif文で受け取っています。また、データだけではなく、idもpropsに詰めています。
export const getStaticPaths = async () =>
{
const paths = getPathList()
return {
paths,
fallback: false
}
}
export default todoPage
getStaticPathsはgetPathListを呼び出しpathsとして設定しているだけです。
![](https://assets.st-note.com/img/1664640598486-GsPoZxHGHc.png?width=800)
5.5 トップページからそれぞれの動的ページにジャンプするリンクを貼る。
トップページを以下のように改造しました。
//index.tsx
import { useState } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import Image from 'next/image'
import Link from 'next/link'
const Home: NextPage = () => {
const [number,setNumber] = useState(1)
const handler_numChange = (e: React.ChangeEvent<HTMLInputElement>) =>{
setNumber(parseInt(e.target.value))
}
return (
<>
<Head><title>ホーム</title></Head>
<Image
src="/images/test.png"
alt="test img"
width={100}
height={100}
/>
<p>hello</p>
<Link href="/static-page"><a>別ページへ</a></Link><br/>
<label>
1から199を半角入力してください:<br/>
<input type="number" value={number} onChange={handler_numChange}/>
</label><br/>
<Link href={`/todo/${number.toString()}`}><button>todoへ</button></Link>
</>
)
}
export default Home
![](https://assets.st-note.com/img/1664640628336-edNmiwHCek.png)
次回
みんな大好き装飾について、人気の高いMUIの使い方について記載します。
この記事が気に入ったらサポートをしてみませんか?