https://github.com/imamachi-n/react-typescript-sfc
Single File Components for React & TypeScript
https://github.com/imamachi-n/react-typescript-sfc
eslint material-ui prettier react react-hooks react-router redux styled-components typescript
Last synced: 3 months ago
JSON representation
Single File Components for React & TypeScript
- Host: GitHub
- URL: https://github.com/imamachi-n/react-typescript-sfc
- Owner: Imamachi-n
- Created: 2020-01-26T13:41:04.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2020-03-09T08:06:26.000Z (over 6 years ago)
- Last Synced: 2026-01-03T14:38:51.060Z (6 months ago)
- Topics: eslint, material-ui, prettier, react, react-hooks, react-router, redux, styled-components, typescript
- Language: TypeScript
- Homepage:
- Size: 15.6 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Typescript & React で Single File Components
経年劣化に耐える React のソフトウェア設計を考えるためのサンプルプロジェクト。
学習目的で、実際に React を使ってアプリケーション開発を行う上で必要だと思ったものをまとめてみました。
## 目次
- [Typescript & React で Single File Components](#typescript--react-%e3%81%a7-single-file-components)
- [目次](#%e7%9b%ae%e6%ac%a1)
- [デモ](#%e3%83%87%e3%83%a2)
- [URL(デプロイ先)](#url%e3%83%87%e3%83%97%e3%83%ad%e3%82%a4%e5%85%88)
- [画面スクリーンショット](#%e7%94%bb%e9%9d%a2%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88)
- [トップページ - スクロール](#%e3%83%88%e3%83%83%e3%83%97%e3%83%9a%e3%83%bc%e3%82%b8---%e3%82%b9%e3%82%af%e3%83%ad%e3%83%bc%e3%83%ab)
- [レスポンシブ UI(Mobile 用と Desktop 用)](#%e3%83%ac%e3%82%b9%e3%83%9d%e3%83%b3%e3%82%b7%e3%83%96-uimobile-%e7%94%a8%e3%81%a8-desktop-%e7%94%a8)
- [設計・アーキテクチャ](#%e8%a8%ad%e8%a8%88%e3%83%bb%e3%82%a2%e3%83%bc%e3%82%ad%e3%83%86%e3%82%af%e3%83%81%e3%83%a3)
- [Single File Components for React](#single-file-components-for-react)
- [CloudFront/S3 with AWS CDK](#cloudfronts3-with-aws-cdk)
- [詳細](#%e8%a9%b3%e7%b4%b0)
- [TypeScript & React のプロジェクトを作成](#typescript--react-%e3%81%ae%e3%83%97%e3%83%ad%e3%82%b8%e3%82%a7%e3%82%af%e3%83%88%e3%82%92%e4%bd%9c%e6%88%90)
- [絶対パスで React コンポーネントをインポートできるようにする](#%e7%b5%b6%e5%af%be%e3%83%91%e3%82%b9%e3%81%a7-react-%e3%82%b3%e3%83%b3%e3%83%9d%e3%83%bc%e3%83%8d%e3%83%b3%e3%83%88%e3%82%92%e3%82%a4%e3%83%b3%e3%83%9d%e3%83%bc%e3%83%88%e3%81%a7%e3%81%8d%e3%82%8b%e3%82%88%e3%81%86%e3%81%ab%e3%81%99%e3%82%8b)
- [ESLint & Prettier の導入](#eslint--prettier-%e3%81%ae%e5%b0%8e%e5%85%a5)
- [eslint-config-airbnb](#eslint-config-airbnb)
- [eslint-plugin-react](#eslint-plugin-react)
- [eslint-plugin-react-hooks](#eslint-plugin-react-hooks)
- [eslint-plugin-import](#eslint-plugin-import)
- [eslint-plugin-jsx-a11y](#eslint-plugin-jsx-a11y)
- [eslint-plugin-jest](#eslint-plugin-jest)
- [eslint-plugin-prefer-arrow](#eslint-plugin-prefer-arrow)
- [eslint-plugin-mdx](#eslint-plugin-mdx)
- [@typescript-eslint](#typescript-eslint)
- [@typescript-eslint/parser](#typescript-eslintparser)
- [@typescript-eslint/eslint-plugin](#typescript-eslinteslint-plugin)
- [eslint-plugin-prettier](#eslint-plugin-prettier)
- [Stylelint の導入(オプション)](#stylelint-%e3%81%ae%e5%b0%8e%e5%85%a5%e3%82%aa%e3%83%97%e3%82%b7%e3%83%a7%e3%83%b3)
- [Styled-components](#styled-components)
- [stylelint-config-prettier](#stylelint-config-prettier)
- [husky & lint-staged の導入](#husky--lint-staged-%e3%81%ae%e5%b0%8e%e5%85%a5)
- [lint-staged](#lint-staged)
- [hasky](#hasky)
- [Redux の導入](#redux-%e3%81%ae%e5%b0%8e%e5%85%a5)
- [複数の reducer を結合する](#%e8%a4%87%e6%95%b0%e3%81%ae-reducer-%e3%82%92%e7%b5%90%e5%90%88%e3%81%99%e3%82%8b)
- [React Router の導入](#react-router-%e3%81%ae%e5%b0%8e%e5%85%a5)
- [画面遷移時のスクロール位置の初期化](#%e7%94%bb%e9%9d%a2%e9%81%b7%e7%a7%bb%e6%99%82%e3%81%ae%e3%82%b9%e3%82%af%e3%83%ad%e3%83%bc%e3%83%ab%e4%bd%8d%e7%bd%ae%e3%81%ae%e5%88%9d%e6%9c%9f%e5%8c%96)
- [React Router と Redux の統合](#react-router-%e3%81%a8-redux-%e3%81%ae%e7%b5%b1%e5%90%88)
- [Styled-components の導入](#styled-components-%e3%81%ae%e5%b0%8e%e5%85%a5)
- [Global CSS を指定する方法](#global-css-%e3%82%92%e6%8c%87%e5%ae%9a%e3%81%99%e3%82%8b%e6%96%b9%e6%b3%95)
- [Material-UI の導入](#material-ui-%e3%81%ae%e5%b0%8e%e5%85%a5)
- [Styled-components で定義したスタイルを優先する](#styled-components-%e3%81%a7%e5%ae%9a%e7%be%a9%e3%81%97%e3%81%9f%e3%82%b9%e3%82%bf%e3%82%a4%e3%83%ab%e3%82%92%e5%84%aa%e5%85%88%e3%81%99%e3%82%8b)
- [テーマカラーを設定する](#%e3%83%86%e3%83%bc%e3%83%9e%e3%82%ab%e3%83%a9%e3%83%bc%e3%82%92%e8%a8%ad%e5%ae%9a%e3%81%99%e3%82%8b)
- [上部に固定されたヘッダーを作成](#%e4%b8%8a%e9%83%a8%e3%81%ab%e5%9b%ba%e5%ae%9a%e3%81%95%e3%82%8c%e3%81%9f%e3%83%98%e3%83%83%e3%83%80%e3%83%bc%e3%82%92%e4%bd%9c%e6%88%90)
- [Grid React component のスクリーンサイズに応じた調節](#grid-react-component-%e3%81%ae%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b5%e3%82%a4%e3%82%ba%e3%81%ab%e5%bf%9c%e3%81%98%e3%81%9f%e8%aa%bf%e7%af%80)
- [画面サイズに合わせて、React コンポーネントを表示・非表示をコントロール](#%e7%94%bb%e9%9d%a2%e3%82%b5%e3%82%a4%e3%82%ba%e3%81%ab%e5%90%88%e3%82%8f%e3%81%9b%e3%81%a6react-%e3%82%b3%e3%83%b3%e3%83%9d%e3%83%bc%e3%83%8d%e3%83%b3%e3%83%88%e3%82%92%e8%a1%a8%e7%a4%ba%e3%83%bb%e9%9d%9e%e8%a1%a8%e7%a4%ba%e3%82%92%e3%82%b3%e3%83%b3%e3%83%88%e3%83%ad%e3%83%bc%e3%83%ab)
- [画面トップへスクロールして戻るボタン](#%e7%94%bb%e9%9d%a2%e3%83%88%e3%83%83%e3%83%97%e3%81%b8%e3%82%b9%e3%82%af%e3%83%ad%e3%83%bc%e3%83%ab%e3%81%97%e3%81%a6%e6%88%bb%e3%82%8b%e3%83%9c%e3%82%bf%e3%83%b3)
- [React Helmet の導入](#react-helmet-%e3%81%ae%e5%b0%8e%e5%85%a5)
- [ヘッダー情報を追加する](#%e3%83%98%e3%83%83%e3%83%80%e3%83%bc%e6%83%85%e5%a0%b1%e3%82%92%e8%bf%bd%e5%8a%a0%e3%81%99%e3%82%8b)
- [Storybook の導入(Create React App 用)](#storybook-%e3%81%ae%e5%b0%8e%e5%85%a5create-react-app-%e7%94%a8)
- [Storybook の作成](#storybook-%e3%81%ae%e4%bd%9c%e6%88%90)
- [MDX で Storybook ドキュメントを作成する](#mdx-%e3%81%a7-storybook-%e3%83%89%e3%82%ad%e3%83%a5%e3%83%a1%e3%83%b3%e3%83%88%e3%82%92%e4%bd%9c%e6%88%90%e3%81%99%e3%82%8b)
- [Storybook Deployer を使って GitHub Pages へ Storybook をデプロイする](#storybook-deployer-%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%a6-github-pages-%e3%81%b8-storybook-%e3%82%92%e3%83%87%e3%83%97%e3%83%ad%e3%82%a4%e3%81%99%e3%82%8b)
- [Docker 上で開発環境をセットアップする](#docker-%e4%b8%8a%e3%81%a7%e9%96%8b%e7%99%ba%e7%92%b0%e5%a2%83%e3%82%92%e3%82%bb%e3%83%83%e3%83%88%e3%82%a2%e3%83%83%e3%83%97%e3%81%99%e3%82%8b)
- [VSCode の設定について](#vscode-%e3%81%ae%e8%a8%ad%e5%ae%9a%e3%81%ab%e3%81%a4%e3%81%84%e3%81%a6)
- [拡張機能の管理](#%e6%8b%a1%e5%bc%b5%e6%a9%9f%e8%83%bd%e3%81%ae%e7%ae%a1%e7%90%86)
- [VSCode の設定の管理](#vscode-%e3%81%ae%e8%a8%ad%e5%ae%9a%e3%81%ae%e7%ae%a1%e7%90%86)
- [既存の React プロジェクトのアップデート](#%e6%97%a2%e5%ad%98%e3%81%ae-react-%e3%83%97%e3%83%ad%e3%82%b8%e3%82%a7%e3%82%af%e3%83%88%e3%81%ae%e3%82%a2%e3%83%83%e3%83%97%e3%83%87%e3%83%bc%e3%83%88)
- [Create React App](#create-react-app)
- [Storybook](#storybook)
- [React などの他のパッケージのアップグレード](#react-%e3%81%aa%e3%81%a9%e3%81%ae%e4%bb%96%e3%81%ae%e3%83%91%e3%83%83%e3%82%b1%e3%83%bc%e3%82%b8%e3%81%ae%e3%82%a2%e3%83%83%e3%83%97%e3%82%b0%e3%83%ac%e3%83%bc%e3%83%89)
- [参考資料](#%e5%8f%82%e8%80%83%e8%b3%87%e6%96%99)
- [公式ドキュメント](#%e5%85%ac%e5%bc%8f%e3%83%89%e3%82%ad%e3%83%a5%e3%83%a1%e3%83%b3%e3%83%88)
## デモ
### URL(デプロイ先)
**Storybook - スタイルガイド(GitHub Pages)**
**React アプリケーション(AWS: CloudFront/S3)**
準備中。。。
### 画面スクリーンショット
#### トップページ - スクロール
- ランディングページによくある、リンクをクリックすると自動スクロールされる仕組み。
- トップページの先頭に戻るボタンを表示(スクロールをトリガーにする)。

#### レスポンシブ UI(Mobile 用と Desktop 用)
- Desktop 用の画面では、ナビゲーションをページ上部に表示させる。
- Mobile 用の画面では、ナビゲーションをページ下部に表示させる(指で押しやすいため)。

## 設計・アーキテクチャ
### Single File Components for React
準備中。
**参考資料**
[経年劣化に耐える ReactComponent の書き方](https://qiita.com/Takepepe/items/41e3e7a2f612d7eb094a)
### CloudFront/S3 with AWS CDK
準備中。
## 詳細
### TypeScript & React のプロジェクトを作成
```bash
npx create-react-app react-typescript-sfc --template typescript
```
Adding TypeScript
### 絶対パスで React コンポーネントをインポートできるようにする
相対パスだと、リファクタリングによってコンポーネントの配置を変更した場合に、インポート先もすべて変更する必要が出てくる。絶対パスを指定することにより、上記の問題を回避する。
設定方法としては、`tsconfig.json` に以下の設定を追加するだけ。
```json
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
```
例えば、`components/Button` を以下のように絶対パスを使ってインポートできるようになる。
```tsx
import Button from 'components/Button';
```
おそらく、絶対パスにすると補完が効かなくなるので、さらに、`path-intellisense` を拡張機能として追加する。
**参考資料**
[Create React App - Absolute Imports](https://create-react-app.dev/docs/importing-a-component/#absolute-imports)
[VS Code: “Cannot find module” from root path](https://stackoverflow.com/questions/55063550/vs-code-cannot-find-module-from-root-path)
### ESLint & Prettier の導入
まず、コーディングスタイルを統一するために、ESLint と Prettier の導入を行う。
```bash
yarn add -D \
eslint @types/eslint \
prettier @types/prettier \
@typescript-eslint/eslint-plugin \
@typescript-eslint/parser \
eslint-config-airbnb \
eslint-config-prettier \
eslint-plugin-import \
eslint-plugin-react \
eslint-plugin-react-hooks
eslint-plugin-jsx-a11y \
eslint-plugin-jest \
eslint-plugin-prefer-arrow \
eslint-plugin-mdx \
eslint-plugin-prettier \
@types/eslint-plugin-prettier
```
以下の各種設定を 設定ファイル `.eslitrc.js` に記述する。デフォルトの `.eslitrc` の JSON ファイルの場合、キーにいちいちダブルクオーテーションをつけないといけないので、`JSファイル` にしたほうが書きやすいと思う。
#### eslint-config-airbnb
AirBnb が提供する ESLint の有名な共通設定を導入する。
`eslint-config-airbnb` を導入する際、以下のパッケージが必要になる。
- eslint
- eslint-plugin-import
- eslint-plugin-react
- eslint-plugin-react-hooks
- eslint-plugin-jsx-a11y
- @typescript-eslint/parser
eslint-config-airbnb の導入
#### eslint-plugin-react
`eslint-plugin-react` は React 固有の lint の設定を追加するためのプラグイン。使用するために、`extends` と `plugins` に設定を追加する。
```js
extends: [
'eslint:recommended',
'plugin:react/recommended',
],
plugins: [
'react'
],
```
また、React のバージョンを自動的に特定するために、`detect` の設定を行う(将来的に、`detect` がデフォルトになる予定なので、いずれ設定する必要がなくなる)。
```js
settings: {
react: {
version: 'detect',
},
}
```
さらに、JSX のサポート(ESLint 2+)を追加するために、
```js
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
```
eslint-plugin-react の設定
#### eslint-plugin-react-hooks
`eslint-plugin-react-hooks`は、React Hook に対する lint を設定するためのプラグイン。マニュアル設定を適応する場合、以下のように設定する。
```js
plugins: [
"react-hooks"
],
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error',
},
```
#### eslint-plugin-import
`eslint-plugin-import` は ES2015+ (ES6+) import/export syntax の lint に使われる。
デフォルトではすべてのルールが無効化されているので、`extends` 内でプラグインの設定を行うか、
```js
extends: [
'eslint:recommended',
'plugin:import/errors',
'plugin:import/warnings',
],
```
個別にルールを `rules` 内に書き込む必要がある(両方、設定することも可能)。
```js
plugins: [
'import',
],
rules: {
'import/extensions': [
'error',
'always',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
},
],
'import/prefer-default-export': 'off',
}
```
また、TypeScript を使っている場合は、次の設定を追加する必要がある。このとき、`@typescript-eslint/parser` パッケージが依存パッケージに含める必要がある。
```js
extends: [
'eslint:recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript', // 追加
],
```
eslint-plugin-import のインストール方法
ルールを適応するファイルを以下のように指定する。`import/resolver` では、`src` ディレクトリ以下の `ts` や `tsx` などの拡張子を持つファイルのみを対象とする。
```js
settings: {
'import/resolver': {
node: {
extensions: ['.js', 'jsx', '.ts', '.tsx'],
paths: ['src'],
},
},
}
```
また、`import/parsers` を使うことで、対象のファイルに対して、指定した parser を使用することができる。以下では、`ts` や `tsx` の拡張子を持つファイルに対して、TypeScript 用の parser を使うように設定している。
```js
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
}
```
eslint-plugin-import の設定
#### eslint-plugin-jsx-a11y
`eslint-plugin-jsx-a11y` は Web アクセシビリティに関する lint を行うためのプラグイン。`plugins` で以下のように設定する。
```js
"plugins": [
"jsx-a11y",
]
```
推奨設定を適応する場合、以下のように設定する。
```js
extends: [
'plugin:jsx-a11y/recommended',
]
```
eslint-plugin-jsx-a11y の使い方
#### eslint-plugin-jest
`eslint-plugin-jest` は Jest に対する lint を行うためのプラグイン。以下では、推奨設定とスタイルを強制する設定を示した。
```js
extends: [
'plugin:jest/recommended',
'plugin:jest/style',
],
plugins: [
'jest',
],
```
また、Jest が提供するグローバル変数をホワイトリストに追加するために、以下のように設定を行う。
```js
env: {
'jest/globals': true,
},
```
eslint-plugin-jest の使い方
#### eslint-plugin-prefer-arrow
`eslint-plugin-prefer-arrow` はアロー関数に関する lint を行うためのプラグイン。以下のように設定を行う。
```js
plugins: [
'prefer-arrow',
],
rules: {
'prefer-arrow/prefer-arrow-functions': [
'error',
{
disallowPrototype: true,
singleReturnOnly: true,
classPropertiesAllowed: false,
},
],
}
```
eslint-plugin-prefer-arrow の使い方
#### eslint-plugin-mdx
`eslint-plugin-mdx` は MDX ファイル(JSX/TSX 内に markdown ファイルがかけるようにした拡張構文)を lint するためのプラグイン。以下のように設定を行う。
```js
{
extends: ["plugin:mdx/recommended"]
}
```
eslint-plugin-mdx の使い方
#### @typescript-eslint
#### @typescript-eslint/parser
TypeScript で型の情報を必要とする場合は、必須の設定。
```js
parserOptions: {
project: './tsconfig.json',
}
```
@typescript-eslint/parser の使い方
#### @typescript-eslint/eslint-plugin
`@typescript-eslint/eslint-plugin` は TypeScript の lint を行うためのプラグイン。`@typescript-eslint/parser` がインストールされていることが前提。推奨設定は以下のように設定を行う。
```js
extends: [
'plugin:import/typescript',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
],
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-member-accessibility': 'off',
indent: 'off',
'@typescript-eslint/indent': 'off',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
}
```
@typescript-eslint/eslint-plugin の使い方
#### eslint-plugin-prettier
`eslint-plugin-prettier` は Prettier と ESLint を連携させるためのプラグイン。推奨設定は以下のように設定を行う。また、他のプラグインと連携を行うことできる。
```js
extends: [
'plugin:prettier/recommended',
'prettier/react',
'prettier/standard',
],
```
マニュアルで設定を変更する場合は、以下のように設定を行う。
```js
extends: [
'prettier',
],
plugins: [
'prettier',
],
rules: {
'prettier/prettier': 'error',
}
```
eslint-plugin-prettier の使い方
連携できる ESLint プラグインの一覧
### Stylelint の導入(オプション)
Styled-components に Stylelint と Prettier を導入する。ただし、Styled-components の場合、`--fix` オプションを使うことができないため、自動で修正することは不可能(問題箇所の検知のみ可能)。
```bash
yarn add -D \
stylelint \
@types/stylelint \
stylelint-processor-styled-components \
stylelint-config-styled-components \
stylelint-config-recommended \
stylelint-config-prettier
```
#### Styled-components
`.stylelintrc` ファイルを作成して、以下の Styled-components の設定を追加する。
```json
{
"processors": ["stylelint-processor-styled-components"],
"extends": [
"stylelint-config-recommended",
"stylelint-config-styled-components"
]
}
```
Styled-components tooling
#### stylelint-config-prettier
Prettier と競合するルールを排除する。
```json
{
"extends": ["stylelint-config-prettier"]
}
```
stylelint-config-prettier の使い方
### husky & lint-staged の導入
```bash
yarn add husky lint-staged
```
#### lint-staged
`lint-staged` を使うことで、staged git ファイルのみに対して lint をコマンドで実行することができる。
```json
{
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx}": ["eslint --fix", "git add"]
}
}
```
lint-staged の使い方
#### hasky
`husky` は git での commit・push の前に実行するコマンドを設定できる。ここでは、`lint-staged` と合わせて使用し、コミット時に lint が実行されるように設定する。`package.json` を以下のように設定する。
```json
// package.json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}
```
Husky の使い方
### Redux の導入
```bash
yarn add redux react-redux @types/redux @types/react-redux
```
React Redux - Quick Start
Redux - Configuring Your Store
#### 複数の reducer を結合する
Using `combineReducers`
### React Router の導入
```bash
yarn add react-router-dom @types/react-router-dom
```
React Router - Quick Start
#### 画面遷移時のスクロール位置の初期化
デフォルトの設定では、画面遷移時に前のページのスクロール位置が残ってしまい、ページの最上部から表示されない問題が発生する。
以下の設定を追加し、問題を解消する必要がある。
```tsx
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export default function ScrollRestoration() {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
}
```
```tsx
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import ScrollRestoration from 'components/Common/ScrollRestoration';
function App() {
return (
);
}
```
React Router - Scroll Restoration
#### React Router と Redux の統合
React Router - Redux Integration
### Styled-components の導入
#### Global CSS を指定する方法
createGlobalStyle
### Material-UI の導入
```bash
yarn add @material-ui/core @material-ui/icons
```
Material-UI のインストール
#### Styled-components で定義したスタイルを優先する
CSS インジェクションの順番を、Styled-components が最も優先されるように指定する。
ルートコンポーネントをラップする形で、`StylesProvider` コンポーネントを使用し、プロパティに `injectFirst` を指定することで、Styled-components を最も優先するように設定できる。
```tsx
import { StylesProvider } from '@material-ui/core/styles';
ReactDOM.render(
,
document.getElementById('root'),
);
```
Controlling priority
injectFirst
#### テーマカラーを設定する
Material-UI の `ThemeProvider` を用いて、カスタムテーマを対象のコンポーネントに適応する。このとき、Styled-components でも、Material-UI のカスタムテーマを使うために、`ThemeProvider` を併用する。
モジュール名が同じため、以下のように、Material-UI の `ThemeProvider` を `MaterialThemeProvider`、Styled-components の `ThemeProvider` を `StyledThemeProvider` と定義する。
```tsx
import { ThemeProvider as MaterialThemeProvider } from '@material-ui/styles';
import { ThemeProvider as StyledThemeProvider } from 'styled-components';
ReactDOM.render(
,
document.getElementById('root'),
);
```
カスタムテーマを使用する場合は、以下のように `props` からアクセスできる。ただし、上記の設定のみだと、TypeScript の場合、theme の型が `any` になってしまう。
```tsx
import { AppBar } from '@material-ui/core';
import styled from 'styled-components';
const StyledAppBar = styled(AppBar)`
background-color: ${props => props.theme.palette.primary.main};
`;
```
そこで、Styled-components の TypeScript の型を拡張する。具体的には、Material-UI の Theme を継承した、`DefaultTheme` を新たに定義する。ファイル名は、`styled.d.ts` とする。
`DefaultTheme` は `props.theme` のインターフェースとして使用される。デフォルトでは、`DefaultTheme` は未定義なので、Material-UI の Theme を継承して、カスタムテーマの型を定義している。
```tsx
// import original module declarations
import 'styled-components';
import { Theme } from '@material-ui/core';
// and extend them!
declare module 'styled-components' {
export interface DefaultTheme extends Theme {} // eslint-disable-line
}
```
ESLint のせいで、interface の中身が空の場合、エラーとなるため、`// eslint-disable-line` を指定して、ESLint を無視する(あくまで例外的な処置)。
これにより、VSCode 上で補完が効くようになる。
**参考資料**
[Material-UI のテーマのカスタマイズ](https://material-ui.com/customization/palette/#customization)
[Material-UI のカスタムテーマをコンポーネントに適応する方法](https://material-ui.com/customization/theming/#theme-provider)
[How to use Material-UI theme with styled-components?](https://github.com/mui-org/material-ui/issues/10098)
[Styled-components で TypeScript の型定義ファイルを設定する](https://styled-components.com/docs/api#create-a-declarations-file)
[Material-UI と styled components のテーマの共通化](https://qiita.com/Ouvill/items/c6761c32d31ffb11e114#material-ui-%E3%81%A8-styled-components-%E3%81%AE%E3%83%86%E3%83%BC%E3%83%9E%E3%81%AE%E5%85%B1%E9%80%9A%E5%8C%96)
#### 上部に固定されたヘッダーを作成
`React.cloneElement` を使って、子コンポーネントに `elevation` を `props` として渡す。
カスタム Hook である `useScrollTrigger` を使うことで、スクロールをトリガーとして、対象のコンポーネント(ヘッダーなど)を上部に配置する(デフォルトで elevation の値が `4` であり、スクロール時にこれを `0` にすることで、対象のコンポーネントが上部に固定される)。
```tsx
import React from 'react';
import { useScrollTrigger } from '@material-ui/core';
interface ScrollProps {
children: React.ReactElement;
}
export default function ElevationScroll(props: ScrollProps) {
const { children } = props;
const trigger = useScrollTrigger({
disableHysteresis: true,
threshold: 0,
});
return React.cloneElement(children, {
elevation: trigger ? 4 : 0,
});
}
```
**参考資料**
[Material-UI - Elevate App Bar ](https://material-ui.com/components/app-bar/#elevate-app-bar)
[React - cloneElement()](https://reactjs.org/docs/react-api.html#cloneelement)
#### Grid React component のスクリーンサイズに応じた調節
```
innerWidth |xs sm md lg xl
|--------|--------|--------|--------|-------->
width | xs | sm | md | lg | xl
smUp | show | hide
mdDown | hide | show
```
**参考資料**
[Grid - How it works](https://material-ui.com/components/grid/#how-it-works)
[Grid API](https://material-ui.com/api/grid/)
#### 画面サイズに合わせて、React コンポーネントを表示・非表示をコントロール
Mobile 用と Desktop 用で画面の表示を切り替えるのに有用。
```tsx
// 1. Import Layer
import React from 'react';
import styled from 'styled-components';
import { Hidden } from '@material-ui/core';
import { ContaineredTitle, Title } from './Title';
import { ContaineredScrollButton } from './ScrollButton';
import { ContaineredToDoButton } from './TodoButton';
import { ContaineredApiButton } from './ApiButton';
// 2. Types Layer
type Props = {
className?: string;
};
// 3. DOM Layer
const LeftSide: React.FC = props => {
const { className } = props;
return (
{/* For mobile */}
{/* For Desktop */}
);
};
// 4. Style Layer
export const StyledLeftSide = styled(LeftSide)`
display: flex;
`;
export default StyledLeftSide;
```
Material-UI - Hidden
#### 画面トップへスクロールして戻るボタン
**ScrollTop コンポーネント**
```tsx
// 1. Import Layer
import React from 'react';
import styled from 'styled-components';
import { useScrollTrigger, Zoom } from '@material-ui/core';
type ContainerProps = {
children: React.ReactElement;
};
type Props = {
className?: string;
trigger: boolean;
handleClick: (event: React.MouseEvent) => void;
} & ContainerProps;
// 3. DOM Layer
const ScrollTop: React.FC = props => {
const { className, children, trigger, handleClick } = props;
return (
<>
{children}
>
);
};
// 4. Style Layer
export const StyledScrollTop = styled(ScrollTop)`
position: fixed;
bottom: ${props => props.theme.spacing(2)}px;
right: ${props => props.theme.spacing(2)}px;
`;
// 5. Container Layer
export const ContaineredScrollTop: React.FC = props => {
const trigger = useScrollTrigger({
disableHysteresis: true,
threshold: 100,
});
const handleClick = (event: React.MouseEvent) => {
// TODO: アンカーに`id="#back-to-top-anchor"`を設定する必要あり。
const anchor = (
(event.target as HTMLDivElement).ownerDocument || document
).querySelector('#back-to-top-anchor');
if (anchor) {
anchor.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
};
return (
);
};
export default ContaineredScrollTop;
```
**ScrollTopButton コンポーネント**
```tsx
// 1. Import Layer
import React from 'react';
import styled from 'styled-components';
import { ContaineredScrollTop } from 'components/Common/ScrollTop';
import { Fab } from '@material-ui/core';
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp';
// 2. Types Layer
type Props = {
className?: string;
};
// 3. DOM Layer
const ScrollTopButton: React.FC = props => {
const { className } = props;
return (
);
};
// 4. Style Layer
export const StyledScrollTopButton = styled(ScrollTopButton)``;
export default StyledScrollTopButton;
```
**使用例**
```tsx
// 1. Import Layer
import React from 'react';
import styled from 'styled-components';
import { StyledScrollTopButton } from 'components/Common/ScrollTop/ScrollTopButton';
import { Menu } from './components/Menu';
import { Contents } from './components/Contents';
// 2. Types Layer
type Props = {
className?: string;
};
// 3. DOM Layer
const App: React.FC = props => {
const { className } = props;
return (
{/* Anchor for ScrollToTop */}
{/* ScrollToTop Button */}
);
};
// 4. Style Layer
const StyledApp = styled(App)`
background-color: #282c34;
font-size: calc(10px + 2vmin);
color: white;
`;
export default StyledApp;
```
[Material-UI - Back to top](https://material-ui.com/components/app-bar/#back-to-top)
[MDN - Element.scrollIntoView()](https://developer.mozilla.org/ja/docs/Web/API/Element/scrollIntoView)
### React Helmet の導入
```bash
yarn add react-helmet @types/react-helmet
```
#### ヘッダー情報を追加する
メタ情報やタイトルなどをヘッダー情報として追加することができる。
```tsx
// 1. Import Layer
import React from 'react';
import { Helmet } from 'react-helmet';
// 2. Types Layer
type Props = {};
// 3. DOM Layer
export const Header: React.FC = () => {
return (
<>
React & TypeScript SFC - TopPage
>
);
};
```
React Helmet の使い方
### Storybook の導入(Create React App 用)
以下のコマンドを実行すると、Storybook を動かすのに必要なパッケージやファイル・コマンド群をすべて自動で用意してくれる(TypeScript への対応もやってくれるため、**他に設定はいらない**)。
```bash
npx -p @storybook/cli sb init --type react_scripts
```
`.storybook/main.js` ファイルを以下の通り書き換える。`.js` を `.(js|jsx|ts|tsx)` に変更するだけ。
```js
module.exports = {
stories: ['../src/**/*.stories.(js|jsx|ts|tsx)'],
addons: [
'@storybook/preset-create-react-app',
'@storybook/addon-actions',
'@storybook/addon-links',
],
};
```
**参考文献**
[Storybook - Storybook for React](https://storybook.js.org/docs/guides/guide-react/)
[Storybook - TypeScript Config](https://storybook.js.org/docs/configurations/typescript-config/)
#### Storybook の作成
ここでは、`Component Story Format (CSF)` での記述方法を説明する。
以下に例を示す。
```tsx
import React from 'react';
import { action } from '@storybook/addon-actions';
import { StyledBottomNavi } from 'components/BottomNavigation';
export default {
title: 'BottomNavi',
component: StyledBottomNavi,
};
export const BottomNavi = () => (
);
```
**export default**
| プロパティ | 説明 |
| ---------- | ------------------------------------------------------ |
| tille | サイドメニューに表示されるタイトル(詳細は下記参照)。 |
| component | 対象のコンポーネントを指定。 |
タイトルは、以下のように変換される。
```
name -> 'Name'
someName -> 'Some Name'
someNAME -> 'Some NAME'
some_custom_NAME -> 'Some Custom NAME'
someName1234 -> 'Some Name 1234'
someName1_2_3_4 -> 'Some Name 1 2 3 4'
```
**export**
あとは、表示させたいコンポーネントを `export` するだけ。表示されるコンポーネント名は、`export` したときの変数名で決まる(タイトル名の変換は上記と同様)。
**参考資料**
[Storybook - Component Story Format (CSF)](https://storybook.js.org/docs/formats/component-story-format/)
#### MDX で Storybook ドキュメントを作成する
以下のコマンドを実行して、Storybook Docs のアドオンをインストールする。
```bash
yarn add -D @storybook/addon-docs
```
Storybook の設定ファイル `.storybook/main.js` に以下の設定を追加する。
```js
module.exports = {
stories: ['../src/**/*.stories.(js|jsx|ts|tsx|mdx)'],
addons: ['@storybook/addon-docs'],
};
```
以下に例を示す。
```mdx
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { action } from '@storybook/addon-actions';
import { StyledBottomNavi } from 'components/BottomNavigation';
# Bottom Navi
With `MDX` we can define a story for `BottomNavi` right in the middle of our
markdown documentation.
```
**`` タグ**
| 属性 | 説明 |
| --------- | -------------------------------------------------------------------------------------- |
| title | サイドメニューに表示されるコンポーネント名を記載。`/` で区切ることで階層も表現できる。 |
| component | 対象のコンポーネントを指定 |
**`` タグ**
| 属性 | 説明 |
| ---------- | ------------------------------------------------------------- |
| withSource | ソースコードをデフォルトで表示させるかどうか(open/closed)。 |
**`` タグ**
| 属性 | 説明 |
| ---- | -------------------------------------------- |
| name | サイドメニューに表示されるコンポーネント名。 |
このタグ内に、表示させたいコンポーネントを追加する。
**参考資料**
[Storybook Docs - インストール方法](https://github.com/storybookjs/storybook/blob/next/addons/docs/README.md#installation)
[Storybook Docs MDX](https://github.com/storybookjs/storybook/blob/next/addons/docs/docs/mdx.md)
[Storybook の新しいアドオン addon-docs がいい感じ](https://qiita.com/takeyuichi/items/d21cb0a884e5aaac3a17)
#### Storybook Deployer を使って GitHub Pages へ Storybook をデプロイする
以下のコマンドを実行して、Storybook Deployer をインストールする。
```bash
yarn add -D @storybook/storybook-deployer
```
`package.json` ファイルに以下のコマンドを追加する。
```json
{
"scripts": {
"deploy-storybook": "storybook-to-ghpages"
}
}
```
以下のコマンドを実行して、GitHub Pages へ Storybook をデプロイする。
```bash
yarn deploy-storybook
```
GitHub Settings の GitHub Pages の項目で、`gh-pages` ブランチを使って、静的 HTML ファイルをデプロイする設定になっていることを確認。
**参考資料**
[Storybook Deployer - インストール方法](https://github.com/storybookjs/storybook-deployer)
### Docker 上で開発環境をセットアップする
`docker/node/Dockerfile` を用意する。
```docker
FROM node:12.16.1-alpine3.11
WORKDIR /usr/src/app
```
`docker-compose.yml` ファイルを用意する。
```docker
version: '3'
services:
web:
build:
context: ./docker/node
dockerfile: Dockerfile
volumes:
- ./:/usr/src/app
command: sh -c "yarn start"
ports:
- '3000:3000'
```
`package.json` ファイルに以下を追加する。
```json
{
"scripts": {
"docker:build": "docker-compose build",
"docker:install": "docker-compose run --rm web sh -c 'yarn install'",
"docker:run": "docker-compose up",
"docker:stop": "docker-compose down"
}
}
```
`yarn docker:build` で Docker コンテナをビルドする。
`yarn docker:install` で Docker コンテナ内で`yarn install` を実行する(少なくとも、Docker for MacOS では遅くて使い物にならない…。10 数分かかる…)。
`yarn docker:run` で Docker コンテナを起動する。
`yarn docker:stop` で Docker コンテナを停止する。
Docker 環境内で create-react-app
## VSCode の設定について
### 拡張機能の管理
1. VSCode 上で、`command + shift` でコマンドパレットを開く。
2. `Extensions: Configure Recommended Extensions (Workspace Folder)` を実行する。
3. `.vscode/extensions.json` ファイルが作成される。
4. 以下の図のように、拡張機能の略称をリストアップする。

```json
"recommendations": [
"equinusocio.vsc-material-theme",
],
```
VSCode - Workspace recommended extensions
### VSCode の設定の管理
`.vscode/settings.json` 内に設定を書き込むことで、書き込んだ設定を対象のプロジェクトで使うことができる。
ファイルの保存を行ったときに、ESLint の修正が実行されるように設定する。
```json
{
"editor.codeActionsOnSave": { "source.fixAll.eslint": true },
"editor.formatOnSave": true
}
```
## 既存の React プロジェクトのアップデート
### Create React App
Create React App をアップデートする場合、[Change Log](https://github.com/facebook/create-react-app/blob/master/CHANGELOG.md) に書かれているマイグレーション用のコマンドを実行すること。
Updating to New Releases
### Storybook
Storybook をアップデートする場合、[Migration](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) に記載されている内容をもとにマイグレーションを行う。
Storybook v5 以降、TypeScript へのデフォルト対応などを始めとするプリセット周りの整備が進み、更新頻度が高め。
### React などの他のパッケージのアップグレード
以下のコマンドを実行することで、すべての依存パッケージのアップグレードを行うことができる。
```bash
yarn upgrade
```
Upgrade for Minor or Patch Releases
## 参考資料
- [経年劣化に耐える ReactComponent の書き方](https://qiita.com/Takepepe/items/41e3e7a2f612d7eb094a)
- [typescript-fsa に頼らない React × Redux](https://logmi.jp/tech/articles/320496)
- [『りあクト! TypeScript で始めるつらくない React 開発 第 2 版』のサポートページ](https://github.com/oukayuka/ReactBeginnersBook-2.0)
- [material design palette](https://www.materialpalette.com/teal/teal)
## 公式ドキュメント
- [React](https://ja.reactjs.org/)
- [Create React App](https://create-react-app.dev/)
- [Redux](https://redux.js.org/)
- [Redux Toolkit](https://redux-toolkit.js.org/)
- [React Redux](https://react-redux.js.org/)
- [React Router](https://reacttraining.com/react-router/)
- [ESLint](https://eslint.org/)
- [Prettier](https://prettier.io/)
- [Styled-components](https://styled-components.com/)
- [Material UI](https://material-ui.com/)
- [Storybook](https://storybook.js.org/)