4
Help us understand the problem. What are the problem?

posted at

updated at

React + TypeScript + ESLint + Storybook

React + TypeScript + ESLint + UIテスト環境の構築

初めに

この記事はAizu Advent Calendar 2021 10日目の記事です。

概要

React/TypeScriptにESLintを導入するする際、色々とこけることが多かったので書きました。また、今回UIテストにはStorybookを使用しますが、使い方までは説明しないので公式などを見てください。

前提

  • npmが使える
    • ターミナルでnpm -vを実行して8.1.4などの出力がされればOK
    • されなかった場合はnpmの環境構築をしておいてください

動作環境

  • 機種:MacBook Air (M1 2020)
  • OS:MacOS ver 11.2.3
  • npm:ver 8.1.4
  • node:ver 17.2.0

React/TypeScript

まずReactとTypeScriptのプロジェクトを作ります。今回使うcreate-react-appはプロジェクトの名前のディレクトリを生成するため、先にプロジェクト用のディレクトリを作っておく必要はありません。またこのことから、GitHub上でリポジトリを作る場合PrivateとPublicの選択だけして、READMEの生成などは行わない方が良いと思います。

create-react-app

まずプロジェクトのディレクトリを配置したい場所へ移動してください。移動し終えたら、下記のコマンドをプロジェクト名の部分だけ変更して実行しましょう。

$ npx create-react-app プロジェクト名 --template typescript --use-npm

create-react-appはReactの環境を自動でビルドしてくれます。
プロジェクト名はその名の通りあなたの作りたいプロジェクトの名称にしましょう。ただし、英大文字を使えないそうなので、日本語などは無理だと思います。記号だと、-は使えましたが他は試していないので調べてみてください。
--template typescriptはその環境をTypeScriptのものにしてくれます。
最後に--use-npmですが、これは後ほど説明します。

追記

create-react-app A
Cannot create a project named "A" because of npm naming restrictions:

  * name can no longer contain capital letters

Please choose a different project name.
create-react-app あ
Cannot create a project named "あ" because of npm naming restrictions:

  * name can only contain URL-friendly characters

Please choose a different project name.

上のコマンドを実行したら、cd プロジェクト名で作られたディレクトリに移動してください。

ESLint

初期設定

まず、お使いのエディターでpackage.jsonを開き、

package.json
-  "eslintConfig": {
-     "extends":[
-       "react-app",
-       "react-app/jest"
-     ]
-   },

この部分を削除してください。次にターミナル上で、以下を実行してください。

$ npm install eslint --save-dav

これで、このプロジェクトにeslintのパッケージがインストールされます。とはいえインストールされてもまだ使えないので、初期設定のために以下を実行してください。

$ npx eslint --init

実行するといくつかの質問をされるので以下のように答えてください。

質問内容 選択
1 How would you like to use ESLint? To check syntax, find problems, and enforce code style
2 What type of modules does your project use? JavaScript modules (import/export)
3 Which framework does your project use? React
4 Does your project use TypeScript? Yes
5 Where does your code run? 複数選択が可能です、Browserは選択しておいてください
6 How would you like to define a style for your project? Use a popular style guide
7 Which style guide do you want to follow? Airbnb: https://github.com/airbnb/javascript
8 What format do you want your config file to be in? JSON
9 Would you like to install them now with npm? Yes

長くなりそうなので、ここでは解説しません。

これでプロジェクト内にeslintに必要なパッケージ群がインストールされ、.eslintrc.jsonという、以下のようなファイルが生成されたと思います。

.eslintrc.json
{
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "plugin:react/recommended",
        "airbnb"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": 13,
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "@typescript-eslint"
    ],
    "rules": {
    }
}

これでnpx exlint src/* --fixなどと実行すればESlintがsrc内のコードのうち修正可能な部分を自動整形してくれます。ただ、毎回これを打つのは手間なので、package.json内のscriptsにスクリプトを追加します。

package.json
{
    ...,
    "scripts": {
      "lint": "eslint \"src/**/*.{ts,tsx}\" \"src/*.{ts,tsx}\" --fix",
      ...
    },
    ...
}

こうすることで先程のコマンドをnpm run lintで実行できます。

実行するとおそらく大量のエラーが出たことでしょう。これを今から修正していきます。

ESLintの設定

では、私が手元でnpm run lintを実行して出たエラーを上から順に潰していきたいと思います。

1. error Unable to resolve path to module './App'

以下のコマンドを実行してください

$ npm install eslint-import-resolver-typescript --save-dev

したら、.eslintrc.jsonに以下を追記してください

eslintrc.json
{
  "settings": {
    "import/resolver": {
      "typescript": {}
    }
  },
  ...
}

2. error Missing file extension "tsx" for "./App"

.eslintrc.jsonrules内に以下を追加

eslintrc.json
{
  ...
  "rules": {
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "ts": "never",
        "tsx": "never"
      }
    ]
  }
}

3. error 'test' is not defined

.eslintrc.jsonに以下のように追加。

eslintrc.json
{
  env: {
    "jest/globals": true,
    ...
  },
  ...
  plugins: [
    "jest",
    ...
  ],
  ...
}

4. error JSX not allowed in files with extension '.tsx'

.eslintrc.jsonに以下のように追加。

