🐥

TypeScript × Reactの環境構築を極限までわかりやすく

2022/11/21に公開

はじめにTypeScriptについて

TypeScriptはJavaScriptの上位互換としてマイクロソフトが生み出した静的型付け言語です。
JavaScriptとの大きな違いは「型」にあり、強固な型は安全で読みやすいコードを生み出します。それを踏まえたうえでTypeScriptを始めると最初は慣れなくても書き続ければどんどん美しいコードになっていきます。それでは実際に構築してみましょう!

完成したもの

完成した

環境

・node.js => 18.12.1
・typescript => 4.8.4
・react =>18.0.21
・next => 12.3.1
・yarn => 1.22.19
・prettier => 2.7.1
・eslint => 8.25.0

手順①node.jsのインストール

まずはnode.jsを導入しましょう。既に入っている人は飛ばしてください。
入っていない人はnode.js推奨版(LTS)をインストールしてください。選択肢は特にいじらず、nextを押してかまいません。
できたらnode -vを打ち込み、v18.12.1と表示される事を確認してください。

手順②yarnのインストール

node.jsをインストールした事でnpmが使えるようになりました。npm install -g yarnを入力する事でインストールできます。yarn -vでバージョンが出てくればokです!

手順③typescriptプロジェクトの作成

npx create-next-app sample --typescript
と打ち込んでください。sampleには任意のアプリ名で構いません。今はsampleと呼びます。作ったらcode sampleで開きましょう。
ターミナルにyarn devと打つと、localhost:3000のURLがターミナル上で表示されますのでそこにアクセスし、このような画面が表示されれば完了です!

詳細な設定

このままでも大丈夫ですが、ここからは開発をとてもスムーズにする機能を追加していきます。コードを見やすくしてくれたり、自動でデプロイしてくれたり、コードをテストしてくれたりなど、これからの開発でコーディングに集中するためにも、必ず設定することをお勧めします。

手順④eslintの設定

まずはこちらのコードをご覧ください。
また、各プロパティの意味が気になる人はこちらの記事で確認してください。

eslintrc.js
module.exports = {
  root: true,
  extends: [
    "airbnb",
    "airbnb/hooks",
    "plugin:react-hooks/recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "prettier",
  ],
  plugins: ["@typescript-eslint", "react"],
  parser: "@typescript-eslint/parser",
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  settings: {
    react: {
      version: "detect",
    },
  },
  parserOptions: {
    sourceType: "module",
    ecmaFeatures: {
      jsx: true,
    },
  },
  ecmaFeatures: {
    impliedStrict: true, //常にStrictMode
  },
  rules: {
    "react/prop-types": "off",
    "react/self-closing-comp": "error",
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "error",
    "prefer-template": "error",
    "@typescript-eslint/consistent-type-imports": "error",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "no-unreachable": ["error"], //到達できないコードはエラー
  },
};

注意点があります!
一つ目はextends:のプロパティです。こちらでは複数のルールをまとめたパッケージを使用しているのですが、ルールが重複する場合があります。同じの場合はともかく、異なるルールを指定しているときは後に書いたルールが優先されます。指定したのに違う動きをしているときはここを疑ってみて下さい。また、prettierを一番最後にし、eslint:recommendedを最初にすることを覚えておいてください。
二つ目はrulesです。個別のルールはprettierで設定するのであまり凝らない方がいいです。衝突が起きたり、他の人が使う時に面倒になるからです。ここではno-unreachableのみが個別の設定になります。

手順⑤prettierの設定

yarn add prettier --save-dev prettier eslint-config-prettierとコマンドを打ってください。
そしてpackage.jsonファイルの"scripts"
の中に"format": "prettier --write \"./src/**/*.{ts,tsx}\""
を追加してください。

そして、.vscodeディレクトリを作成しその中にsettings.jsonを作成して以下の記述をしてください。

.vscode/settings.json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode", // フォーマッターをPrettierにする
  "editor.formatOnSave": true // ファイル保存時にフォーマットを実行
}

