はじめに
この記事では、Reactの学習のために、Typescript(.tsx)でNext.jsのサーバーを立てる方法を説明します。
- これは動くものを素早く作るためのチュートリアルです
- 設定ファイルをあえてほとんど書かないことで学習環境立ち上げの速さを実現させます
- Typescriptを手っ取り早く設定するために、執筆時点ではNext.jsのcanaryバージョンを使います
そこで、私の就職活動が終わり、彼らのインターンシップ選考が終わったこのタイミングの今こそあれ書くに最適と思い書き始めました。 なぜにこの記事が書かれたか
主に理由は2つあります。
今日作るもの: 読者の http://localhost:3000 に以下の内容を表示するサーバーをNext.js立てる。
インストールしなければならないモノ
本チュートリアルでは以下2点のソフトウェアを必要とします。それぞれ公式サイトのガイドを見て、よしなにインストールしましょう。
エディタは好きなのを使っていただければいいですが、VSCodeや、ac.jpなどで終わる大学のメアドを持っている学生さんならばWebStormを使うことを勧めるのが罪が無いかと思います。 大学なりで開発しようとすると、 yarnのプロキシを設定するには以下のコマンドをターミナルで叩けば良いです。プロキシの設定において、ユーザー名やパスワードを必要としないならば、その箇所を抜いておいてください。
プロキシの設定
プロキシの設定
yarn
プロキシを設定しないとこれから叩くコマンドのyarn add
が通らないかもしれません。
ユーザー名とパスワードが必要な時
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
に以下のプログラムを記述します。
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したらしい表示になるので、アクセスしてみましょう。
おめでとうございます。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.tsx
は http://localhost:3000 に配信されます。
先ほどのIndexの内容をわかるようにするために、pages/kani.tsx
でほぼ等価なページを作ってみましょう。
プロジェクトルート/
├── node_modules/
├── pages/
│ ├── index.tsx
│ └── kani.tsx
├── package.json
└── yarn.lock
内容はこのようにします。
export default function Kani() {
return <div>こんにちはかに</div>
}
するとhttp://localhost:3000/kani に
が配信されます。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;
上記の意味は、変数KaniPage
はFC
型であり、関数Kani
は引数なしで戻り値がReactElement
型であるということをアノテーションしています。
ReactElement
型について、「さっきの関数はdiv要素を返しているやないか」と思われるかもしれませんが、.tsxファイル内では、
<div>こんにちはかに</div>
は
React.createElement('div', 'こんにちはかに');
に変換される仕組みになっています。createElement
の戻り値がReactElement
なんだと思います。知らんけど。
なのでimportに
import * as React from "react";
をつけるのが推奨されています。でも動いているから、ええや。
FC
はFunctional Componentの略であり、関数型であり、React
のComponent
の一種です。
Next.js
はpages/
以下のTypescriptファイルがReact
のComponent
をexport 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
を追加しましょう。
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
を以下のように書き換えます。
import {FC} from "react";
const KaniPage: FC = () => (
<div>
<img src="/static/kani.png" alt=""/>
<p>こんにちはかに</p>
</div>
);
export default KaniPage;
スタイルを設定する
このままではカニがやたらでかいです。スタイルを設定しましょう。
Next.js
にはstyled jsx
が同梱されているので、それでcssを設定してみます。
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;
だいぶ小さくできました。
tsx上で{}
で囲んだ部分は、Typescriptとして評価されます。さらに、{/* */}
で囲んだ部分はコメントにできます。
タイトルを設定する
今まで作ってきたものはタイトルが無名でした。カニのページを作っているのですから、ページのタイトルをカニにしましょう。Next.js
では<head>
要素を使うことができません。なので、Next.js
で提供されている<Head>
コンポーネントを使います。
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;
<>
~</>
という見慣れないタグがありますが、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;
コンパイルエラーが出ましたね。とはいえ、「<>
~</>
を使うたらわ?」言うてはるで、素直に<>
~</>
で囲みまひょ。
Typescriptの軽い説明
直に書く
ここまでの知識で冒頭の「今日作るもの」を作ってみましょう。
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
でサーバーを作るときはpages
とstatic
という特別なディレクトリがありますが、それ以外には特別な役割がないので自由に作ることができます。
プロジェクトルート/
├── node_modules/
├── pages/
│ ├── index.tsx
│ └── kani.tsx
├── static/
│ └── kani.png
├── components/
│ └── myComponent.tsx
├── package.json
└── yarn.lock
早速、Typescriptらしく、Sum
コンポーネントを定義していきましょう。なお、コンポーネント名は先頭大文字にしてください。
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
型のlv
とrv
をメンバーに持つような型を定義しています。SumProps
型にパスするオブジェクトの例は以下です。
const hoge: SumProps = {
lv: 100,
rv: 200
};
また今回定義したSum
はprops
を受け取る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
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}/>
</>
)
ただし、
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
を作ってみましょう。
//追記
export interface TextListProps {
texts: string[];
}
export const TextList: FC<TextListProps> = props => (
<>
{props.texts.map(e => <li key={e}>{e}</li>)}
</>
);
import {TextList} from "../components/myComponent";
export default ()=>(
<>
<TextList texts={["hoge","fuga","piyo"]}/>
</>
);
いい感じですね。配列をReact.Element
配列に写像することでリストを表現するコンポーネントなどが作れます。そのために、TypescriptのArrayに備え付けのmap
関数が多々用いられます。
今回は、mapする際に、一番外側の要素にkey属性をつけています。このkey属性の中身に兄弟となるReact.Element
の中でユニークな値(string
型でもnumber
型でも)をつけることで、React
のパフォーマンスが上がります。どうするのが良いのかはよそで調べましょう。
参考: リストと key – React
childrenの素振り(子供を振り回してはならない)
これは天下り的に説明したほうがわかりやすいかもしれません。
//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>
</>
);
import {Layout} from "../components/myComponent";
export default ()=>(
<Layout title={"レイアウトテスト"}>
<div>
<p>レイアウトの子要素です</p>
<p> Hello</p>
</div>
</Layout>
)
つまり、props
にchildren: ReactNode;
を取ると、子要素が取れます。
合体
今日つくるページは、今まで作ったコンポーネントでこのように書くことができます。
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がすごく書きやすくなりそう
続き