Next.jsで猫画像ジェネレーターを作ろう
Next.jsの概要
Next.jsは、オープンソースのUIライブラリReactをベースにしたフロントエンドフレームワークです。
Reactで開発する場合、webpackのようなバンドラーを用いるのが普通です。webpackの設定ファイルを記述するには、一定の知識が必要です。特に、チャンク分割やCSS Modulesの設定は複雑だったりと、設定ファイルのメンテナンスが大変です。Next.jsは、webpackの設定があらかじめなされた状態で開発が始められるようになっています。
Next.jsはルーティング時のプリフェッチや画像の最適化などのパフォーマンス最適化をフレームワーク内で内包しており、ゼロコンフィグで簡単にパフォーマンスの高いアプリケーションを構築することができます。ページ単位のサーバーサイドレンダリング(SSR)や静的サイト生成(SSG)の機能も提供しており、用途に合わせて柔軟にアーキテクチャを選択できるのも特徴です。
Next.jsはVercel社が開発しています。同社はVercelというホスティングサービスを提供しているので、Next.jsで構築したアプリケーションは簡単に公開できます。
これから作るもの
このチュートリアルでは、題して「猫画像ジェネレーター」です。どんなものかというと、ボタンを押したら、猫画像のAPIから画像のURLを取得し、ランダムに可愛い猫画像を表示するシンプルなウェブアプリケーションです。
最終的な成果物はこちらで確認できます。チュートリアルを開始する前に事前に触ってみることで、各ステップでどんな実装をしているかのイメージが掴みやすくなります。
また、ソースコードはyytypescript/random-catで確認することができます。
Next.jsのセットアップ
最初に create-next-app
でプロジェクトを作成します。TypeScriptをベースにしたプロジェクトを作成するために --example with-typescript
を指定します。random-cat
は作成するリポジトリの名前なので好きな名前で作成してください。
sh
yarn create next-app --example with-typescript random-cat
sh
yarn create next-app --example with-typescript random-cat
プロジェクトが作成できたら、早速アプリケーションを起動してみます。作成されたリポジトリに移動してyarn dev
を実行します。
sh
cd random-catyarn dev
sh
cd random-catyarn dev
アプリケーションの起動したら、ターミナルに表示されているURLにブラウザでアクセスしてください。
このチュートリアルに必要なもの
このチュートリアルで必要なものは次のとおりです。
- Node.js v16以上
- Yarn v1系 (このチュートリアルはv1.22.17で動作確認しています)
Node.jsの導入については、開発環境の準備をご覧ください。
パッケージ管理ツールとしてYarnを利用します。最初にインストールをしておきましょう。すでにインストール済みの方はここのステップはスキップして大丈夫です。
shell
npm install -g yarn
shell
npm install -g yarn
不要なファイルを削除する
チュートリアルを進める前に、ここでは使わないファイルを削除します。プロジェクトをシンプルな状態にして、作業を進めやすくしましょう。
ディレクトリ構造を変更するので、先ほど起動したdevサーバーは一度停止してください。
最初にソースファイルのディレクトリをすべて削除して、src/pages/index.tsxを作成します。
sh
rm -rf pages utils interfaces componentsmkdir -p src/pages && touch src/pages/index.tsx
sh
rm -rf pages utils interfaces componentsmkdir -p src/pages && touch src/pages/index.tsx
Next.jsでは、pages
ディレクトリ配下のディレクトリ構造がページのルーティングに対応します。たとえば、src/pages/users.tsxとファイルを作成すると、/users
へアクセスしたとき、それが実行されます。src/pages/index.tsxの場合は、/
へアクセスしたときに実行されます。
このpages
ディレクトリに置かれたコンポーネントのことを、Next.jsの用語でページコンポーネントと呼びます。
最初のページコンポーネントを実装する
index.tsxで「Hello,Next.js👋」と表示するようにコンポーネントを実装してみましょう。
pages/index.tsxtsx
constIndexPage = () => {return <h1 >Hello, Next.js 👋</h1 >;};export defaultIndexPage ;
pages/index.tsxtsx
constIndexPage = () => {return <h1 >Hello, Next.js 👋</h1 >;};export defaultIndexPage ;
コンポーネントの実装が完了したら、yarn dev
を実行し、改めてdevサーバーを起動してブラウザで確認してみてください。
猫の画像を表示する
最初はAPIでデータを取得せずに静的なURLを指定して画像を表示します。
先ほど「Hello, Next.js 👋」と表示していた箇所を次のように書き換えてみてください。
pages/index.tsxtsx
constIndexPage = () => {return <img src ="https://cdn2.thecatapi.com/images/bpc.jpg" />;};export defaultIndexPage ;
pages/index.tsxtsx
constIndexPage = () => {return <img src ="https://cdn2.thecatapi.com/images/bpc.jpg" />;};export defaultIndexPage ;
猫画像を表示することができました。
猫の画像URLを状態で管理する
猫の画像を切り替えられるようにするために、src属性に直接指定していた画像URLをcatImageUrl
の状態変数で管理するように変更します。
pages/index.tsxtsx
import {useState } from "react";constIndexPage = () => {const [catImageUrl ,setCatImageUrl ] =useState ("https://cdn2.thecatapi.com/images/bpc.jpg");return <img src ={catImageUrl } />;};export defaultIndexPage ;
pages/index.tsxtsx
import {useState } from "react";constIndexPage = () => {const [catImageUrl ,setCatImageUrl ] =useState ("https://cdn2.thecatapi.com/images/bpc.jpg");return <img src ={catImageUrl } />;};export defaultIndexPage ;
ボタンクリックでランダムに画像を切り替える
配列で保持した猫画像のURLをランダムに返すrandomCatImage
関数を実装します。
ボタンを新しく追加して、ボタンがクリックされた時にsetCatImageUrl(randomCatImage())
でランダムな画像URLでcatImageUrl
の状態を更新して猫画像を変更するようにします。
pages/index.tsxtsx
import {useState } from "react";constcatImages : string[] = ["https://cdn2.thecatapi.com/images/bpc.jpg","https://cdn2.thecatapi.com/images/eac.jpg","https://cdn2.thecatapi.com/images/6qi.jpg",];constrandomCatImage = (): string => {constindex =Math .floor (Math .random () *catImages .length );returncatImages [index ];};constIndexPage = () => {const [catImageUrl ,setCatImageUrl ] =useState ("https://cdn2.thecatapi.com/images/bpc.jpg");consthandleClick = () => {setCatImageUrl (randomCatImage ());};return (<div ><button onClick ={handleClick }>きょうのにゃんこ🐱</button ><div style ={{marginTop : 8 }}><img src ={catImageUrl } /></div ></div >);};export defaultIndexPage ;
pages/index.tsxtsx
import {useState } from "react";constcatImages : string[] = ["https://cdn2.thecatapi.com/images/bpc.jpg","https://cdn2.thecatapi.com/images/eac.jpg","https://cdn2.thecatapi.com/images/6qi.jpg",];constrandomCatImage = (): string => {constindex =Math .floor (Math .random () *catImages .length );returncatImages [index ];};constIndexPage = () => {const [catImageUrl ,setCatImageUrl ] =useState ("https://cdn2.thecatapi.com/images/bpc.jpg");consthandleClick = () => {setCatImageUrl (randomCatImage ());};return (<div ><button onClick ={handleClick }>きょうのにゃんこ🐱</button ><div style ={{marginTop : 8 }}><img src ={catImageUrl } /></div ></div >);};export defaultIndexPage ;
ブラウザでボタンを何回かクリックすることで、猫の画像を更新できます。
The Cat API について
APIリクエストで猫の画像を取得する前に利用するAPIについて簡単に紹介します。
このチュートリアルでは猫の画像をランダムに表示するにあたりThe Cat APIを利用します。
このAPIは特定の条件で猫の画像を取得したり、品種ごとの猫の情報を取得することができます。
今回のチュートリアルではAPIドキュメントのQuickstartに記載されている/v1/images/search
へリクエストを投げてランダムな猫の画像を取得します。
試しにブラウザでhttps://api.thecatapi.com/v1/images/searchへアクセスしてみてください。
ランダムな結果が返ってくるので値は少し違うと思いますが、次のような構造のデータがレスポンスとして取得できます。レスポンスのデータ構造が配列になっている点に注意してください。
レスポンスで得られるurl
が猫の画像へアクセスするためのURLです。今回はこの値を取得して猫の画像をランダムに表示します。
json
[{"breeds": [],"categories": [{"id": 2,"name": "space"}],"id": "5dc","url": "https://cdn2.thecatapi.com/images/5dc.jpg","width": 760,"height": 500}]
json
[{"breeds": [],"categories": [{"id": 2,"name": "space"}],"id": "5dc","url": "https://cdn2.thecatapi.com/images/5dc.jpg","width": 760,"height": 500}]
APIリクエストで猫の画像を取得
最初にAPIリクエストで猫の画像を取得するfetchCatImage
を実装してコンソールで確認してみます。
fetch
はHTTPリクエストでリソースを取得するブラウザ標準のAPIです。戻り値としてResponseオブジェクトを返します。Responseオブジェクトのjson()
メソッドを実行することで、レスポンスのBodyテキストをJSONオブジェクトに変換するPromiseを取得できます。
pages/index.tsxtsx
import {useEffect ,useState } from "react";constcatImages : string[] = ["https://cdn2.thecatapi.com/images/bpc.jpg","https://cdn2.thecatapi.com/images/eac.jpg","https://cdn2.thecatapi.com/images/6qi.jpg",];constrandomCatImage = (): string => {constindex =Math .floor (Math .random () *catImages .length );returncatImages [index ];};constfetchCatImage = async () => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = awaitres .json ();returnresult [0];};fetchCatImage ().then ((image ) => {console .log (`猫の画像: ${image .url }`);});constIndexPage = () => {const [catImage ,setCatImage ] =useState <string | undefined>(undefined );useEffect (() => {setCatImage (randomCatImage ());}, []);consthandleClick = () => {setCatImage (randomCatImage ());};return (<div ><button onClick ={handleClick }>きょうのにゃんこ🐱</button ><div style ={{marginTop : 8 }}><img src ={catImage } /></div ></div >);};export defaultIndexPage ;
pages/index.tsxtsx
import {useEffect ,useState } from "react";constcatImages : string[] = ["https://cdn2.thecatapi.com/images/bpc.jpg","https://cdn2.thecatapi.com/images/eac.jpg","https://cdn2.thecatapi.com/images/6qi.jpg",];constrandomCatImage = (): string => {constindex =Math .floor (Math .random () *catImages .length );returncatImages [index ];};constfetchCatImage = async () => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = awaitres .json ();returnresult [0];};fetchCatImage ().then ((image ) => {console .log (`猫の画像: ${image .url }`);});constIndexPage = () => {const [catImage ,setCatImage ] =useState <string | undefined>(undefined );useEffect (() => {setCatImage (randomCatImage ());}, []);consthandleClick = () => {setCatImage (randomCatImage ());};return (<div ><button onClick ={handleClick }>きょうのにゃんこ🐱</button ><div style ={{marginTop : 8 }}><img src ={catImage } /></div ></div >);};export defaultIndexPage ;
ページを読み込んで、ChromeのDevToolsを開きコンソールタブで次のようなテキストが表示されていたら成功です。
猫の画像: https://cdn2.thecatapi.com/images/bhg.jpg
リンクをクリックすることで、猫の画像をブラウザで確認することもできます。
APIのレスポンスに型付け
今の状態だと fetchCatImage()
の戻り値が any
のままなので、呼び出し側で存在しないプロパティを参照しても気づけずにバグが発生する危険性があります。
pages/index.tsxts
constfetchCatImage = async () => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = awaitres .json ();returnresult [0];};fetchCatImage ().then ((image ) => {// 戻り値がany型なので型エラーにならないconsole .log (image .alt );});
pages/index.tsxts
constfetchCatImage = async () => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = awaitres .json ();returnresult [0];};fetchCatImage ().then ((image ) => {// 戻り値がany型なので型エラーにならないconsole .log (image .alt );});
APIレスポンスの取り扱いはフロントエンドでバグが混在しやすい箇所なので、型を指定することで安全にAPIレスポンスを扱えるようにしていきます。
レスポンスに含まれる猫画像の型をSearchCatImage
として定義し、レスポンスのデータ構造をSearchCatImageResponse
として定義します。
pages/index.tsxts
interfaceCatCategory {id : number;name : string;}interfaceSearchCatImage {breeds : string[];categories :CatCategory [];id : string;url : string;width : number;height : number;}typeSearchCatImageResponse =SearchCatImage [];
pages/index.tsxts
interfaceCatCategory {id : number;name : string;}interfaceSearchCatImage {breeds : string[];categories :CatCategory [];id : string;url : string;width : number;height : number;}typeSearchCatImageResponse =SearchCatImage [];
fetchCatImage
関数の戻り値をPromise<SearchCatImageResponse>
として型を指定します。
res.json()
は型定義にてPromise<any>
を返すようになっているので、型アサーション(as)を使いas SearchCatImageResponse
とAPIレスポンスの型を指定しています。
これで、APIレスポンスに存在しないプロパティを指定している箇所で型エラーが発生するようになります。
pages/index.tsxts
interfaceCatCategory {id : number;name : string;}interfaceSearchCatImage {breeds : string[];categories :CatCategory [];id : string;url : string;width : number;height : number;}typeSearchCatImageResponse =SearchCatImage [];constfetchCatImage = async ():Promise <SearchCatImage > => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = (awaitres .json ()) asSearchCatImageResponse ;returnresult [0];};fetchCatImage ().then ((image ) => {Property 'alt' does not exist on type 'SearchCatImage'.2339Property 'alt' does not exist on type 'SearchCatImage'.console .log (image .); alt });
pages/index.tsxts
interfaceCatCategory {id : number;name : string;}interfaceSearchCatImage {breeds : string[];categories :CatCategory [];id : string;url : string;width : number;height : number;}typeSearchCatImageResponse =SearchCatImage [];constfetchCatImage = async ():Promise <SearchCatImage > => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = (awaitres .json ()) asSearchCatImageResponse ;returnresult [0];};fetchCatImage ().then ((image ) => {Property 'alt' does not exist on type 'SearchCatImage'.2339Property 'alt' does not exist on type 'SearchCatImage'.console .log (image .); alt });
型アサーションはコンパイラーの型推論を上書きするため、誤ってバグを生む危険性があります。利用は最小限にして必要な場合に限り使うようにしましょう。
📄️ 型アサーション「as」
ワンポイント解説: HTTPリクエストとジェネリクス
axios
などのHTTPクライアントライブラリではレスポンスの型をジェネリクスで指定できるようになっています。ジェネリクスで型指定ができる場合は積極的にジェネリクスを使うようにしましょう。
ts
axios.get<SearchCatImageResponse>("https://api.thecatapi.com/v1/images/search");
ts
axios.get<SearchCatImageResponse>("https://api.thecatapi.com/v1/images/search");
ボタンをクリックした時にAPIで猫画像を更新
APIリクエストでランダムな猫画像の取得ができるようになったので、ボタンをクリックした時にAPIリクエストで猫画像を取得して画面を更新します。
pages/index.tsxtsx
import {useState } from "react";interfaceCatCategory {id : number;name : string;}interfaceSearchCatImage {breeds : string[];categories :CatCategory [];id : string;url : string;width : number;height : number;}typeSearchCatImageResponse =SearchCatImage [];constfetchCatImage = async ():Promise <SearchCatImage > => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = (awaitres .json ()) asSearchCatImageResponse ;returnresult [0];};constIndexPage = () => {const [catImageUrl ,setCatImageUrl ] =useState ("https://cdn2.thecatapi.com/images/bpc.jpg");consthandleClick = async () => {constimage = awaitfetchCatImage ();setCatImageUrl (image .url );};return (<div ><button onClick ={handleClick }>きょうのにゃんこ🐱</button ><div style ={{marginTop : 8 }}><img src ={catImageUrl }width ={500}height ="auto" /></div ></div >);};export defaultIndexPage ;
pages/index.tsxtsx
import {useState } from "react";interfaceCatCategory {id : number;name : string;}interfaceSearchCatImage {breeds : string[];categories :CatCategory [];id : string;url : string;width : number;height : number;}typeSearchCatImageResponse =SearchCatImage [];constfetchCatImage = async ():Promise <SearchCatImage > => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = (awaitres .json ()) asSearchCatImageResponse ;returnresult [0];};constIndexPage = () => {const [catImageUrl ,setCatImageUrl ] =useState ("https://cdn2.thecatapi.com/images/bpc.jpg");consthandleClick = async () => {constimage = awaitfetchCatImage ();setCatImageUrl (image .url );};return (<div ><button onClick ={handleClick }>きょうのにゃんこ🐱</button ><div style ={{marginTop : 8 }}><img src ={catImageUrl }width ={500}height ="auto" /></div ></div >);};export defaultIndexPage ;
APIリクエストを経由して猫の画像をランダムに表示できるようになりました。😺
Next.jsのデータフェッチAPI
実装に入る前にNext.jsのデータフェッチAPIについて簡単に紹介します。
Next.jsではページコンポーネントでデータをフェッチする方法として、getInitialProps
, getServerSideProps
,getStaticProps
の3つがあります。
getInitialProps
SSR(サーバーサイドレンダリング)時にサーバー側でデータ取得の処理が実行され、クライアントサイドでルーティングが発生した場合はクライアント側でデータの取得が実行されます。このAPIはサーバーとクライアントの両方で実行されるため、ユニバーサルな実装を意識する必要もあり実装難易度が上昇します。
公式ドキュメントでもこのAPIよりも次で紹介する2つのデータフェッチ手法を使うことが推奨されています。
getServerSideProps
SSRではgetInitialProps
と同様にサーバー側でデータの取得が実行されます。大きな違いはクライアントサイドでルーティングが発生した場合もデータの取得がサーバー側で実行される点です。サーバー側のみで実行されることが保証されるため、getServerSideProps
内でのみ利用しているモジュールはクライアントのコードにバンドルされず配信するファイルサイズを削減することができます。また、ユニバーサルな実装を意識する必要も無いので考慮すべき点が減り、データベースから直接データを取得するような処理を記述することも可能です。
getStaticProps
ビルド実行時にのみデータ取得が実行されます。これは他の2つと異なりページを描画する時にはデータ取得が実行されないことに注意が必要です。ユーザーログインが不要なLPやブログなどの静的なページを構築する時に利用します。
getServerSidePropsで初期画像もランダムに表示
これまでページを描画する時は固定の猫画像を表示していました。ページを描画するタイミングでもランダムな猫画像を表示するようにします。
最初にIndexPage
コンポーネントで猫画像のURLをinitialCatImageUrl
プロパティとしてpropsで受け取るようにします。コンポーネントのpropsの型をIndexPageProps
として定義して、IndexPage: NextPage<IndexPageProps>
で受け取るpropsの型を指定します。
この時点では、initialCatImageUrl
はundefinedとなるので、一時的に猫画像が表示されない状態になっています。
pages/index.tsxtsx
import {useState } from "react";import type {NextPage } from "next";interfaceCatCategory {id : number;name : string;}interfaceSearchCatImage {breeds : string[];categories :CatCategory [];id : string;url : string;width : number;height : number;}typeSearchCatImageResponse =SearchCatImage [];constfetchCatImage = async () => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = (awaitres .json ()) asSearchCatImageResponse ;returnresult [0];};interfaceIndexPageProps {initialCatImageUrl : string;}constIndexPage :NextPage <IndexPageProps > = ({initialCatImageUrl }) => {const [catImageUrl ,setCatImageUrl ] =useState (initialCatImageUrl );consthandleClick = async () => {constimage = awaitfetchCatImage ();setCatImageUrl (image .url );};return (<div ><button onClick ={handleClick }>きょうのにゃんこ🐱</button ><div style ={{marginTop : 8 }}><img src ={catImageUrl }width ={500}height ="auto" /></div ></div >);};export defaultIndexPage ;
pages/index.tsxtsx
import {useState } from "react";import type {NextPage } from "next";interfaceCatCategory {id : number;name : string;}interfaceSearchCatImage {breeds : string[];categories :CatCategory [];id : string;url : string;width : number;height : number;}typeSearchCatImageResponse =SearchCatImage [];constfetchCatImage = async () => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = (awaitres .json ()) asSearchCatImageResponse ;returnresult [0];};interfaceIndexPageProps {initialCatImageUrl : string;}constIndexPage :NextPage <IndexPageProps > = ({initialCatImageUrl }) => {const [catImageUrl ,setCatImageUrl ] =useState (initialCatImageUrl );consthandleClick = async () => {constimage = awaitfetchCatImage ();setCatImageUrl (image .url );};return (<div ><button onClick ={handleClick }>きょうのにゃんこ🐱</button ><div style ={{marginTop : 8 }}><img src ={catImageUrl }width ={500}height ="auto" /></div ></div >);};export defaultIndexPage ;
続いてgetServerSideProps
で猫画像を取得して、IndexPage
にpropsとして渡します。
pages/index.tsxtsx
import {useState } from "react";import type {NextPage ,GetServerSideProps } from "next";interfaceCatCategory {id : number;name : string;}interfaceSearchCatImage {breeds : string[];categories :CatCategory [];id : string;url : string;width : number;height : number;}typeSearchCatImageResponse =SearchCatImage [];constfetchCatImage = async () => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = (awaitres .json ()) asSearchCatImageResponse ;returnresult [0];};interfaceIndexPageProps {initialCatImageUrl : string;}constIndexPage :NextPage <IndexPageProps > = ({initialCatImageUrl }) => {const [catImageUrl ,setCatImageUrl ] =useState (initialCatImageUrl );consthandleClick = async () => {constimage = awaitfetchCatImage ();setCatImageUrl (image .url );};return (<div ><button onClick ={handleClick }>きょうのにゃんこ🐱</button ><div style ={{marginTop : 8 }}><img src ={catImageUrl }width ={500}height ="auto" /></div ></div >);};export constgetServerSideProps :GetServerSideProps <IndexPageProps > = async () => {constcatImage = awaitfetchCatImage ();return {props : {initialCatImageUrl :catImage .url ,},};};export defaultIndexPage ;
pages/index.tsxtsx
import {useState } from "react";import type {NextPage ,GetServerSideProps } from "next";interfaceCatCategory {id : number;name : string;}interfaceSearchCatImage {breeds : string[];categories :CatCategory [];id : string;url : string;width : number;height : number;}typeSearchCatImageResponse =SearchCatImage [];constfetchCatImage = async () => {constres = awaitfetch ("https://api.thecatapi.com/v1/images/search");constresult = (awaitres .json ()) asSearchCatImageResponse ;returnresult [0];};interfaceIndexPageProps {initialCatImageUrl : string;}constIndexPage :NextPage <IndexPageProps > = ({initialCatImageUrl }) => {const [catImageUrl ,setCatImageUrl ] =useState (initialCatImageUrl );consthandleClick = async () => {constimage = awaitfetchCatImage ();setCatImageUrl (image .url );};return (<div ><button onClick ={handleClick }>きょうのにゃんこ🐱</button ><div style ={{marginTop : 8 }}><img src ={catImageUrl }width ={500}height ="auto" /></div ></div >);};export constgetServerSideProps :GetServerSideProps <IndexPageProps > = async () => {constcatImage = awaitfetchCatImage ();return {props : {initialCatImageUrl :catImage .url ,},};};export defaultIndexPage ;
これで、ページを更新するタイミングでもランダムに猫画像が表示されるようになりました。🎉
プロダクションビルドと実行
Next.jsではnext build
を実行することで最適化されたプロダクション用のコードを生成でき、next start
で生成されたプロダクションコードを実行できます。
このチュートリアルではボイラテンプレートを利用しているので、package.jsonにbuildコマンドとstartコマンドがすでに用意されています。
yarn build
とyarn start
を実行して本番用のアプリケーションを実行してみましょう。
sh
yarn build && yarn start
sh
yarn build && yarn start
アプリケーション起動後にhttp://localhost:3000へブラウザでアクセスをすることで、本番用のアプリケーションの実行を確認できます。
以上でNext.jsで作る猫画像ジェネレーターの完成です。😸