さらにextentions.jsonファイルを作成し、以下を記述します。これでチーム開発の時に必要な拡張機能を知らせることが出来ます。

extentions.json
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "stylelint.vscode-stylelint",
    "styled-components.vscode-styled-components",
    "Code formatter using prettier.prettier-Code formatter",
    "stylelint.stylelint",
    "Integrates ESLint JavaScript into VS Code.ESLint"
  ]
}

そして.prettierrcファイルを作成し、以下の設定をします。

.prettierrc
{
  "singleQuote": true,
  "semi": false,
  "quoteProps": "consistent",
  "jsxBracketSameLine": false,
  "singleAttributePerLine": true,
  "arrowParens": "always"
}

詳しく知りたい人はprettierの公式ドキュメントを読んでください。

手順⑥styled-componentsを導入しよう

一つのファイルにCSSなどを記入できるようになります。コンポーネントを作り、HTMLのタグのように扱えるのです!
コマンドで以下を打ちましょう。
yarn add @types/styled-components

手順⑦MUIを使ってみよう

material-ui(MUI)はモダンで最も人気のあるUIの一つです。
これを使用すると簡単にかっこいいデザインが作れます!
ターミナルで以下を打ち込みましょう。
yarn add @mui/material @mui/styled-engine-sc styled-components
yarn add @emotion/react
yarn add @emotion/styled
これで使用できるようになりました!styled-componentと併せて```index.tsxを書き換えてみましょう!

pages/index.tsx
import type { NextPage } from 'next'
import Head from 'next/head'
import React from 'react'
import styled from 'styled-components'
import { Button } from '@mui/material'

const Container = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  min-height: 100vh;
  padding: 0 0.5rem;
`

const Main = styled.main`
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 5rem 0;
`

const Title = styled.h1`
  margin: 0;
  font-size: 4rem;
  line-height: 1.15;
  text-align: center;
  a {
    color: #0070f3;
    text-decoration: none;
  }
  a:hover,
  a:focus,
  a:active {
    text-decoration: underline;
  }
`

const Description = styled.p`
  font-size: 1.5rem;
  line-height: 1.5;
  text-align: center;
`

const Code = styled.code<{ bgColor: string }>`
  padding: 0.75rem;
  font-family: Menlo, 'Monaco, Lucida Console', 'Liberation Mono',
    'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace;
  font-size: 1.1rem;
  background: ${(props) => props.bgColor};
  border-radius: 5px;
`

const Grid = styled.div`
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  max-width: 800px;
  margin-top: 3rem;
  @media (max-width: 600px) {
    flex-direction: column;
    width: 100%;
  }
`

const Card = styled.a`
  width: 45%;
  padding: 1.5rem;
  margin: 1rem;
  color: inherit;
  text-align: left;
  text-decoration: none;
  border: 1px solid #eaeaea;
  border-radius: 10px;
  transition: color 0.15s ease, border-color 0.15s ease;
  :hover,
  :focus,
  :active {
    color: #0070f3;
    border-color: #0070f3;
  }
  h2 {
    margin: 0 0 1rem;
    font-size: 1.5rem;
  }
  p {
    margin: 0;
    font-size: 1.25rem;
    line-height: 1.5;
  }
`

const Footer = styled.footer`
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100px;
  border-top: 1px solid #eaeaea;
  a {
    display: flex;
    flex-grow: 1;
    align-items: center;
    justify-content: center;
  }
`

const Logo = styled.span`
  height: 1em;
  margin-left: 0.5rem;
`

