TypeScriptを導入する前提でReact/Next.jsをはじめる

この記事は「[株式会社オープンストリーム "小ネタ" Advent Calendar 2020](https://qiita.com/advent-calendar/2020/opst-mini)」の2日目の記事です。

## はじめに

フロントエンドフレームワークのVueと比較してTypeScriptが導入しやすいReactで、TypeScriptを前提としたプロジェクトを作成します。


## 環境

```bash
$ node --version
v14.15.0
```

```json:package.json
{
...
  "dependencies": {
    "next": "10.0.3",
    "react": "17.0.1",
    "react-dom": "17.0.1"
  },
  "devDependencies": {
    "@types/node": "^14.14.10",
    "@types/react": "^17.0.0",
    "typescript": "^4.1.2"
  }
}

```

## React のプロジェクトを作る

こちらは TypeScript を使うオプションがあるので使っていきましょう。

```bash
 npx create-react-app --template typescript .
```

## Next.js のプロジェクトを作る

`create-react-app` のようにプロジェクト立ち上げから TypeScript に対応するテンプレートは用意されていません。
なので、プロジェクトを作った後は自分でTypeScriptの設定を追加します。

```bash
npx create-next-app .
```

次にTypeScriptで使うためのパッケージを導入します。

```
yarn add --dev typescript @types/node @types/react @types/react-dom
```

このパッケージを導入しないと `yarn dev` したときに失敗します。

> It looks like you're trying to use TypeScript but do not have the required package(s) installed.
> .
> Please install @types/node by running:
>
>         yarn add --dev @types/node
>
> If you are not trying to use TypeScript, please remove the tsconfig.json file from your package root (and any TypeScript files in your pages directory).

ページを構成するファイルも `jsx` から `tsx` に変更します。

 * `./pages/_app.js` -> `./pages/_app.tsx`
 * `./pages/index.js` -> `./pages/index.tsx`
 * `./pages/api/hello.js` -> `./pages/api/hello.ts`  
   APIは `ts` にする

APIのサンプルには型をつけます。APIを作りこむのはまたの機会にしましょう。

```ts:pages/api/hello.ts
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { NextApiRequest, NextApiResponse } from "next";

export default (req: NextApiRequest, res: NextApiResponse) => {
  res.statusCode = 200
  res.json({ name: 'John Doe' })
}
```

この状態で `yarn dev` を実行してdevelopment用のサーバーを立ち上げるとTypeScriptを使うようになります。
`tsconfig.json` は `yarn dev` を実行したときに自動生成されます。

> We detected TypeScript in your project and created a tsconfig.json file for you.

### おまけ: Reactのページ(コンポーネント)をNext.jsに移植する

Reactのclassで作ったコンポーネントはをそのまま Next.js の pages に移植すると動作します。

例えば `create-react-app` で生成されたサンプルもほぼそのまま移植することができますが、
**外部ファイルとしてCSSや画像が配置されているので** これを解決します。

```tsx:react/src/App.tsx
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;
```

CSSに関してはtsxに書かれているままの記述でインポートできないため、Nextの `_app.tsx` に追記します(グローバルで適用されれます)

```tsx:next/pages/_app.tsx
import '../styles/globals.css'
import '../styles/react.css'  // ./src/App.css

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp
```

> error - ./styles/react.css
> Global CSS cannot be imported from files other than your Custom <App>. Please move all global CSS imports to pages/_app.js. Or convert the import to Component-Level CSS (CSS Modules).
> Read more: https://err.sh/next.js/css-global

logo.svgは `public` に移動させます。また、 `import logo from './logo.svg';` は使えないので、 `img` タグの `src` はWebに公開した時の絶対パスを指定します。
  
Nextに移植したコンポーネントはこんな感じです。
  
```tsx:next/pages/react.tsx
import React from 'react';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src="/logo.svg" className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;
```
 
また、classの形で書いたReactコンポーネントもNext.jsで動作するようです。
例えばこのようなReactコンポーネントも同様にNextのpagesに配置すると動作します。
(簡単なサンプルで試したので、複雑なコンポーネントではうまくいかないかもしれません)

```tsx:react/src/Sample.tsx
import React, { Component } from 'react';

type Props = { ... }
type State = { ... }

class App extends Component<Props, State> {
  constructor(props: Props) {
    super(props)
    ...
    }

  render() {
    return (
     ...
    );
  }
}

export default App;
```

Nuxt.jsの `pages` にVueのコンポーネントを移植した時と同じような感触ですね！


## 参考

Reactの環境構築 — 仕事ですぐに使えるTypeScript ドキュメント
https://future-architect.github.io/typescript-guide/react.html#next-js

Next.jsとTypeScriptでアプリを作る - Qiita
https://qiita.com/kojiro_ueda/items/a490f5dfc77f5f89ecbd#2-webapi%E3%82%82%E4%BD%9C%E3%81%A3%E3%81%A6spa%E3%81%8B%E3%82%89%E5%8F%A9%E3%81%91%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B