(記事一覧編)jQuery使いのエンジニアがReactとNext.jsでWebサイト制作するために最低限押さえておきたいコード
こちらの記事は以下の記事の続きです。
jQuery使いのエンジニアがReactとNext.jsでWebサイト制作するために最低限押さえておきたいコード(基礎編)
今回は前回ほど長くありませんが、その代わりにちょっとレベルが上がっています。
前回のおさらい
前回は
- コンポーネントの作成
- propsの渡し方、受け取り方、分割代入
- mapを使って配列からコンポーネントを表示
このあたりをやりました。
今回やること
-
async
await
getStaticProps
- 記事一覧を作る
SSG
Webサイト制作ではSEOを考慮する必要があり、SSG(静的サイト生成)がほぼ必須です。
ヘッドレスCMSなどからAPIを叩いて(関西人はしばくかもしれません)データを取得し、取得したデータをもとに記事一覧や記事詳細などのページを生成する場合がほとんどでしょう。
それに使用するのがgetStaticProps
とgetStaticPaths
という特別な名前の関数で、こいつをページコンポーネント(src/pages配下のファイル)からexport
する必要があります。
今回も記事がちょっとだけ長いので、この記事内ではgetStaticProps
だけ説明してgetStaticPaths
についてはまた次の記事で説明します。
記事一覧(/news/)
まず記事一覧ページを作りたいので、src/pages/news/index.tsx
を作成しましょう。
このtsxファイル内では、記事の一覧を取得してそのデータをもとに記事コンポーネントを表示したいので、先ほど軽く説明したgetStaticProps
を使用します。
データの取得はgetStaticProps
内で行う必要があり、この関数はページコンポーネントからしか呼べません。
基本的な形は以下です。
export const getStaticProps = async () => {
const { posts } = await 記事データを取得する非同期関数()
return {
props: { posts }
}
}
記事データを取得する非同期関数()の部分ですが、同じようなデータを取得する記述を何度も書いてしまうと修正が面倒ですし、コピペコピペになってしまうとよくないので、適当な関数に閉じ込めて別ファイルに切り分けます。
その前に新しく出てきたasync
await
が気になると思うので軽く説明します。
ざっくり説明しているのでちゃんと学びたい人はほかの記事で学んでください。
async
async
がついた関数はPromise
を返します。
ちょっとよくわからないかもしれませんが、簡単に言うと
- データを取得するのに時間がかかります!その間は「データの取得まだ終わってないで~」
- データの取得に失敗したら「失敗したわ…」
- 成功したら「データとってきたで~これを受け取れ!」
というのを教えてくれるオブジェクトであるPromise
を返す関数です。
このasync
がついた(Promise
を返す)関数はawait
を使うと値を簡単にいい感じに取り出すことができます。
いい感じに の部分が気になる人はPromise async await
とかでググってみてください。
例はこんな感じです。
const fetchPosts = async () => {
const res = await fetch('データ取得先のURL')
const posts = await res.json()
return { posts }
}
fetch
の部分はデータを取得するまで時間がかかるPromise
を返す関数です。
データの取得まだ終わってないで~の段階でそれを教えられてもどうしようもないので、取得に失敗するか、それとも成功してデータが返ってくるのか まで待つ必要があり、それをやってくれるのがawait
です。
ノブのいう「ちょっと待て」です。
fetchPosts
の処理を追っていってみましょう。
await
await
は先ほど説明した通りデータの取得が終わるまで「ちょっと待て」をやってくれるもので、async
がついた関数の内部で使用することができます。
先ほどの例を見ると
const fetchPosts = async () => { //asyncがついた関数
const res = await fetch('データ取得先のURL') // awaitが使える
const posts = await res.json() // resはawaitされているので、データの取得が終わったらここが実行される
return { posts }
}
こうなっているので、
- fetchするからawaitしといてくれ
- posts「了解。終わったらresって箱に入れといて」
- posts「resが来たわ jsonにするで~」
- return「json()し終わったらpostsって名前で返しとくわ」
こんな感じでヘッドレスCMSなどが提供しているAPIから非同期でデータを取得します。
データの取得はgetStaticProps
の中でやると書きましたが、こいつにもasync
がついていましたね。
記事データを取得する非同期関数
これをまだ作っていなかったので作りましょう。
ダミーのデータを返してくれるAPIがあるのでこちらを利用します。
https://jsonplaceholder.typicode.com/posts
type Post = { id: number, title: string }
type Posts = { posts: Post[] }
const fetchPosts = async (): Promise<Posts> => {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
const posts = await res.json()
return { posts }
}
こうです。
: Promise<Posts>
の部分はTypeScriptの型の情報で、posts
っていうプロパティを持ったオブジェクトがPromise
でデータ取得できたら{ id: number, title: string }
こういうオブジェクトが集まった配列が返ってくるで みたいな型です。
ちょっと日本語がわかりにくい気もしますが、要するに記事のデータはこういう配列で返ってくるで~みたいなもんです。
どんなデータが返ってくるのか型をつけておかないとコンポーネントに渡すデータがあっているかわからなくなってしまうので基本的に型はつけます。
TypeScriptをかけないときはこれがあると逆にわかりにくく感じる場合もあると思いますが、逆にこれがないとどこで詰まっているのかわからなくなってくるので頑張って書いたほうがいいです。
これで記事取得関数ができたので、記事一覧ページを表示できそうです。
いきなり難しくなりますが以下のようになります。
型の部分がちょっとややこしいですが、それ以外は今までに学習したことしか使っていません。
import type { NextPage, GetStaticProps, InferGetStaticPropsType } from 'next'
import NextLink from 'next/link'
import { fetchPosts } from '../../utils/fetchPosts'
type Props = Awaited<ReturnType<typeof fetchPosts>>
export const getStaticProps: GetStaticProps<Props> = async () => {
const { posts } = await fetchPosts()
return {
props: { posts },
}
}
const News: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({ posts }) => {
return (
<ul>
{posts.map((post) => {
return <li key={post.id}><NextLink href={`/posts/${id}`}>{ title }</NextLink></li>
})}
</ul>
)
}
export default News
ちょっとずつ見ていきましょう。
まず
import type { NextPage, GetStaticProps, InferGetStaticPropsType } from 'next'
import NextLink from 'next/link'
import { fetchPosts } from '../../utils/fetchPosts'
この部分は1行目が型のimport、2行目はリンクコンポーネントです。
3行目がさっき作った関数のimportですね。
1行目はfrom 'next'
しておけばサジェストされるので名前だけふんわり覚えておけばOKです。
type Props = Awaited<ReturnType<typeof fetchPosts>>
この部分はTypeScriptの記述で、typeof fetchPosts
の部分でfetchPosts
関数の型を取得します。
次にReturnTypeを使ってこの関数がどんな情報(型)を返すんですか?というのを取得します。
この段階でReturnType<typeof fetchPosts>
ここの部分はfetchPosts
関数の返り値のPromise<Posts>
型になっています。
ここからPosts
の部分の型が欲しいので、Awaited
型を使用します。
最終的に
type Props = {
posts: { id: number, title: string }[]
}
が得られます。
fetchPosts
関数からtype Posts
をexport
しておけばまぁそれでもいい気もしますが、import
部分が長くなったりであんまりいい気がしないので、今回はちょっと難しそうに見える型を使ってみました。
1個ずつ見ていくと特に難しくはないので落ち着いてみましょう。
export const getStaticProps: GetStaticProps<Props> = async () => {
const { posts } = await fetchPosts()
return {
props: { posts },
}
}
ここの部分はすでに説明が終わっているので型のGetStaticProps<Props>
の部分を一応説明すると、propsがどんなデータ(型)を返しますか?というのがGetStaticProps
型で、<>
の部分にさっき作ったProps
型を指定することによってpropsがどんなデータを返すのかを教えています。
これでpropsにpostsのデータが入るので後は表示するだけです。
const News: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({ posts } // propsから分割代入しています) => {
return (
<ul>
{ posts.map(( { id, title } ) => {
return <li key={ id }><NextLink href={`/posts/${id}`}>{ title }</NextLink></li>
}) }
</ul>
)
}
export default News
この部分です。
: NextPage<InferGetStaticPropsType<typeof getStaticProps>>
の部分は型の説明です。覚えるでOKかと思います。getStaticProps
に型がちゃんとついていればそのほかは暗記で問題ありません。
ここで型を書いてあげることによって、({ posts })
の部分に型がつくのでposts
っていうプロパティが入ってきているんだな ということがわかり、posts
にも型がついているのでmap
で回してコンポーネントにデータを渡すときも型の恩恵を受けることができます。
posts.map
の部分は前回の記事で学習した、配列からJSXの新しい配列を返して表示する部分です。
key
を忘れないようにしましょう。(もし忘れていてもエディタが怒ってくれます)
できた~
これで記事一覧ページを作ることができました。
実際は記事を全件ではなく10件くらい取得してページネーションをつけたりサイドバーにカテゴリ一覧を表示したりすることになると思います。
ページネーションは記事の件数と一ページの表示件数、現在何ページ目か、リンクを何個表示するか の情報があれば計算して表示できるのでやってみてください。
サイドバーにカテゴリ一覧を表示するのも、記事一覧を取得したのと同じようにデータを取得してgetStaticProps
のprops
の部分で
return {
props: {
posts, categories
}
}
みたいに複数返してあげて、
const News: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({ posts, categories }) =>
みたいにすればOKです。
まとめ
今回はgetStaticProps
の使い方と、async
await
を学びました。
この辺ができればデータの取得はできるので簡単な一覧ページの表示くらいはできるようになっているはずです。
記事詳細のデータも同じような感じです。
TypeScript周りがよくわからなかった人はサバイバルTypeScriptを見るのをおすすめします。
お疲れさまでした!
次回
この記事内で記事詳細ページやPromise.all
を使ったカテゴリごとの記事一覧取得くらいまでやろうと思っていたのですが、思ったより長くなってしまったので以下は次の記事で説明します。
- getStaticPaths
- Promise.all
- [slug].tsx
Discussion