const HomePage: NextPage = () => {
  return (
    <Container>
      <Head>
        <title>Create Next App</title>
        <meta
          name="description"
          content="Generated by create next app"
        />
        <link
          rel="icon"
          href="favicon.ico"
        />
      </Head>
      <Main>
        <Title>
          Welcome to <a href="https://nextjs.org">Next.js!</a>
        </Title>

        <Description>
          Get started by editing <Code bgColor="#fafafa">pages/index.js</Code>
        </Description>

        <Grid>
          <Card href="https://nextjs.org/docs">
            <h2>Documentation &rarr;</h2>
            <p>Find in-depth information about Next.js features and API.</p>
          </Card>

          <Card href="https://nextjs.org/learn">
            <h2>Learn &rarr;</h2>
            <p>Learn about Next.js in an interactive course with quizzes!</p>
          </Card>

          <Card href="https://github.com/vercel/next.js/tree/master/examples">
            <h2>Examples &rarr;</h2>
            <p>Discover and deploy boilerplate example Next.js projects.</p>
          </Card>

          <Card href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app">
            <h2>Deploy &rarr;</h2>
            <p>
              Instantly deploy your Next.js site to a public URL with Vercel.
            </p>
          </Card>
        </Grid>
        <Button
          type="submit"
          fullWidth
          variant="contained"
          sx={{ mt: 3, mb: 2 }}
          href="https://github.com/mui/material-ui/tree/v5.10.8/docs/data/material/getting-started/templates/dashboard"
        >
          material-ui
        </Button>
      </Main>
      <Footer>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by{' '}
          <Logo>
            <img
              src="vercel.svg"
              alt="Vercel Logo"
              width={72}
              height={16}
            />
          </Logo>
        </a>
      </Footer>
    </Container>
  )
}

export default HomePage

そしたらyarn devでページを開いてみましょう!

このような画面が表示されれば完璧です!material-uiのボタンが追加されていますね!

手順⑧デプロイとテスト

この手順を終えるとコードがおかしくなっていないか。また、pushしたときに自動でデプロイしてくれる機能が付けられます!(簡単な設定が必要です)
今回はgithub pagesにデプロイできるようにします。
.githubディレクトリを作成してください。
その中にdeploy.ymlファイルとtest.ymlファイルを作成し、以下を記述してください。

deploy.yml
name: Deploy Next.js site to Pages

on:
  push:
    branches: ['master']

  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: 'pages'
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Detect package manager
        id: detect-package-manager
        run: |
          if [ -f "${{ github.workspace }}/yarn.lock" ]; then
            echo "::set-output name=manager::yarn"
            echo "::set-output name=command::install"
            echo "::set-output name=runner::yarn"
            exit 0
          elif [ -f "${{ github.workspace }}/package.json" ]; then
            echo "::set-output name=manager::npm"
            echo "::set-output name=command::ci"
            echo "::set-output name=runner::npx --no-install"
            exit 0
          else
            echo "Unable to determine packager manager"
            exit 1
          fi
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '16'
          cache: ${{ steps.detect-package-manager.outputs.manager }}
      - name: Setup Pages
        uses: actions/configure-pages@v2
        with:
          static_site_generator: next
      - name: Restore cache
        uses: actions/cache@v3
        with:
          path: |
            .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
      - name: Install dependencies
        run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
      - name: Build with Next.js
        run: ${{ steps.detect-package-manager.outputs.runner }} next build
      - name: Static HTML export with Next.js
        run: ${{ steps.detect-package-manager.outputs.runner }} next export
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v1
        with:
          path: ./out

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v1


次はテストファイルです。

test.yml
name: Test

on: push

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: setup Node.js
        uses: actions/setup-node@v1
        with:
          node-version: 18
      - uses: actions/cache@v2
        id: client-yarn-cache
        with:
          path: 'node_modules'
          key: client-yarn-${{ hashFiles('yarn.lock') }}
      - run: yarn install
        if: steps.client-yarn-cache.outputs.cache-hit != 'true'
      - run: yarn lint
      - run: yarn typecheck

以上になります。これで必要なパッケージのインストールがまとめて完了したり、デプロイしたりの機能が出来ました!以上で設定は完了です!

追記

webpackとbabelをインストールした。手順はこちらを参照して下さい。

参考文献

https://zenn.dev/big_tanukiudon/articles/c1ab3dba7ba111/

https://mui.com/material-ui/getting-started/installation/

https://zenn.dev/jpn_asane/articles/d7f44682b74fdc/

https://prettier.io/docs/en/options.html/
https://tech.playground.style/javascript/babel-webpack/

Discussion