Multi Vitamin & Mineral

Multi Vitamin & Mineral です。プログラムに関することを書いております。

React with TypeScript プロジェクトに Material-UI を導入してみる

React with TypeScript のプロジェクトにて、 Material-UI を使い始めるまでの方法について書いていきます。

簡単な管理画面(ダッシュボード)を作成して完了としたいのですが、当該記事では Material-UI を利用する環境を用意して終えることにします。利用方法については次の記事に任せる構成にします。

シリーズ一覧

  1. React + TypeScript の環境構築
  2. Material-UI の導入
  3. Material-UI のデザインをカスタマイズ
  4. Material-UI のレイアウトを作成

前提事項

事前準備

以下の記事で書きました、 React を TypeScript で利用する環境を作成済であることを前提とします。

multimineral-tech.com

この記事中の内容は、上記作業を終えた上で行っているモノと読んでください。

バージョン情報

今回の記事は以下のバージョンで行ったことを前提としています。バージョン違いで記事の内容と異なる場合がありますので、その点はご了承ください。

主要なライブラリは以下になります。

ライブラリ バージョン
material-ui ^4.11.2
react ^17.0.1
react-router-dom ^5.2.0
typescript ^4.1.3

参考までに、今回の記事に関係ないライブラリも含めた package.json も掲載しておきます。

// package.json
{
  // 中略
  "dependencies": {
    "@material-ui/core": "^4.11.2",
    "@material-ui/icons": "^4.11.2",
    "@testing-library/jest-dom": "^5.11.6",
    "@testing-library/react": "^11.2.2",
    "@testing-library/user-event": "^12.6.0",
    "@types/jest": "^26.0.19",
    "@types/node": "^12.19.11",
    "@types/react": "^16.14.2",
    "@types/react-dom": "^16.9.10",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "4.0.1",
    "typescript": "^4.1.3",
    "web-vitals": "^0.2.4"
  },
  // 中略
  "devDependencies": {
    "@types/react-router-dom": "^5.1.7",
    "@typescript-eslint/eslint-plugin": "^4.11.0",
    "@typescript-eslint/parser": "^4.11.0",
    "eslint": "^7.16.0",
    "eslint-config-airbnb": "^18.2.1",
    "eslint-config-prettier": "^7.1.0",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-jsx-a11y": "^6.4.1",
    "eslint-plugin-prettier": "^3.3.0",
    "eslint-plugin-react": "^7.21.5",
    "eslint-plugin-react-hooks": "^4.2.0",
    "prettier": "^2.2.1"
  }
}

Material-UI のインストール

Material-UI のインストールです。

material-ui.com

こちらの手順にある内容をまず実施しておきます。

npm でインストールするのは以下の2つ。

$ npm install --save @material-ui/core
$ npm install --save @material-ui/icons

フォントは HTML ファイルに追加することにします。

<!-- ./public/index.html -->
<!-- 前略 -->
  <head>
    <!-- 中略 -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
    <!-- 中略 -->
  </head>
<!-- 後略 -->

例えば、 Material-UI の Date Picker のような部品を利用したいとなったなら @material-ui/pickers もインストールする必要があります。こういった部品(Button や DatePicker など)に関しては公式サイトに解説のページがあります。そしてそのページ内で追加でインストールすべきモノが説明されています。ですので、今後部品が必要になった都度インストールすればOKかと思います。

ということで、今回は一旦ここまで。あとは必要に応じて。

簡単な画面の作成(Material-UI 利用前)

続いて、簡単な画面を作成することにします。

今回はめっちゃ簡素なダッシュボードのようなものを作ります。「Top ページ」と「About ページ」いう2ページのみの構成とし、互いに行き来ができるようにします。ダッシュボードという名の「テンプレート」を用意し、「Top ページ」と「About ページ」は、この「テンプレート」の中に表示されるようにします。

ディレクトリ・ファイル構成

「テンプレート」と「Top ページ」と「About ページ」に対応したファイルを追加します。ディレクトリ構成は好みが分かれるところでしょうが、取り敢えず今回は下記のような形にします。