eslintrc.json
{
  ...
  "rules": {
    "react/jsx-filename-extension": [
      "warn",
      {
        "extensions": [
          ".tsx"
        ]
      }
    ],
    ...
  }
}

追加の設定

これで全てのエラーが消えたかと思います。ここからは私の好みが含まれますがより良い開発のためにいくつか設定していこうと思います。また、設定していることを前提としてやっていくので今回は設定しておいてください。

1. ファンクションコンポーネントをアロー関数で表記するようにする

.eslintrc.jsonに以下のように追加。

eslint.json
{
  ...
  "rules": {
    "react/function-component-definition": [
      2,
      {
        "namedComponents": "arrow-function",
        "unnamedComponents": "arrow-function"
      }
    ],
    ...
  }
}

App.tsxなどでエラーが出ますがnpm run lintを実行すれば修正されます

2. 関数の返り値の型を明示的に記入させる

.eslintrc.jsonに以下のように追加。

eslint.json
{
  ...
  "rules": {
    "@typescript-eslint/explicit-function-return-type": [
      "error",
      {
        "allowExpressions": true
      }
    ],
    ...
  }
}

App.tsxreportWebVitals.tsでエラーが起きていると思うので修正します。

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

const App: React.VFC = () => (
  ...
reportWebVitals.ts
import { ReportHandler } from 'web-vitals';

const reportWebVitals = (onPerfEntry?: ReportHandler): void => {
  ...

Prop Typesに関するルールをオフにする

.eslintrc.jsonに以下のように追加。

eslint.json
{
  ...
  "rules": {
    "react/prop-types": "off",
    "react/require-default-props": "off",
    ...
  }
}

これでESLintの設定は終わりです。一応ここまでで出来上がった.eslintrc.jsonを載せておきます。設定の順番が異なっていたりしてもあまり気にしなくて大丈夫です。

eslintrc.json
{
  "settings": {
    "import/resolver": {
      "typescript": {}
    }
  },
  "env": {
    "jest/globals": true,
    "browser": true,
    "es2021": true
  },
  "extends": [
    "plugin:react/recommended",
    "airbnb"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 13,
    "sourceType": "module"
  },
  "plugins": [
    "react",
    "@typescript-eslint",
    "jest"
  ],
  "rules": {
    "react/prop-types": "off",
    "@typescript-eslint/explicit-function-return-type": [
      "error",
      {
        "allowExpressions": true
      }
    ],
    "react/function-component-definition": [
      2,
      {
        "namedComponents": "arrow-function",
        "unnamedComponents": "arrow-function"
      }
    ],
    "react/jsx-filename-extension": [
      "warn",
      {
        "extensions": [
          ".tsx"
        ]
      }
    ],
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "ts": "never",
        "tsx": "never"
      }
    ]
  }
}

Storybook

以下のコマンドを実行してください

$ npx sb init

途中で

? Do you want to run the 'eslintPlugin' fix on your project?

と聞かれるのでyと入力。

すると、srcディレクトリ内にstoriesディレクトリが生成されます。これで、npm run storybookと実行すればStorybookが起動するのですが、このディレクトリ内に生成されたファイルはESLintによってエラーを吐いてしまうのでStorybookが途中で落ちます。今回は参考用にbutton.cssButton.tsxButton.stories.tsxIntroduction.stories.mdxを残してそれ以外は消してください。

ではこれらのファイルのエラーを無くします。

1. error '@storybook/react' should be listed in the project's dependencies, not devDependencies

package.jsondevDependencies内から"@storybook/react": "^6.4.9",を消し、同様の内容をdependencies内に追記してください。"^6.4.9"の部分は変わっているかもしれないので生成されていた方を使うようにしてください。

2. error Prop spreading is forbidden

該当する行の上に

// eslint-disable-next-line react/jsx-props-no-spreading

と書き込んじゃってください。行儀が悪いですが、Storybook用の型がよくわからないのでこれでどうにかします。

3. error propType "primary" is not required, but has no corresponding defaultProps declaration

interfaceごと消してしまってください。代わりに、以下のように型を宣言してください。

type ButtonProps = {
  label: string,
  primary?: boolean,
  backgroundColor?: string,
  size?: 'small' | 'medium' | 'large',
  onClick?: () => void
}

そして、Buttonの型宣言を修正します。

Button.tsx
...
const Button: React.VFC<ButtonProps> = ({
  primary = false,
  size = 'medium',
  backgroundColor,
  label,
}) => {
  ...

ついでに引数からpropsを消しちゃったので、以下のようにpropsを消します。

    <button
      type="button"
      className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
      style={{ backgroundColor }}
    >

4. error Prefer default export

Button.tsxのButtonの宣言についているexportを外して、ファイルの一番下にexport default Buttonを追記。

Button.tsx
import React from 'react';
import './button.css';

...

const Button: React.VFC<ButtonProps> = ({
  ...
};

export default Button;

これによってButton.stories.tsxでButtonをインポートしている部分を修正しないとなのでします。

Button.stories.tsx
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';

import Button from './Button';

...

これにてStorybookの設定完了です。npm run storybookと実行してみてください。Storybookが立ち上がるはずです。

これにてReact + TypeScript + ESLint + Storybookの環境構築完了です。お疲れ様でした、そして頑張ってください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
4
Help us understand the problem. What are the problem?