LoginSignup
7
1

More than 3 years have passed since last update.

なる早でTypescriptで(勉強用の)Next.jsサーバーを立ち上げる

Last updated at Posted at 2019-06-30

はじめに

この記事では、Reactの学習のために、Typescript(.tsx)でNext.jsのサーバーを立てる方法を説明します。

  • これは動くものを素早く作るためのチュートリアルです
  • 設定ファイルをあえてほとんど書かないことで学習環境立ち上げの速さを実現させます
  • Typescriptを手っ取り早く設定するために、執筆時点ではNext.jsのcanaryカナリヤバージョンを使います

なぜにこの記事が書かれたか

主に理由は2つあります。
  • Qiita書くと就活に有利らしいが、私はQiitaを書いたことがないために投稿の練習をしたいため
  • 私が所属しているラボの後輩はWeb技術に興味津々で、彼らはインターンシップ選考で他者を蹴落とすために可及的速やかにTODOアプリなどを自分のGitHubに置きたく、何かしらのチュートリアルを望んでいるため

そこで、私の就職活動が終わり、彼らのインターンシップ選考が終わったこのタイミングの今こそあれ書くに最適と思い書き始めました。


筆者の環境
  • OS: macOS Mojave 10.14.5
  • ブラウザ: Safari バージョン12.1.1
  • Node.js: v10.16.0
  • Yarn: 1.15.2

今日作るもの: 読者の http://localhost:3000 に以下の内容を表示するサーバーをNext.js立てる。
スクリーンショット 2019-06-30 16.19.23.png

インストールしなければならないモノ

本チュートリアルでは以下2点のソフトウェアを必要とします。それぞれ公式サイトのガイドを見て、よしなにインストールしましょう。

エディタは好きなのを使っていただければいいですが、VSCodeや、ac.jpなどで終わる大学のメアドを持っている学生さんならばWebStormを使うことを勧めるのが罪が無いかと思います。

プロキシの設定

プロキシの設定

大学なりで開発しようとすると、yarnプロキシを設定しないとこれから叩くコマンドのyarn addが通らないかもしれません。

yarnのプロキシを設定するには以下のコマンドをターミナルで叩けば良いです。プロキシの設定において、ユーザー名やパスワードを必要としないならば、その箇所を抜いておいてください。

ユーザー名とパスワードが必要な時
yarn config set proxy http://ユーザー名:パスワード@プロキシのアドレス:プロキシのポート番号
yarn config set https-proxy http://ユーザー名:パスワード@プロキシのアドレス:プロキシのポート番号
ユーザー名が必要な時
yarn config set proxy http://ユーザー名@プロキシのアドレス:プロキシのポート番号
yarn config set https-proxy http://ユーザー名@プロキシのアドレス:プロキシのポート番号
ユーザー名とパスワードがいらない時
yarn config set proxy http://プロキシのアドレス:プロキシのポート番号
yarn config set https-proxy http://プロキシのアドレス:プロキシのポート番号
プロキシ設定の解除方法
yarn config delete proxy
yarn config delete https-proxy

プロジェクトの準備

プログラムと作業ファイルを置きたいフォルダ(ディレクトリ)を何処ぞやに作ってください。

ターミナル(あるいはコマンドプロンプト)を開き、ターミナルのカレントディレクトリをそのフォルダにして下さい。

「カレントディレクトリ?何それ?」という方は、あとあとcdコマンドについて調べて頂くものとして、とりあえず以下の内容を試してください。
1. ターミナルでcdと入力
2. スペースキーを押す
3. さっき作ったフォルダをドラッグ&ドロップ
4. Enterキーを押す
5. pwdと入力しEnterを押した時に、そのフォルダのパスが表示されていればオッケー

このチュートリアルの以下のコマンドは、全てそのディレクトリ上で行われていることを前提とします。また、先ほど作ったフォルダをこのチュートリアルではプロジェクトルートと呼ぶことにします。


パッケージの追加

本チュートリアルで使うパッケージをプロジェクトルートに追加するために、プロジェクトルート上で以下のコマンドを入力します。