./src
    │  App.css
    │  App.test.tsx
    │  App.tsx
    │  index.css
    │  index.tsx
    │  logo.svg
    │  react-app-env.d.ts
    │  reportWebVitals.ts
    │  setupTests.ts
    │
    ├─assets     ### <-ADD 画像などの静的ファイルを入れる。
    ├─components ### <-ADD 部品置き場。<HogeButton /> みたいな独自タグを作っていく。
    ├─consts     ### <-ADD 定数置き場。
    ├─pages      ### <-ADD 画面置き場。
    │      AboutPage.tsx         ### <-ADD About ページ
    │      TopPage.tsx           ### <-ADD Top ページ
    │
    └─templates  ### <-ADD テンプレート置き場。
           DashboardTemplate.tsx ### <-ADD ダッシュボードのテンプレート

ということで、ディレクトリとファイルを追加します。

$ mkdir src/assets
$ mkdir src/components
$ mkdir src/consts
$ mkdir src/pages
$ mkdir src/templates
$ touch src/pages AboutPage.tsx
$ touch src/pages TopPage.tsx
$ touch src/templates DashboardTemplate.tsx

ページの作成

「Top ページ」と「About ページ」を作ります。最初は超単純にしておきましょう。

// /src/pages/TopPage.tsx
import React from 'react';

const TopPage: React.FC = () => {
  return <>トップページの内容</>;
};

export default TopPage;
// /src/pages/AboutPage.tsx
import React from 'react';

const AboutPage: React.FC = () => {
  return <>アバウトページの内容</>;
};

export default AboutPage;

ルーティング

次に、作成したページを表示させるためのルーティングを作っていきます。

ページを遷移させるルーティングの機能には react-router-dom を利用します。 TypeScript を使っていますので、型もインストールします。型は開発環境でしか使いませんので --save-dev オプションにしています。

$ npm install --save react-router-dom
$ npm install --save-dev @types/react-router-dom

src/App.tsx がアプリケーションの入り口になります。ここにルーティングを書くことになります。以下のように編集します。

// src/App.tsx
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

import AboutPage from './pages/AboutPage';
import TopPage from './pages/TopPage';

const App: React.FC = () => {
  return (
    <Router>
      <Switch>
        <Route path="/" component={TopPage} exact />
        <Route path="/about" component={AboutPage} exact />
      </Switch>
    </Router>
  );
};

export default App;

これで、 http://localhost:3000/ は「Top ページ」へ、 http://localhost:3000/about は「About ページ」に遷移します。

ページ遷移の <a> タグの設置には <Link> を使います。<Link to="/about">About</Link> のように書くと、 AboutPage へのリンクが設置されます。

今回はルーティングが本題ではありませんので、解説は省きます。ただ、今回は公式サイトの「Quick Start」に書かれているレベルのことしかしていませんので、知りたい方は下記を見ていただければいいかな、と。

reactrouter.com

テンプレートの作成

次に、「テンプレート」を作ってみましょう。まずは簡単な構成にします。

// /src/templates/DashboardTemplate.tsx
import React from 'react';

export interface DashboardTemplateProps {
  children: React.ReactNode;
  title: string;
}

const DashboardTemplate: React.FC<DashboardTemplateProps> = ({
  children,
  title,
}) => {
  return (
    <div>
      <h1>{title}</h1>
      <div>{children}</div>
    </div>
  );
};

export default DashboardTemplate;

呼び出し元には title という属性を指定してもらいます。これを h1 で表示します。

呼び出し元の内容は、 children になります。ここは取り敢えず div で囲って表示します。( <> でもいいのかもしれません。ですが、確実にブロックレベルになるように div っていた方がトラブらないかな? と思ってこうしてます。)

「呼び出し元ではどう使うんだ?」というのが分からないとピンと来ないとは思いますので、この辺りは後述します。

titlechildrenDashboardTemplate のプロパティ(props)となりますので、 DashboardTemplateProps という interface を作りました。これを DashboardTemplate: React.FC<DashboardTemplateProps> のように型指定します。

テンプレートの利用

それでは作成した「テンプレート」を「Top ページ」と「About ページ」で利用してみましょう。

// /src/pages/TopPage.tsx
import React from 'react';

import DashboardTemplate from '../templates/DashboardTemplate'; // <=ADD

