Multi Vitamin & Mineral

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

貼り付けた画像に枠と影を追加(はてなブログ改造計画)

記事中に貼り付けた画像に枠と影をつけようかな、と思ったので、今回はそう改造する方法です。

変更方法

記事中に貼り付けた画像を対象として枠と影をつけます。

変更手順

管理画面から以下のようにたどります。

デザイン > レンチマーク > デザインCSS

ここに追記をします。

追加CSS

.entry-content img {
  border: 1px #000 solid;
  box-shadow: 5px 5px 2px #ccc;
}

変更結果

PNG透過なし画像
PNG透過なし画像

PNG透過あり画像
PNG透過あり画像

透過の有無に関わらず同じような表示になります。

説明

以降、設定した CSS の説明です。

指定箇所についてです。

.entry-content img {

.entry-content が記事の内容。その配下の img タグとしました。

ここは意外と悩ましいところでした。はてなブログの仕様が変わったのか、時期によって生成されているタグが違いましたね。。。もっと絞り込むクラスを入れても良いのかも知れませんが、この程度に留めました。

次に装飾の内容です。

  border: 1px #000 solid;
  box-shadow: 5px 5px 2px #ccc;
  • border: 1px #000 solid : 1px の黒の実線。
  • box-shadow: 5px 5px 2px #ccc : 「右に5px 下に5px 2pxのぼかし グレー( #ccc )」の影。

影は box-shadow 属性が有名ですが、最近は filter 属性に drop-shadow を当てるのが流行りでしょうか。

  /* (注意)うちのブログはこうなっていません。 */
  filter: drop-shadow(5px 5px 2px #ccc);

こうすると、透過 PNG や SVG などで四角以外の画像でも上手く影を付けてくれるのが特徴です。(枠がついていてヤヤコシイですが、枠の中の表示だけ見てください。)この場合は border を付けない方が良いと思います。

drop-shadow の場合
drop-shadow の場合

うちのブログでは、枠を付けたいので box-shadow を使うことにしました。

drop-shadow についてはこちらに。

developer.mozilla.org

尚、利用できるブラウザを調べるのには CanIUse を使うと良いでしょう。

caniuse.com

強調文字に蛍光マーカーを引く(はてなブログ改造計画)

強調文字が太字だけだと目立ちっぷりが足りないので、蛍光マーカーを引いたような見た目に改造する方法です。

変更方法

エディタの「B」ボタンで強調した部分の装飾です。 Markdown で書くと **強調** の部分に当たります。

変更手順

管理画面から以下のようにたどります。

デザイン > レンチマーク > デザインCSS

ここに追記をします。

追加CSS

.entry-content strong {
  background: linear-gradient(transparent 30%, #a6d9fe 30%);
  font-weight: bold;
}

変更結果

変更結果
変更結果

説明

以降、設定した CSS の説明です。

指定箇所についてです。

.entry-content strong {

.entry-content が記事の内容。その配下の strong タグとしました。

次に装飾の内容。

  background: linear-gradient(transparent 30%, #a6d9fe 30%);
  font-weight: bold;

ここは linear-gradient という属性が分かりにくいのでその点を詳しく書きました。

linear-gradient 属性は、本来はグラデーションです。ですが、 開始色終了色あえて同じ割合(パーセント)を指定すると、その指定割合(パーセント)の前後をベタ塗りできるというテクニックがありますので、これを使ってます。

  • linear-gradient : グラデーションで徐々に色を変化させます。
    • transparent 30% : 開始色。上から 30% の高さまで透明色。それ以降はグラデーションという意味。
    • #a6d9fe 30% : 終了色。上から 30% の高さから青( `#a6d9fe )色。それ以前はグラデーションという意味。
  • font-weight:
    • bold : 太字

linear-gradient(方向, 開始色, 終了色) になります。 方向 を書かないとデフォルトの「左から右」が適用されます。 linear-gradient(transparent, #a6d9fe) であれば、透明から青色に徐々に変わります。補助として「半角スペース + 割合(パーセント)」を入れると、 開始色 の場合はそこまでベタ塗りでそれ以降がグラデーション、 終了色 の場合はそこからベタ塗りでそれ以前がグラデーション。

developer.mozilla.org

色を変えたり、割合を変えたり、他の属性を追加したりしてカスタマイズして取り入れると良いかと思います。

当サイトのURLが変更になりました。はてなブログの独自ドメイン化の話

URL が変わりました

当ブログのURLが変更になりました。

旧)https://hiranoon.hatenablog.com

新)https://multimineral-tech.com/

旧 URL は、当面は新URLにリダイレクトされます。しばらくはどちらからもアクセスできますが、しばらく後は新 URL のみとなります。

今後とも、何卒よろしくお願いいたします。

(余談)はてなブログの設定変更色々

んま、そゆことで、この後は余談です。

設定を色々いじってここまできました。今回の作業として実施したのは以下です。

  1. お名前.com でドメインを取得した。
  2. 「はてなブログPro」に加入した。
  3. お名前.comで DNS 関連機能の設定を追加した。
  4. はてなブログの設定画面に取得した独自ドメインを登録した。
  5. Google Analytics との連携を実施した。
  6. Google Search Console との連携を実施した。

はてな公式で結構詳しく説明してくれています。

help.hatenablog.com

blog.hatenablog.com

公式だけで十分、ですが、お名前.com に慣れていない人は他のサイトも参考にする方が良いかもです。

Google 周りは Analytic と Search Console だけ設定しました。

Google Analytics はトラッキングIDと言われる「UA-XXXX」というコードをはてなブログに設定すればOK。が、そのトラッキングIDが Google Analytics のサイトのどこに出てくるか分からんかったですね。。。以下のサイトさんが参考になりました。

blog.muraweb.net

Google Search Console は旧URLから新URLへのアドレス変更ツールが用意されています。が、できませんでした。。。

Google Search Console
Google Search Console

はてなブログの独自ドメイン移行って、勝手に「302」リダイレクトをしてくれるみたいなんです。が、「301」リダイレクトはできないようです。詰みじゃん。

ま、いいか。

React with TypeScript プロジェクトの Material-UI でレイアウトを作成する

前回は Material-UI のデザインの変更を行いました。今回はその続きとして Theme をカスタマイズする元となるレイアウト作成を行います。ソースコードは前回の記事を踏まえていますのでその点ご了承ください。(といって、踏まえないでも読めてしまうように工夫はしておきます。)

それぞれの章は以下の流れで書いています。

  1. 全体のソースの掲載
  2. 部分部分の説明

「全体のソース」は長くなりがちですが、さっと流して読み進めていただければと思います。

シリーズ一覧

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

初期状態

前回やったこと、今回やること

前回は makeStyles を利用して、独自 CSS を追加しました。また、勝手にデフォルトの Theme が使わることになるにも触れました。

multimineral-tech.com

今回は、管理画面(ダッシュボード)のレイアウトを構築します。 Theme のカスタマイズはこの次に。(カスタマイズする元画面を作るのを先にしました。)

変更前のソース

前回まで作った DashboardTemplate.tsx から説明用の余計なモノを削り、シンプルな形に変更しておきました。

// /src/templates/DashboardTemplate.tsx
import React from 'react';
import { Link } from 'react-router-dom';
import { makeStyles, Theme } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Container from '@material-ui/core/Container';

const useStyles = makeStyles((theme: Theme) => ({
  link: {
    margin: theme.spacing(1, 1.5),
  },
}));

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

const DashboardTemplate: React.FC<DashboardTemplateProps> = ({
  children,
  title,
}) => {
  const classes = useStyles();
  return (
    <div>
      <AppBar>
        <Toolbar>
          <nav>
            <Link to="/" className={classes.link}>
              Top
            </Link>
            <Link to="/about" className={classes.link}>
              About
            </Link>
          </nav>
        </Toolbar>
      </AppBar>
      <Container>
        <h1>{title}</h1>
        <div>{children}</div>
      </Container>
    </div>
  );
};

export default DashboardTemplate;

見た目は以下です。 <Container> 内の <h1> が、 <AppBar> の下に潜って見えなくなっちゃってますが、こちらは追々修正します。

変更前
変更前

サイドメニューの追加

左サイドにメニューを用意し、 Top と About のリンクをこちらに移動させます。

使うのは <Drawer> 。これが左サイドメニューになります。(実際には上下左右どこにでも配置可能です。)

そして、その中に縦にリンクを配置するのに <List> とその仲間( <ListItem> , <ListItemIcon> , <ListItemText> )を使います。

この時点で <Link> タグを削っています。一時的にコメントアウトしています。 Top と About の切り替えはまた後で追加します。まずはデザイン面をやっつけます。

// /src/templates/DashboardTemplate.tsx
import React from 'react';
// import { Link } from 'react-router-dom';
// import { makeStyles, Theme } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Container from '@material-ui/core/Container';
import Drawer from '@material-ui/core/Drawer';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import HomeIcon from '@material-ui/icons/Home';
import InfoIcon from '@material-ui/icons/Info';

// const useStyles = makeStyles((theme: Theme) => ({
//   link: {
//     margin: theme.spacing(1, 1.5),
//   },
// }));

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

const DashboardTemplate: React.FC<DashboardTemplateProps> = ({
  children,
  title,
}) => {
  // const classes = useStyles();
  return (
    <div>
      <AppBar>
        <Toolbar>
          {/* ココにあった <nav> を削除 */}
        </Toolbar>
      </AppBar>
      {/* ココから追加 */}
      <Drawer variant="permanent">
        <List>
          <ListItem>
            <ListItemIcon>
              <HomeIcon />
            </ListItemIcon>
            <ListItemText primary="Home" />
          </ListItem>
          <ListItem>
            <ListItemIcon>
              <InfoIcon />
            </ListItemIcon>
            <ListItemText primary="About" secondary="hogehoge" />
          </ListItem>
        </List>
      </Drawer>
      {/* ココまで追加 */}
      <Container>
        <h1>{title}</h1>
        <div>{children}</div>
      </Container>
    </div>
  );
};

export default DashboardTemplate;

以下のような表示になりました。

コンポーネントの追加
コンポーネントの追加

Drawer の追加

上下左右に配置できる領域です。本来は、引き出しのようにスライドして表示・非表示が切り替わります。(下記リンク先の「Temporary drawer」の項の「LEFT RIGHT TOP BOTTOM」の4つのボタンを押すとどんなものか分かると思います。)

material-ui.com

表示しっ放しにすることも可能です。すると単なるサイドバーになります。

      <Drawer variant="permanent">
        {/* 中略 */}
      </Drawer>

<Drawer> には variant という属性があります。属性値は以下のようになってます。(公式の説明は私は一読では分からんかったので、私に分かるように書いてみた。。。)

  • permanent : ずっと表示されたまま。
  • persistent : 表示・非表示の切り替えができる。表示と非表示の機能(ボタン等)はそれぞれ自作する必要がある。
  • temporary : 表示・非表示の切り替えができる。基本は非表示なのが特徴。表示機能(ボタン等)は自作が必要だが、 <Drawer> の領域外をクリックすると閉じるので、閉じる機能は作らなくてもOK。

persistenttemporary の違いが分かりにくいんですよね。 persistent は一度表示したら表示しっぱなし(永続的)、 temporary は別のところをクリックすると引っ込む(一時的)ということです。

今回は permanent なので、非表示になることがありません。

material-ui.com

List の追加

縦方向に並べる索引が Lists です。

material-ui.com

        <List>
          <ListItem>
            <ListItemIcon>
              <HomeIcon />
            </ListItemIcon>
            <ListItemText primary="Home" />
          </ListItem>
          <ListItem>
            <ListItemIcon>
              <InfoIcon />
            </ListItemIcon>
            <ListItemText primary="About" secondary="hogehoge" />
          </ListItem>
        </List>

典型的な構造は以下のようになります。(モチロン他のパターンもある。)

  • <List>
    • <ListItem>
      • <ListItemIcon>
      • <ListItemText>
    • <ListItem>
      • ...
    • <ListItem>
      • ...
    • ...

HTML でいうと、 <List><ul><ListItem><li> にあたります。実際にそう変換されてブラウザに出力されます。

その <li> にあたる <ListItem> の中には、 <ListItemIcon><ListItemText> といった専用の部品を入れる前提で考えると良さそうです。先のリンク先には他の部品も使った例がありますので、好みで利用すると良いです。

ListItemText

<ListItemText> の属性に primary="Home" とあります。 <li> のメインテキストを指します。 secondary<li> のサブテキストになります。2種類の文字を設定できる仕掛けになってます。

例えば、 <ListItemText primary="About" secondary="hogehoge" /> とすると以下のような見た目になります。(あくまで例です。今回は secondary は使いません。)

ListItemText の設定
ListItemText の設定

ちなみに、私は最初に <ListItemText primary="hoge"> のように primary だけ使われているサンプルコードを見て意味が理解できませんでした。メインとサブのテキストが設定できる仕組みになってて、そのメインだけ設定しているという意味だったんですね。詳しくは以下にあります。

material-ui.com

ListItemIcon

Material-UI には Material Icons というものがあります。

material-ui.com

任意で好きなアイコンが使えます。使いたいアイコンをクリックすると、使うべきコードも表示されます。

Material Icons の選択
Material Icons の選択

<ListItemIcon> はコンテントとして Material Icons が使われる想定になっています。 <ListItemIcon><HomeIcon /></ListItemIcon> のように書くだけなので、これは直感的で分かりやすいですよね。

Drawer の開閉機能の設置

続いて <Drawer> の開閉機能を設置します。

開く機能を持ったボタン(いわゆるハンバーガーメニューというヤツ)を <Toolbar> に付けるようにします。 <IconButton> というボタンです。

閉じる機能は <Drawer> 自身のモノを使います。 variant="temporary" にして領域外をクリックしたら閉じるというヤツです。

開閉の状態は React Hooks で管理します。 drawerOpen という変数( state )を用意しています。

import React from 'react';
// import { Link } from 'react-router-dom';
// import { makeStyles, Theme } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Container from '@material-ui/core/Container';
import Drawer from '@material-ui/core/Drawer';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import HomeIcon from '@material-ui/icons/Home';
import InfoIcon from '@material-ui/icons/Info';
import MenuIcon from '@material-ui/icons/Menu';
import IconButton from '@material-ui/core/IconButton'; // 追加

// const useStyles = makeStyles((theme: Theme) => ({
//   link: {
//     margin: theme.spacing(1, 1.5),
//   },
// }));

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

const DashboardTemplate: React.FC<DashboardTemplateProps> = ({
  children,
  title,
}) => {
  // const classes = useStyles();
  // 追加: Drawer の開閉状態(フックを利用)
  const [drawerOpen, setDrawerOpen] = React.useState(false);
  // 追加: Drawer の開閉
  const handleDrawerToggle = () => {
    setDrawerOpen(!drawerOpen); // Drawer の開閉状態を反転
  };
  return (
    <div>
      <AppBar>
        <Toolbar>
          {/* 追加 */}
          <IconButton color="inherit" onClick={handleDrawerToggle}>
            <MenuIcon />
          </IconButton>
        </Toolbar>
      </AppBar>
      {/* 属性を変更 */}
      <Drawer
        variant="temporary"
        open={drawerOpen}
        onClose={handleDrawerToggle}
      >
        <List>
          <ListItem>
            <ListItemIcon>
              <HomeIcon />
            </ListItemIcon>
            <ListItemText primary="Home" />
          </ListItem>
          <ListItem>
            <ListItemIcon>
              <InfoIcon />
            </ListItemIcon>
            <ListItemText primary="About" />
          </ListItem>
        </List>
      </Drawer>
      <Container>
        <h1>{title}</h1>
        <div>{children}</div>
      </Container>
    </div>
  );
};

export default DashboardTemplate;

以下のような表示になりました。

Drawer の開閉機能の設置
Drawer の開閉機能の設置

React Hooks による開閉状態の保持

以下のコードを追加しました。 drawerOpen という変数(以降 state と呼ぶ)に開閉状態を保持します。 handleDrawerToggledrawerOpentrue/false を反転させています。

  // 追加: Drawer の開閉状態(フックを利用)
  const [drawerOpen, setDrawerOpen] = React.useState(false);
  // 追加: Drawer の開閉
  const handleDrawerToggle = () => {
    setDrawerOpen(!drawerOpen); // Drawer の開閉状態を反転
  };

以下、 React Hooks を簡単に説明しておきます。

React.useState(状態の初期値); という関数は、「状態(の変数)」と「状態をセットする関数」を配列で返却してくれます。これをフック (Hook) と呼びます。 const [状態, 状態をセットする関数] = React.useState(状態の初期値); のように書いて使います。

「状態」である drawerOpen は、最初は false(状態の初期値) になります。また false をセットしているので drawerOpen の型は boolean であると決まりました。

「状態をセットする関数」である setDrawerOpen は、 setDrawerOpen(true)setDrawerOpen(false) のようにして使います。これで drawerOpen の値を変更します。( drawerOpen = true; のような書き方はできません!) setDrawerOpen(!drawerOpen);drawerOpen の逆(! は boolean の反転)をセットしていますから true/false が反転することを意味します。

この反転を行う処理を handleDrawerToggle という関数で用意しています。

React Hooks は公式サイトでも分かりやすく説明されています。

ja.reactjs.org

Drawer の属性を変更

<Drawer> の属性を変更しました。 <Drawer> が開閉する temporary に変更し、閉じた時の state の変更処理を加えます。

      {/* 属性を変更 */}
      <Drawer
        variant="temporary"
        open={drawerOpen}
        onClose={handleDrawerToggle}
      >
  • variant : temporary にして表示・非表示の切り替えができるようにします。領域外をクリックすると閉じます。
  • open : true で開いて false で閉じます。
  • onClose : 閉じた時に呼び出される関数です。

temporary にたので領域外をクリックすると閉じます。このときに onClose で指定した関数を呼び出します。

drawerOpentrue であった場合に領域外をクリックすると、以下のような動きになります。

  1. drawerOpentrue
  2. open 属性が true(=drawerOpen) であるため <Drawer> は開いている。
  3. 領域外をクリックする。
  4. onClose 属性で定義した handleDrawerToggle 関数が呼び出される。
  5. handleDrawerToggle 関数は drawerOpen を反転させるので drawerOpenfalse になる。
  6. open 属性が false になるので <Drawer> が閉じる。

IconButton の追加

<Toolbar><IconButton> を追加しました。ボタンのアイコンは <MenuIcon /> を使います。

      <AppBar>
        <Toolbar>
          {/* 追加 */}
          <IconButton color="inherit" onClick={handleDrawerToggle}>
            <MenuIcon />
          </IconButton>
        </Toolbar>
      </AppBar>

onClick={handleDrawerToggle} を見て分かると思いますが、クリックすると handleDrawerToggle 関数が起動して drawerOpen が反転します。その結果 <Drawer> が開くという仕掛けになります。

次は Drawer をレスポンシブにしたいと思います。

React with TypeScript プロジェクトの Material-UI でデザインをカスタマイズする

前回は Material-UI のコンポーネントを利用するところまでやりました。今回はその続きになります。ソースコードも前回の記事を踏まえていますのでその点ご了承ください。(といって、踏まえないでも読めてしまうとは思いますが。)

multimineral-tech.com

さて、今回あ Theme をいじったり Style をいじったりします。その方法は色々あるのですが、今回はその一例となります。なるべく今現在の主流と思われるやり方、といいますか、公式で使われている方法を採用しています。

シリーズ一覧

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

Theme という概念

説明をするその前に、 Material-UI には Theme という概念があります。(「Theming」と呼ばれているようです。)

よくあるテーマってやつだね、となんとなく察するかも知れませんが、そうです。 Material-UI のテーマとはどんなんであるのか、今回の前提知識として少し説明をしておきます。

  • Theme により色や配置などのデザインが決まる。
  • 何もしなくてもデフォルトの Theme が適用される。
  • 色は Palette に定義されている。
  • Palette には Primary , Error , Warning などのキーワードがあり、それぞれに色が定義されている。
  • 色を指定するときは、このキーワードを利用する。
  • デザインの変更は、基本はこの Theme や Palette を変更する

混乱しやすい部分は、何もしなくてもデフォルトの Theme が適用される点かなと思います。なので、 Theme を使ってなくても Theme を意識する必要があるので注意です。(本格的に利用するなら Theme をいじらないことってないんでしょうけど、利用しはじめの時はピンとこないかな、と思います。)

Theme と Palette については以上ですが、少しだけ説明を加えておきます。

Theme について

前回の記事では、 AppBar というコンポーネントを使ったら何も設定していないのに「青色」になってました。

デザインをカスタマイズ前
デザインをカスタマイズ前

Theme では「色や配置」などのデザインが定義されています。 AppBar のような Material-UI のコンポーネントには、 Theme で定義された色やデザインになる仕組みになっています。 AppBar が「青色」になっていたのは、 Theme の色が適用されていた訳です。

Theme はカスタマイズが可能です。ですが、特に何もしなかった場合はデフォルトの Theme が適用されます。先程の「青色」はデフォルトの色だった訳です。

公式サイトの Theme はこちらです。

material-ui.com

Palette について

では、 Theme の色は何になっているのでしょうか? これは Palette というもので定義されています。 Primary , Secondary , Error , Warning , Info , Success という区分けでそれぞれ色が定義されています。

AppBar に色を指定しないと Primary になります。 Primary はデフォルトで青色です。こういう理屈で青色のバーが上部に表示されていました。

公式サイトの Palette はこちらです。

material-ui.com

makeStyles で Style を追加する

さて、CSS でデザインをしていきましょう。

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

// /src/templates/DashboardTemplate.tsx
import React from 'react';
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';
// (2) 追加
import {
  makeStyles,
  Theme,
} from '@material-ui/core/styles';

// (2) useStyles を追加
const useStyles = makeStyles(
  (theme: Theme) => ({
    toolbar: {
      fontWeight: 'bold',
    },
    link: {
      margin: theme.spacing(1, 1.5),
    },
  })
);

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

const DashboardTemplate: React.FC<DashboardTemplateProps> = ({
  children,
  title,
}) => {
  // (2) classes を追加
  const classes = useStyles();

  return (
    <div>
      {/* (1) それぞれに属性を追加 */}
      <AppBar position="static" color="default">
        <Toolbar className={classes.toolbar}>
          <nav>
            <Link to="/" className={classes.link}>
              Top
            </Link>
            <Link to="/about" className={classes.link}>
              About
            </Link>
          </nav>
        </Toolbar>
      </AppBar>
      <Container>
        <h1>{title}</h1>
        <div>{children}</div>
      </Container>
    </div>
  );
};

export default DashboardTemplate;

(1) 属性の追加

AppBar 以下のタグに属性を追加しています。

      {/* (1) それぞれに属性を追加 */}
      <AppBar position="static" color="default">
        <Toolbar className={classes.toolbar}>
          <nav>
            <Link to="/" className={classes.link}>
              Top
            </Link>
            <Link to="/about" className={classes.link}>
              About
            </Link>
          </nav>
        </Toolbar>
      </AppBar>

それぞれの設定内容について説明します。

  • AppBar
    • position="static" : 表示位置設定です。 CSS の position と同じモノが属性として設定できます。デフォルトは fixed ですので static に変更しています。
    • color="default" : 色です。デフォルトは Theme の Primary が設定されます。 default を指定するとグレー表示になります。
  • Toolbar
    • className={classes.toolbar} : 後述する独自 CSS の追加です。
  • Link
    • className={classes.link} : 後述する独自 CSS の追加です。

AppBarpositioncolorAppBar 固有の属性です。公式のサイトにも利用方法が書かれています。

LinkclassName は React の属性で HTML の class に相当します。 Material-UI では属性値の {classes.toolbar} の方が重要で、次項ではコレについて説明します。

(2) 独自 CSS の追加

makeStyles という機能で独自 CSS を追加しています。この部分に関して話をします。

// (2) useStyles を追加
const useStyles = makeStyles(
  (theme: Theme) => ({
    toolbar: {
      fontWeight: 'bold',
    },
    link: {
      margin: theme.spacing(1, 1.5),
    },
  })
);

// ...中略...

const DashboardTemplate: React.FC<DashboardTemplateProps> = ({
  children,
  title,
}) => {
  // (2) classes を追加
  const classes = useStyles();
  // ...中略...
};

const useStyles = makeStyles(() => ({ ... });useStyles という関数を作っています。 makeStyles は引数に関数を取ります。この関数の返却値( ({ ... }) の部分にあたる)に JSON 形式で CSS を記述します。

できた useStyles は React のコンポーネント内で利用できます。 const classes = useStyles(); のようにして受け取り、 <Link to="/" className={classes.link}> のように className 属性に指定ができます。

  1. makeStyles に独自 CSS を記述する。
  2. makeStyles の結果(= useStyles)を React のコンポーネント内で受け取る。
  3. タグの中の className 属性の値で利用する。

独自 CSS の追加と利用

makeStyles(() => ( ... ));... の部分に JSON で書くだけです。属性は CSS のようなハイフンつなぎではなくキャメルケースになることに注意です。以下のように定義します。

const useStyles = makeStyles(
  () => ({
    toolbar: {
      fontWeight: 'bold',
    },
  })
);

利用も難しくはないと思います。

  const classes = useStyles();
  return (
    <Toolbar className={classes.toolbar}>
  );

こんな感じで toolbar で定義した fontWeight: 'bold' が適用されます。

Theme の値を利用した CSS の追加

先程は 'bold' という属性値を追加しました。この属性値の設定に Theme の値を使うことも可能です。例えば、 border の色に Theme の Primary の色を使いたい、みたいなことです。

今回は、リンク文字のスペースに theme.spacing() という関数を使います。

Theme の spacing

Theme には spacing という値が定義されています。余白に利用する基本的な数字です。デフォルトの Theme だと 8px になっているようです。

リンク文字の余白として、 CSS の margin を追加したいと思います。 theme.spacing() という関数を theme.spacing(1, 1.5), のよう使うと以下のような意味になります。

margin: 8px, 12px;

8px * 1, 8px * 1.5 が展開されて、上記のような CSS として扱われます。(ちなみに、 theme.spacing(1, 2, 3, 4), なら 8px, 16px, 24px, 32px; になります。)

theme.spacing() については公式サイトの以下に説明があります。

material-ui.com

theme.spacing を使った CSS の追加と利用

Theme を使うときは makeStyles の引数が必要になります。 theme という引数(型は Theme なので theme: Theme と書いている)を指定すると、現在適用される Theme を利用されることになります。 theme をどこかで定義する必要はありません。

const useStyles = makeStyles(
  (theme: Theme) => ({
    link: {
      margin: theme.spacing(1, 1.5),
    },
  })
);

利用は難しくはないと思います。

  const classes = useStyles();
  return (
    <Link to="/about" className={classes.link}>
      About
    </Link>
  );

こんな感じで link で定義した margin: theme.spacing(1, 1.5), が適用されます。最終的に margin: 8px, 12px; という CSS として出力されます。

対応結果

以上にて、以下のような表示に変わりました。

デザインをカスタマイズ後
デザインをカスタマイズ後

次は Theme を編集する話をしていきたいと思います。

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> の部分が隠れちゃったのと、リンク部分がちょっと窮屈なので、その点の改善を少しやっていきたいと思います。

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

React で TypeScript を使う環境を構築する手順(いつもどおり VSCode に ESLint と Prettier を添えます。)

React の開発環境を作る。 JavaScript ではなく TypeScript を使いたい。という場合の環境構築手順メモです。利用するのは VSCode 。 ESLint と Prettier も使ってコードのチェックやフォーマットを実行してくれるようにしておきました。

  • React
  • TypeScript
  • ESLint
  • Prettier
  • VSCode

以前、下記のような投稿をしました。こちらは React ではなかったのです。今回は下記の React 版になりますね。

multimineral-tech.com

今回は最小の設定、最小の構成を目指して環境を作りました。ここから各人の好みで調整しやすい構成にしたつもりです。参考になれば幸いです。

また、バージョンの違いによってはうまく行かないこともあるでしょう。実行時期により導入されるバージョンが変わってくると思いますので、そのあたりは適宜対応いただきたいです。(別のプロジェクトではうまく行ってたのが今回はエラーが消えなかったり、と、結構苦労しました。。。設定ミスかと思って調べたらバージョン違い(=作成時期の違い)に依るモノだった。。。)現時点でうまく行った例になりますが、その内容を漏らさず記載したつもりです。

環境情報(執筆時点)

執筆時点でのバージョンの情報を掲載しておきます。同一バージョンであれば同じ結果になる、はず。

手順開始前の環境

以下のような環境を前提とします。

種類 バージョン
windows 10 Pro
node v14.15.3
npm 6.14.9
VSCode 1.52.1
VSCode Extension / ESLint v2.1.14
VSCode Extension / Prettier - Code formatter v5.8.0
GitBash (GNU bash) version 4.4.23(1)-release

このあたりのツールの導入は過去記事を参照してもらえるとありがたいです。

multimineral-tech.com

multimineral-tech.com

multimineral-tech.com

手順実施後の結果

前述の環境を前提にして、当記事の作業(つまりこの後の文章にある作業)を行った最終結果を参考までに掲載しておきます。

バージョン情報は ./package.json に記載されますので、そちらの一部を抜粋して掲載します。

// package.json
{
  // 中略
  "dependencies": {
    "@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-scripts": "4.0.1",
    "typescript": "^4.1.3",
    "web-vitals": "^0.2.4"
  },
  // 中略
  "devDependencies": {
    "@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"
  }
}

React with TypeScript の準備

React の環境は create-react-app を使って構築することにします。

create-react-app の実行

npx create-react-app (任意のプロジェクト名) --template typescript で TypeScript を使った React プロジェクトが作成できます。

私の環境では npx create-react-app だけだとうまく動かなかったので、グローバルに create-react-app をインストールしてから実行しました。 create-react-app のインストールはしなくて済むならそれに越したことはないと思いますので、飛ばせるなら飛ばしちゃってください。

### 下記コマンドは最初は実行しないで試してみましょう。
$ npm install -g create-react-app
C:\Program Files (x86)\Nodist\bin\create-react-app -> C:\Program Files (x86)\Nodist\bin\node_modules\create-react-app\index.js
+ create-react-app@4.0.1
added 67 packages from 25 contributors in 5.529s
$ create-react-app --version
4.0.1
### 最初は下記コマンドからスタートしましょう。
### もしエラーになるようなら「npm install -g create-react-app」を実行してから
### 再び下記コマンドを試してみましょう。
$ npx create-react-app sample-project --template typescript

任意ではありますが、以下のコマンドで実行を確認してもよいでしょう。ブラウザが立ち上がると思います。

$ cd sample-project
$ npm start

補足 : create-react-app のオプション

ちょっと古い記事だと npx create-react-app (任意のプロジェクト名) --typescript というオプションを使っている説明があります。今は公式サイトにて --template typescript を使うように指定されているので、こちらを使いましょう。

create-react-app.dev

ちなみに、公式サイトでは npm install -g create-react-app は止めとけと書かれています。。。いや、やらずに動くんならやりたくないんですが、まあ、動かないのはしょうがないので私の環境ではやっちゃってます。

あと、「 npm install --save typescript @types/node @types/react @types/react-dom @types/jest というコマンドも実行しとけ」と書かれていますが、特に実行しなくてもこれらは導入されますね。私は実行しませんでしたが、ちゃんと入っているか package.json の確認はしておいて良いかも知れませんね。

ESLint の導入と設定

続いて ESLint を導入し、設定を行いましょう。

VSCode で開く

[ファイル] > [フォルダーを開く] より、作成したプロジェクトのフォルダーを指定して開きます。(今回の例だと「sample-project」フォルダーです。)(エクスプローラで右クリックから「Code で開く」でも良し。)

その後、 ctrl + @ などで、下部にコンソールを開いておくと良いです。

ESLint の導入

ESLint は npx eslint --init で導入できます。プロジェクトの直下にてこのコマンドを入力し、各種設定を選択します。

選択した内容についてはインラインで記載していますが、「airbnb」の選択と「設定ファイルを JavaScript とする」部分の選択は好みでよいと思います。その2つ以外は下記の通りになっちゃうと思います。

$ npx eslint --init
### To check syntax, find problems, and enforce code style を選択
√ How would you like to use ESLint? · style       
### JavaScript modules (import/export) を選択
√ What type of modules does your project use? · esm
### React を選択
√ Which framework does your project use? · react
### Yes を選択
√ Does your project use TypeScript? · No / Yes
### Browser を選択
√ Where does your code run? · browser
### Use a popular style guide を選択
√ How would you like to define a style for your project? · guide
### Airbnb: https://github.com/airbnb/javascript を選択
√ Which style guide do you want to follow? · airbnb      
### JavaScript を選択
√ What format do you want your config file to be in? · JavaScript
...(中略)...
### Yes を選択
√ Would you like to install them now with npm? · No / Yes
...(中略)...
+ eslint-config-airbnb@18.2.1
+ eslint-plugin-jsx-a11y@6.4.1
+ eslint-plugin-react@7.21.5
+ eslint-plugin-react-hooks@4.2.0
+ eslint-plugin-import@2.22.1
+ eslint@7.16.0
+ @typescript-eslint/parser@4.11.0
+ @typescript-eslint/eslint-plugin@4.11.0
added 2 packages from 3 contributors, updated 7 packages and audited 1953 packages in 14.141s
...(中略)...
ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy.

補足 : ESLint 関連プラグインの導入

他の記事によっては以下のコマンドを打つべしってありましたが、2020/12現在では不要みたいです。 npx eslint --init で一緒にインストールされましたね。まあ、やっても問題にはならないとおもいますが。

$ yarn add -D eslint-plugin-react @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-airbnb eslint-plugin-jsx-a11y eslint-plugin-import

ESLint の設定

React, TypeScript の環境にあった内容に変更します。

// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
    jest: true,        // <=ADD
  },
  extends: [
    'plugin:react/recommended',
    'airbnb',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: [
    'react',
    '@typescript-eslint',
  ],
  rules: {
    // ↓↓↓ADD↓↓↓
    'no-use-before-define': 'off',
    '@typescript-eslint/no-use-before-define': ['error'],
    '@typescript-eslint/no-unused-vars': 'error',
    'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }],
    'import/extensions': [
      'error',
      { extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'] },
    ],
    'react/prop-types': 'off',
    'spaced-comment': ['error', 'always', { markers: ['/ <reference'] }],
    // ↑↑↑ADD↑↑↑
  },
  // ↓↓↓ADD↓↓↓
  settings: {
    'import/resolver': {
      node: {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },
    },
  },
  // ↑↑↑ADD↑↑↑
}

env:jest: true がないと、テストに対して 'test' is not defined no-undef と怒られます。入れておきましょう。

stackoverflow.com

import 文にて 'React' was used before it was defined no-use-before-define と怒られることがあるので、 rules: の先頭2行に no-use-before-define に関する設定を入れています。なんだかバージョンによってはうまく動くよってな意見もありましたので、環境によっては不要かもしれません。

stackoverflow.com

他にも、 rules:settings: については追記した量が多いのですべての説明は省きます。

ESLint のコマンド実行

TypeScript のファイルを対象に ESLint を試しに実行してみます。コマンドは npx eslint . --ext .tsx --ext .ts です。

$ npx eslint . --ext .tsx --ext .ts

(略)\src\App.tsx
  11:16  error  `code` must be placed on a new line                          react/jsx-one-expression-per-line
  11:40  error  ` and save to reload.        ` must be placed on a new line  react/jsx-one-expression-per-line

...(中略)...

✖ 6 problems (6 errors, 0 warnings)
  6 errors and 0 warnings potentially fixable with the `--fix` option.

6つのエラーが出ました。「fixable with the --fix option.」とある通り --fix オプションを付けると強制修正を行ってくれます。この後 Prettier を入れますので、いま時点ではそのままにしておきます。

一点、「code must be placed on a new line」の指摘だけ直しちゃいます。

// ./src/App.tsx
// 修正前
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
// 修正後
        <p>Edit >src/App.tsx and save to reload.</p>

どのみち削除してしまうコードですので放ったらかしでも良いんですが、 ESLint がうるさいので <code> を削ってしまいます。まあ別に今やらなくてもいいとは思ってますが。

Prettier の導入と設定

続いて Prettier を導入し、設定を行いましょう。

Prettier の導入

Prettier および ESLint と連携するためのライブラリを導入します。

$ npm install ---save-dev prettier eslint-config-prettier eslint-plugin-prettier

ESLint の Prettier に関する設定

ESLint の設定を見直します。

// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
    jest: true,
  },
  extends: [
    'plugin:react/recommended',
    'airbnb',
    'plugin:prettier/recommended',         // <=ADD
    'prettier/@typescript-eslint',         // <=ADD
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: [
    'react',
    '@typescript-eslint',
    'prettier',                    // <=ADD
  ],
  rules: {
    'no-use-before-define': 'off',
    '@typescript-eslint/no-use-before-define': ['error'],
    '@typescript-eslint/no-unused-vars': 'error',
    'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }],
    'import/extensions': [
      'error',
      { extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'] },
    ],
    'react/prop-types': 'off',
    'spaced-comment': ['error', 'always', { markers: ['/ <reference'] }],
    'prettier/prettier': 'error', // <=ADD
  },
  settings: {
    'import/resolver': {
      node: {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },
    },
  },
}

plugins: に Prettier を、 extends: には、 ESLint と Prettier を連動させるためのプラグインを追記しています。

rules: に追加した 'prettier/prettier': 'error', これなんですが、「Prettier の全ルールを一発で全部追加するぜ!」という意味です。全部まとめて error として追加しています。(個別にレベルを設定できないという問題があるってことになりますね。これで困ったことはありませんが。)

qiita.com

Prettier に関する設定

さて、その ESLint と連動させた Prettier 自身の設定をしていきましょう。

まずは設定ファイルを用意します。

$ touch .prettierrc.json

設定ファイルの中身は以下のようにしておきます。ここらへんは好みでいいと思いますが、以下は、私が最小限で良さそうなものを用意したつもりです。設定内容は好みで変えてください。(セミコロンは無い方が好きな人が多そう。)

// .prettierrc.json
{
  "endOfLine": "lf",
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5"
}

補足 : VSCode は再起動しておくと吉

次項では Prettier のプラグインについて書いていますが、もし既にインストール済みであったなら、エディタ上のエラーの表示がおかしくなっているのではないでしょうか? いや、おかしくなかったら良いんですが。。。

私は VSCode の再起動で表示が正されたので、取り敢えず再起動してみると良いかもしれません。いや、おかしくなかったら良いんですが。。。

ESLint のコマンド実行(Prettier 付き)

Prettier を連動させた状態で試しに実行してみましょう。

$ npx eslint . --ext .tsx --ext .ts

(略)\src\react-app-env.d.ts
  1:40  error  Delete `␍`  prettier/prettier

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

なんか改行コードがおかしいっぽいですね。

ファイルを開いて直接修正してもよいですが、試しに強制修正を実施してみましょう。

$ npx eslint . --ext .tsx --ext .ts --fix

これでエラーが消えました。

VSCode の設定

プラグインの導入

毎回 npx eslint ... コマンドを打つのもいいですが、自動でチェックしてコード上に表示してくれると嬉しいです。そのために ESLint と Prettier のプラグインを導入します。

プラグインの導入方法は下記に書きましたので割愛します。(プラグイン入れるだけなので大した話ではないですが。。。)

multimineral-tech.com

プラグインの利用を促す

VSCode ではプロジェクトにて推奨するプラグインの利用を促すことができます。 .vscode/extensions.jsonを書いておけばVSCodeでプロジェクトを開いた時に推奨プラグインとして出せます。

まずはファイルを用意します。

$ mkdir .vscode
$ touch .vscode/extensions.json

次に、利用を促すプラグインを記載します。

// .vscode/extensions.json
{
  "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}

もし、プラグインが入っていない場合は VSCode から以下のような催促が入ります。

プラグインの利用を促す
プラグインの利用を促す

インストールボタンを押せば、2つのプラグインがインストールされます。

ファイル保存時に強制的に --fix をかける

npx eslint (...略...) --fix で強制修正ができました。この修正をファイル保存時に実行されるよう設定します。

まずは設定ファイルを用意します。

$ touch .vscode/settings.json

次に、ファイル保存時に設定する内容を記載します。(他の設定も混じってます。)

// .vscode/settings.json
{
  "editor.codeActionsOnSave": {
  "source.fixAll.eslint": true
  },
  "eslint.format.enable": true,
  "files.eol": "\n"
}

上記はファイルを touch で作成しましたが、以下の過去記事では、

multimineral-tech.com

次に VSCode の設定を行います。 Ctrl + , で設定を開き、 Workspace タブにして、 code action on save で検索します。GUIでそのまま設定したいところですが、ここでは「Edit in setting.json」をクリックします。すると .vscode/setting.json が生成され、このファイルが開きます。

という説明をしました。どちらも結果は同じになりますので、お好きな方法で設定ファイルを用意してください。

ESLint をファイル保存で実行

それでは VSCode に設定した内容が反映されたか試してみましょう。

何でもいいのですが、取り敢えず App.tsx の1行目のセミコロンを外して、それから保存をしてみます。

// ./src/App.tsx
// 試しに修正
import React from 'react'  // 最後のセミコロンを外す
// Ctrl + s で保存する
import React from 'react'; // 自動でセミコロンが付与された

セミコロンが自動で付与されたら成功です。