yarn add react react-dom next@canary
yarn add -D typescript @types/react @types/react-dom @types/node

/*TODO: Next.js 8.1.1がリリースされたら@canaryを消す*/

コマンドが走り終わると、プロジェクトルートに以下のフォルダとファイルが出来上がります。

プロジェクトルート/
├── node_modules/ ⇦ なんかファイルいっぱい
├── package.json ⇦ 読めそう!
└── yarn.lock ⇦ 人が読めるファイルじゃない

気になるならばpackage.json の中身を見てみましょう。 

以下は執筆時のpackage.jsonの内容です。


{
  "dependencies": {
    "next": "^8.1.1-canary.63",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  },
  "devDependencies": {
    "@types/node": "^12.0.10",
    "@types/react": "^16.8.22",
    "@types/react-dom": "^16.8.4",
    "typescript": "^3.5.2"
  }
}

yarn add で追加したパッケージが"dependencies"にバージョンとともにリストされ、yarn add -Dで追加したパッケージが"devDependencies"にリストされています。また実際のパッケージの中身と、それぞれのパッケージが依存するパッケージがnode_modules/にドサッと入ります。

今後、もしnode_modules/をバッと消しても、

yarn install

を叩けばpackage.jsonの内容(とyarn.lockの内容)をもとに node_modules/に必要な中身を入れてくれます。つまり、node_modules/なしでよその人に作ったプロジェクトを渡しても、向こう側でインストールできるので、よそ様にプロジェクトを渡すときはnode_modules/を消して渡す、gitにはnode_modules/をアップロードしないというのが作法です。

Hello World

さて、ようやっとサーバーを立てます。

プロジェクトルートにpagesという名前のフォルダを作り、その中にindex.tsxを作ります。

プロジェクトルート/
├── node_modules/
├── pages/
│   └── index.tsx
├── package.json
└── yarn.lock

また、index.tsxに以下のプログラムを記述します。

index.tsx
import {FC} from "react";

const Index: FC = () => (
    <p>Hello World!</p>
);

export default Index;

これでTypescript(.tsx)でHello Worldのプログラムができました。では、サーバーを立ち上げましょう。以下のコマンドを入力してください、

yarn next

すると、以下のような表示が出て…

yarn run v1.15.2
warning package.json: No license field
$ [プロジェクトルート]/node_modules/.bin/next
[ wait ]  starting the development server ...
[ info ]  waiting on http://localhost:3000 ...
We detected TypeScript in your project and created a tsconfig.json file for you.

Your tsconfig.json has been populated with default values.