const TopPage: React.FC = () => {
  // DashboardTemplate で囲うよう変更
  return (
    <DashboardTemplate title="トップページのタイトル">
      <>トップページの内容</>
    </DashboardTemplate>
  );
};

export default TopPage;
// /src/pages/AboutPage.tsx
import React from 'react';

import DashboardTemplate from '../templates/DashboardTemplate'; // <=ADD

const AboutPage: React.FC = () => {
  // DashboardTemplate で囲うよう変更
  return (
    <DashboardTemplate title="アバウトページのタイトル">
      <>アバウトページの内容</>
    </DashboardTemplate>
  );
};

export default AboutPage;

DashboardTemplate タグで囲うように変更しました。

DashboardTemplate タグには title="トップページのタイトル" という属性を記述しています。これが <h1>{title}</h1>{title} として使われます。

DashboardTemplate タグの中身の <>アバウトページの内容</><div>{children}</div>{children} として使われます。

結果として、以下のような HTML として出力されます。( <>...</> は React の便宜上書いている意味無しタグなので、HTML出力時には無視されます。)

<div>
  <h1>トップページのタイトル</h1>
  <div>トップページの内容</div>
</div>

ということで、 Chrome に表示されたイメージと、出力されたHTMLは以下の画像のようになりました。

Material-UI 利用前の画面
Material-UI 利用前の画面

Material-UI の簡単な利用

ここまでで Material-UI を利用するための土台ができあがりました。次は「テンプレート」で Material-UI のコンポーネントを利用してみたいと思います。

今回は「テンプレート」の変更を行います。画面上部のヘッダーとして AppBar を追加し、そこに2つのページに遷移するリンクを設置します。

ヘッダーの追加

/src/templates/DashboardTemplate.tsx を変更します。内容の全貌は以下の通りです。

// /src/templates/DashboardTemplate.tsx
import React from 'react';
// 以下の import を追加
import { Link } from 'react-router-dom';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Container from '@material-ui/core/Container';

export interface DashboardTemplateProps {
  children: React.ReactNode;
  title: string;
}

const DashboardTemplate: React.FC<DashboardTemplateProps> = ({
  children,
  title,
}) => {
  return (
    <div>
      {/* (1) <AppBar>...</AppBar> を追加 */}
      <AppBar>
        <Toolbar>
          <nav>
            <Link to="/">Top</Link>
            <Link to="/about">About</Link>
          </nav>
        </Toolbar>
      </AppBar>
      {/* (2) <div> を <Container> に変更 */}
      <Container>
        <h1>{title}</h1>
        <div>{children}</div>
      </Container>
    </div>
  );
};

export default DashboardTemplate;

(1) AppBar の追加と Container への変更

{/* (1) <AppBar>...</AppBar> を追加 */} にある AppBar の追加についてです。

      {/* (1) <AppBar>...</AppBar> を追加 */}
      <AppBar>
        <Toolbar>
          <nav>
            <Link to="/">Top</Link>
            <Link to="/about">About</Link>
          </nav>
        </Toolbar>
      </AppBar>

AppBar はヘッダーを作るモノ、 Toolbar はその中で要素を横に並べるモノになります。組み合わせて利用します。(※1)

ナビゲーションのリンクなので一応 <nav> を書いて、その中に <Link> タグでリンク(<a> として出力される)を配置しました。

AppBar についての Material-UI の公式サイトは下記になります。

material-ui.com

※1. AppBarToolbar の違いはなんじゃろと思ってましたが、同じ疑問が下記にて解消されていました。

stackoverflow.com

(2) Container への変更

続いて、 {/* (2) <div> を <Container> に変更 */} にある Container の利用についてです。

      {/* (2) <div> を <Container> に変更 */}
      <Container>
        <h1>{title}</h1>
        <div>{children}</div>
      </Container>

これは divContainer に変更しただけです。 Container は中央寄せされる div というくらいに思ってください。

これで以下のように表示されます。

Material-UI 利用後の画面
Material-UI 利用後の画面

Top と About のリンクで画面がカチカチ切り替わります。 <h1> の部分が隠れちゃったのと、リンク部分がちょっと窮屈なので、その点の改善を少しやっていきたいと思います。

が! 流石に長くなりそうなので次の記事にて話を続けます。