[ info ]  bundled successfully, waiting for typecheck results ...
[ ready ] compiled successfully (ready on http://localhost:3000)

やがて、 http://localhost:3000 にサーバーがreadyしたらしい表示になるので、アクセスしてみましょう。

スクリーンショット 2019-06-30 17.10.33.png

おめでとうございます。Hello Worldが立ち上がりました。

閉じ方

ターミナルに[info]なり[ready]なりが表示されているうちはサーバーが生きているので、サーバーを落としたければ、ターミナル上でCtrl+Cを入力してください。Windowsならば、コマンドプロンプト でCtrl+Cを押した後にyを入力することを求められるかもしれません。素直に従えば、サーバーを落とせます。

しかし、yarn nextで立ち上がるサーバーは開発用サーバーなので、サーバーを立ち上げたままファイルに変更を加えると、サーバーの内容にも変更が加わります。なので、本日、まだこのチュートリアルを続けるつもりならば、立ち上げてままにしてください。

なしてNext.js@canaryを使うたん?

ここのTypescript対応がゼロコンフィグだから。

解説

Next.jsで立ち上げるサーバーではpages/というフォルダと、static/というフォルダの内容が配信されます。
https://github.com/zeit/next.js/

また、特別にpages/index.tsxhttp://localhost:3000 に配信されます。

先ほどのIndexの内容をわかるようにするために、pages/kani.tsxでほぼ等価なページを作ってみましょう。

プロジェクトルート/
├── node_modules/
├── pages/
│   ├── index.tsx
│   └── kani.tsx
├── package.json
└── yarn.lock

内容はこのようにします。

kani.tsx
export default function Kani() {
    return <div>こんにちはかに</div>
}

するとhttp://localhost:3000/kani
スクリーンショット 2019-06-30 17.40.51.png

が配信されます。Next.jsでは、戻り値が特別な関数をexport defaultすると、ページが配信される仕組みになっています。

Typscript(Javascript)は変数に関数を代入できます。従って、このように書いても上と等価です。

const KaniPage = function Kani() {
    return <div>こんにちはかに</div>
};
export default KaniPage; 

Typescriptは型をアノテーションすることができます。先ほどのコードにやたら丁寧にアノテーションをしてみましょう。

import {FC, ReactElement} from "react";

const KaniPage:FC = function Kani():ReactElement {
    return <div>こんにちはかに</div>
};

export default KaniPage;

上記の意味は、変数KaniPageFC型であり、関数Kaniは引数なしで戻り値がReactElement型であるということをアノテーションしています。

ReactElement型について、「さっきの関数はdiv要素を返しているやないか」と思われるかもしれませんが、.tsxファイル内では、

<div>こんにちはかに</div>

React.createElement('div', 'こんにちはかに');

に変換される仕組みになっています。createElementの戻り値がReactElementなんだと思います。知らんけど。

なのでimportに

import * as React from "react";

をつけるのが推奨されています。でも動いているから、ええや。

FCFunctional Componentの略であり、関数型であり、ReactComponentの一種です。

Next.jspages/以下のTypescriptファイルがReactComponentexport defaultをしていたならば、それを表示する仕組みになっています。

次に行きましょう関数は「無名」でも構いません。つまり

import {FC, ReactElement} from "react";

const KaniPage:FC = function():ReactElement {
    return <div>こんにちはかに</div>
};

export default KaniPage;

も等価です。

もっと行きましょう。予約語functionを用いた関数定義は大抵、arrow演算子こと=>で書き換えられます。また、左辺値でFCに代入することを宣言しているので、ReactElementは冗長です。

import {FC} from "react";

const KaniPage:FC = () => {
    return <div>こんにちはかに</div>
};

export default KaniPage;

また、即retrunするアロー関数は'{}'を'()'にすることで、returnを省略できます。

つまり、

import {FC} from "react";

const KaniPage: FC = () => (
    <div>こんにちはかに</div>
);

export default KaniPage;

これで、Indexに何が書いてあるか、なんとなくわかったでしょうか?

もっと気持ち悪く書くと、これでも動きますし、これを書く気分の日もあります。

export default () => (
    <div>こんにちはかに</div>
);

Next.jsの軽い説明

Next.jsを使ってできることを軽く説明します。

画像を配信する

先ほどのkaniページで、カニの画像を表示するようにしましょう。

まず、static/というフォルダを作り、その中に以下のkani.pngを追加しましょう。

kani.png
5つ足カニ by Nanimono Demonai CC BY-SA 3.0

プロジェクトルート/
├── node_modules/
├── pages/
│   ├── index.tsx
│   └── kani.tsx
├── static/
│   └── kani.png
├── package.json
└── yarn.lock

kani.tsxを以下のように書き換えます。

kani.tsx
import {FC} from "react";

const KaniPage: FC = () => (
    <div>
        <img src="/static/kani.png" alt=""/>
        <p>こんにちはかに</p>
    </div>
);

export default KaniPage;

このようなページが配信されました。
スクリーンショット 2019-06-30 18.29.49.png

スタイルを設定する

このままではカニがやたらでかいです。スタイルを設定しましょう。

Next.jsにはstyled jsxが同梱されているので、それでcssを設定してみます。

kani.tsx
import {FC} from "react";

const KaniPage: FC = () => (
    <div>
        <img src="/static/kani.png" className="kani"/>
        <p>こんにちはかに</p>
        { /*language=CSS*/}
        <style jsx>{`
            .kani {
                max-width: 50px;
            }
        `}</style>
    </div>

);

export default KaniPage;

スクリーンショット 2019-06-30 18.34.11.png

だいぶ小さくできました。
tsx上で{}で囲んだ部分は、Typescriptとして評価されます。さらに、{/* */}で囲んだ部分はコメントにできます。

タイトルを設定する

今まで作ってきたものはタイトルが無名でした。カニのページを作っているのですから、ページのタイトルをカニにしましょう。Next.jsでは<head>要素を使うことができません。なので、Next.jsで提供されている<Head>コンポーネントを使います。

kani.tsx
import {FC} from "react";
import Head from "next/head";

const KaniPage: FC = () => (
    <>
        <Head>
            <title>カニのページ</title>
        </Head>
        <div>
            <img src="/static/kani.png" className="kani"/>
            <p>こんにちはかに</p>
            { /*language=CSS*/}

        </div>
        <style jsx>{`
            .kani {
                max-width: 50px;
            }
        `}</style>
    </>
);

export default KaniPage;

スクリーンショット 2019-06-30 18.34.11.png

<>~</>という見慣れないタグがありますが、React.Fragmentという名前のタグです。returnで戻せるタグを複数にすることができないので、一つにまとめるために使うものだと持ってください。

なので、<>~</>を抜くとどんなエラーが出るのか見てみましょう。

これはエラー
import {FC} from "react";
import Head from "next/head";

const KaniPage: FC = () => (
        <Head>
            <title>カニのページ</title>
        </Head>
        <div>
        <img src="/static/kani.png" className="kani"/>
        <p>こんにちはかに</p>
        </div>
);

export default KaniPage;

http://localhost:3000/kani
スクリーンショット 2019-06-30 18.55.24.png

コンパイルエラーが出ましたね。とはいえ、「<>~</>を使うたらわ?」言うてはるで、素直に<>~</>で囲みまひょ。

Typescriptの軽い説明

直に書く

ここまでの知識で冒頭の「今日作るもの」を作ってみましょう。

index.tsx
import * as React from "react";
import Head from "next/head";

export default () => (
    <>
        <Head>
            <title>TitleはHelloWorld</title>
        </Head>
        <h1>Hello World</h1>
        <hr/>
        <p>こんにちは世界<br/>こんばんはカニ</p>
        <img src="/static/kani.png" alt="" className="kani"/>

        <p>1+1={1 + 1}</p>

        <ruby>世界<rt>World</rt></ruby>

        <li>hoge</li>
        <li>fuga</li>
        <li>piyo</li>

        {/*language=CSS*/}
        <style jsx>{`
            .kani {
                max-width: 50px;
            }
        `}</style>
    </>
);

コンポーネントを分ける

直に書いた<p>1+1={1 + 1}</p>はしょうもないですね。もう少し面白くしましょう。

二つの数字lv :number,rv :numberを受け取って<p>lv+rv={lv + rv}</p>を表示するコンポーネントSumを作ってみましょう。

まず、プロジェクトルートcomponents/というディレクトリを作りその中にmyComponent.tsxを作ります。Next.jsでサーバーを作るときはpagesstaticという特別なディレクトリがありますが、それ以外には特別な役割がないので自由に作ることができます。

プロジェクトルート/
├── node_modules/
├── pages/
│   ├── index.tsx
│   └── kani.tsx
├── static/
│   └── kani.png
├── components/
│   └── myComponent.tsx
├── package.json
└── yarn.lock

早速、Typescriptらしく、Sumコンポーネントを定義していきましょう。なお、コンポーネント名は先頭大文字にしてください。

myComponent.tsx
import {FC} from "react";

export interface SumProps {
    lv: number;
    rv: number;
}

export const Sum: FC<SumProps> = (props: SumProps) => (
    <p>{props.lv}+{props.rv}={props.lv + props.rv}</p>
);

interfaceはTypescriptの予約語でこの例では、number型のlvrvをメンバーに持つような型を定義しています。SumProps型にパスするオブジェクトの例は以下です。

const hoge: SumProps = {
    lv: 100,
    rv: 200
};

また今回定義したSumpropsを受け取るFCです。上の例も型付けが過剰です。右辺値はシンプルに以下のように書けます。

export const Sum: FC<SumProps> = props => (
    <p>{props.lv}+{props.rv}={props.lv + props.rv}</p>
);

これをpages/sumTest.tsxで使ってみましょう。sumTest.tsxを作成し、以下のように利用します。

プロジェクトルート/
├── node_modules/
├── pages/
│   ├── index.tsx
│   ├── kani.tsx
│   └── sumTest.tsx
├── static/
│   └── kani.png
├── components/
│   └── myComponent.tsx
├── package.json
└── yarn.lock
sumTest.tsx
import {Sum} from "../components/myComponent";

export default () => (
    <>
        <Sum lv={1} rv={1}/>
        <Sum lv={100} rv={73}/>
        <Sum lv={-300} rv={50}/>
        <Sum lv={100} rv={-50}/>
    </>
)

スクリーンショット 2019-06-30 19.53.37.png
いい感じですね。

ただし、

100+-50=50

が気味が悪いです。あとあと直すようにしましょう。気張ってください。

map関数の素振り

<li>hoge</li>
<li>fuga</li>
<li>piyo</li>

この部分を
文字列配列texts :string[]を受け取って<><li>x_1</li><li>x_2</li>...<li>x_n</li></>を表示するコンポーネントTextListを作ってみましょう。

myComponent.tsx
//追記

export interface TextListProps {
    texts: string[];
}

export const TextList: FC<TextListProps> = props => (
    <>
        {props.texts.map(e => <li key={e}>{e}</li>)}
    </>
);


listTest.tsx
import {TextList} from "../components/myComponent";

export default ()=>(
    <>
        <TextList texts={["hoge","fuga","piyo"]}/>
    </>
);

スクリーンショット 2019-06-30 20.05.44.png

いい感じですね。配列をReact.Element配列に写像することでリストを表現するコンポーネントなどが作れます。そのために、TypescriptのArrayに備え付けのmap関数が多々用いられます。

今回は、mapする際に、一番外側の要素にkey属性をつけています。このkey属性の中身に兄弟となるReact.Elementの中でユニークな値(string型でもnumber型でも)をつけることで、Reactのパフォーマンスが上がります。どうするのが良いのかはよそで調べましょう。

参考: リストと key – React

childrenの素振り(子供を振り回してはならない)

これは天下り的に説明したほうがわかりやすいかもしれません。

myComponent.tsx
//import {FC} from "react"; 取る
import {FC, ReactNode} from "react"; //書き換え

//中略
//追記
import Head from "next/head";

export interface LayoutProps {
    children: ReactNode;
    title: string;
}

export const Layout: FC<LayoutProps> = props => (
    <>
        <Head>
            <title>Titleは{props.title}</title>
        </Head>
        <h1>{props.title}</h1>
        <hr/>
        <div>
            {props.children}
        </div>
    </>
);
layoutTest.tsx
import {Layout} from "../components/myComponent";

export default ()=>(
    <Layout title={"レイアウトテスト"}>
        <div>
            <p>レイアウトの子要素です</p>
            <p> Hello</p>
        </div>
    </Layout>
)

スクリーンショット 2019-06-30 20.26.51.png

つまり、propschildren: ReactNode;を取ると、子要素が取れます。

合体

今日つくるページは、今まで作ったコンポーネントでこのように書くことができます。

index.tsx
import {Layout, Sum, TextList} from "../components/myComponent";

export default () => (
    <Layout title={"Hello World"}>
        <p>こんにちは世界<br/>こんばんはカニ</p>
        <img src="/static/kani.png" alt="" className="kani"/>

        <Sum lv={2} rv={1}/>
        <ruby>世界
            <rt>World</rt>
        </ruby>

        <TextList texts={["hoge", "fuga", "piyo"]}/>

        { /*language=CSS*/}
        <style jsx>{`
            .kani {
                max-width: 50px;
            }
        `}</style>
    </Layout>
);

まとめ

  • Next.jsの8.1.1から、Typescriptがすごく書きやすくなりそう

続き

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1