share facebook facebook facebook twitter twitter menu hatena pocket slack

2021.10.11 MON

Material UI v5 と Emotion の環境構築

遠藤 優輝

WRITTEN BY 遠藤 優輝

以前 Material UIEmotion を使った環境構築について記事を書いたのですが、Material UI が v4 から v5にアップデートされたので、今回は以前の記事のリライトになります。

前提

【React】Material-UI v4 と Emotion を併用するときの環境構築の記事のリライトになります。
記事執筆時点で Material UI のバージョンは v5 なります。
npm ではなく yarn を使っています。

React の環境構築

まずは create-react-app の typescript テンプレートを利用してプロジェクトを作成します。今回はプロジェクト名を myapp にします。

$ npx create-react-app myapp --template typescript

Material UI v5 と Emotion のインストール

スタイリングシステムが変更され emotion or styled-components が必須になりました。今回はタイトルにある通り emotionをインストールします。

$ yarn add @mui/material @emotion/react @emotion/styled

もし styled-componentsを使いたい場合は以下のコマンドでインストールできます。

$ yarn add @mui/material @mui/styled-engine-sc styled-components

一旦動くか確認してみましょう。不要なコードを消して App.tsx を以下に変更します。

myapp/src/App.tsx

import React from "react";
import Stack from "@mui/material/Stack";
import Button from "@mui/material/Button";

const App: React.FC = () => {
  return (
    <Stack spacing={2} direction="row">
      <Button variant="text">Text</Button>
      <Button variant="contained">Contained</Button>
      <Button variant="outlined">Outlined</Button>
    </Stack>
  );
};

export default App;

スタイリングの方法

v5 から makeStyle ではなく styled が推奨になったようです。以下の書き方でスタイルが反映されます。

myapp/src/App.tsx

import React from "react";
import { styled } from "@mui/material/styles";
import Button from "@mui/material/Button";

const MyButton = styled(Button)({
  backgroundColor: "red",
  "&:hover": {
    backgroundColor: "red",
  },
});

const App: React.FC = () => {
  return <MyButton variant="contained">ボタン</MyButton>;
};

export default App;

また sx props というの新しいスタリングの方法が導入されました。ユーティリティファーストな書き方ができます。

myapp/src/App.tsx

import React from "react";
import { styled } from "@mui/material/styles";
import Button from "@mui/material/Button";

const App: React.FC = () => {
  return (
    <Button
      variant="contained"
      sx={{
        backgroundColor: "red",
        "&:hover": {
          backgroundColor: "red",
        },
      }}
    >
      ボタン
    </Button>
  );
};

export default App;

Emotion の css prop を使う

公式で推奨されているわけではないですが css propを使いたい方は以下のように書くことができます(個人的に css prop を使っているので、以降のスタイリングも css propを使っていきます)。

myapp/src/App.tsx

/** @jsxImportSource @emotion/react */
import React from "react";
import { css } from "@emotion/react";
import Button from "@mui/material/Button";

const style = css`
  background-color: red;
  &:hover {
    background-color: red;
  }
`;

const App: React.FC = () => {
  return (
    <Button variant="contained" css={style}>
      ボタン
    </Button>
  );
};

export default App;

CRACO のインストール

/** @jsxImportSource @emotion/react */ を毎回書くのは面倒なので CRACOをインストールします。CRACO は create-react-app の設定を上書きするときに使えるライブラリになります。

Create React App Configuration Override, an easy and comprehensible configuration layer for create-react-app - GitHub - gsoft-inc/craco: Create React App Configuration Override, an easy and compreh...
$ yarn add @craco/craco

craco.config.js をルートに作り babel の設定を上書きします。

myapp/craco.config.js

module.exports = {
  babel: {
    presets: [
      [
        "@babel/preset-react",
        { runtime: "automatic", importSource: "@emotion/react" },
      ],
    ],
    plugins: ["@emotion/babel-plugin"],
  },
};

App.tsxファイルから/** @jsxImportSource @emotion/react */ の記述を消してもスタイルが反映されるようになりました。

myapp/src/App.tsx

import React from "react";
import { css } from "@emotion/react";
import Button from "@mui/material/Button";

const style = css`
  background-color: red;
  &:hover {
    background-color: red;
  }
`;

const App: React.FC = () => {
  return (
    <Button variant="contained" css={style}>
      ボタン
    </Button>
  );
};

export default App;

Theme を Material UI と Emotion で共有する

Emotion の css prop で Material UI の Theme を使えるようにします。
まずは index.tsx を以下のように書き換えます。

myapp/index.tsx

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { ThemeProvider } from "@emotion/react";
import {
  createTheme,
  ThemeProvider as MUThemeProvider,
} from "@mui/material/styles";

const theme = createTheme();

ReactDOM.render(
  <React.StrictMode>
    <MUThemeProvider theme={theme}>
      <ThemeProvider theme={theme}>
        <App />
      </ThemeProvider>
    </MUThemeProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

reportWebVitals();

emotion.d.tsファイルを作成し Emotion の Theme 型に Material UI の Theme 型を継承します。

myapp/src/types/emotion.d.ts

import { Theme as MUTheme } from "@mui/material/styles";
declare module "@emotion/react" {
  export interface Theme extends MUTheme {}
}

App.tsxを以下のように書き換えます。Emotion からも Material UI の
Theme が使えるようになっていますね。

myapp/src/App.tsx

import React from "react";
import { css, Theme } from "@emotion/react";
import Button from "@mui/material/Button";

const style = (theme: Theme) => css`
  color: ${theme.palette.error.light};
`;

const App: React.FC = () => {
  return (
    <Button variant="contained" css={style}>
      ボタン
    </Button>
  );
};

export default App;

Theme の拡張

次にテーマを拡張したいときの設定方法になります。
テーマの拡張には主に 3 パターンあると思っています。

  • Theme の上書きをしたいとき
  • Theme に新規項目を加えたいとき
  • すでにある Theme を拡張したいとき

順番に書いていきます。

Theme の上書きをしたいとき

上書きしたいだけならシンプルです。
createThemeで該当する値を上書きすれば問題ありません。

const theme = createTheme({
  typography: {
    fontFamily: `"Meiryo", "メイリオ", sans-serif`,
  },
});

Theme に新規項目を加えたいとき

次に新しい項目を作成したいときです。
「Theme の上書きをしたいとき」と同様に createTheme に新しく加えたい値を書きます。
この時点では型定義のエラーが出るかもしれませんが、次の作業で対応します。

const theme = createTheme({
  headerHeight: 100,
});

型の拡張をおこなます。
material-ui.d.tsという名前(任意の名前で OK)でファイルを作り、以下のように書きます。
ThemeThemeOptions の両方で型を拡張します。

material-ui.d.ts

import { Theme, ThemeOptions } from "'@mui/material/styles";
declare module "@mui/material/styles" {
  interface Theme {
    headerHeight: number;
  }
  interface ThemeOptions {
    headerHeight: number;
  }
}

すでにある Theme を拡張したいとき

基本的には、「Theme に新規項目を加えたいとき」と同じです。
以下は typographyfont-size を計算する新しい関数を追加しています。

const theme = createTheme({
  typography: {
    size: (n: number) => n * 4,
  },
});

material-ui.d.ts

import {
  Typography,
  TypographyOptions,
} from "@mui/material/styles/createTypography";
declare module "@mui/material/styles/createTypography" {
  interface Typography {
    size: (number) => number;
  }
  interface TypographyOptions {
    size: (number) => number;
  }
}

型定義は myapp/node_modules/@mui/material/styles/createTheme.d.tsで確認できます。
以下のように型定義されているので、必要そうなところを拡張するといった感じです。

createTheme.d.ts

/** 略 **/
export interface ThemeOptions extends SystemThemeOptions {
  mixins?: MixinsOptions;
  components?: Components;
  palette?: PaletteOptions;
  shadows?: Shadows;
  transitions?: TransitionsOptions;
  typography?: TypographyOptions | ((palette: Palette) => TypographyOptions);
  zIndex?: ZIndexOptions;
  unstable_strictMode?: boolean;
}

export interface Theme extends SystemTheme {
  mixins: Mixins;
  components?: Components;
  palette: Palette;
  shadows: Shadows;
  transitions: Transitions;
  typography: Typography;
  zIndex: ZIndex;
  unstable_strictMode?: boolean;
}
/** 略 **/

一旦ここまでの対応を踏まえて、以下のように書き換えます。Theme の拡張を行えていることが確認できると思います。

myapp/index.tsx

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { ThemeProvider } from "@emotion/react";
import {
  createTheme,
  ThemeProvider as MUThemeProvider,
} from "@mui/material/styles";

const theme = createTheme({
  headerHeight: 100,
  typography: {
    fontFamily: `"Meiryo", "メイリオ", sans-serif`,
    size: (n: number) => n * 4,
  },
});

ReactDOM.render(
  <React.StrictMode>
    <MUThemeProvider theme={theme}>
      <ThemeProvider theme={theme}>
        <App />
      </ThemeProvider>
    </MUThemeProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

reportWebVitals();

myapp/src/App.tsx

import React from "react";
import { css, Theme } from "@emotion/react";
import Button from "@mui/material/Button";

const headerStyle = (theme: Theme) => css`
  height: ${theme.headerHeight}px;
  background: ${theme.palette.primary.main};
`;
const textStyle = (theme: Theme) => css`
  color: ${theme.palette.text.primary};
`;
const buttonStyle = (theme: Theme) => css`
  font-size: ${theme.typography.size(10)}px;
`;

const App: React.FC = () => {
  return (
    <div>
      <header css={headerStyle}>ヘッダー</header>
      <p css={textStyle}>サンプルテキスト</p>
      <Button variant="contained" css={buttonStyle}>
        ボタン
      </Button>
    </div>
  );
};

export default App;

material-ui.d.ts

import { Theme, ThemeOptions } from "'@mui/material/styles";
declare module "@mui/material/styles" {
  interface Theme {
    headerHeight: number;
  }
  interface ThemeOptions {
    headerHeight: number;
  }
}

import {
  Typography,
  TypographyOptions,
} from "@mui/material/styles/createTypography";
declare module "@mui/material/styles/createTypography" {
  interface Typography {
    size: (number) => number;
  }
  interface TypographyOptions {
    size: (number) => number;
  }
}

Global CSS の設定

Global CSS は Emotion の Global Styles機能を使います。

Global を Emotion から import して、Global CSS を定義します。

myapp/src/styles/GlobalStyles.tsx

import React from "react";
import { Global, css, Theme } from "@emotion/react";

const global = (theme: Theme) => css`
  html,
  body {
    width: 100%;
    height: 100%;
    margin: 0;
    font-family: ${theme.typography.fontFamily};
  }
`;
const GlobalStyles: React.FC = () => {
  return <Global styles={global} />;
};

export default GlobalStyles;

上記で作成したファイルを index.tsxで読み込むだけで Global な CSS を設定できます。

myapp/src/index.tsx

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { ThemeProvider } from "@emotion/react";
import {
  createTheme,
  ThemeProvider as MUThemeProvider,
} from "@mui/material/styles";
import GlobalStyles from "./styles/GlobalStyles";

const theme = createTheme({
  headerHeight: 100,
  typography: {
    fontFamily: `"Meiryo", "メイリオ", sans-serif`,
    size: (n: number) => n * 4,
  },
});

ReactDOM.render(
  <React.StrictMode>
    <MUThemeProvider theme={theme}>
      <ThemeProvider theme={theme}>
        <GlobalStyles />
        <App />
      </ThemeProvider>
    </MUThemeProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

reportWebVitals();

まとめ

Material UI v5 における環境構築の話でした。
v4 から変更された部分も結構ありますので、参考にしていただけると幸いです。

参考 URL

https://mui.com/
https://emotion.sh/docs/introduction
https://github.com/gsoft-inc/craco
https://zenn.dev/h_yoshikawa0724/articles/2021-09-26-material-ui-v5

元記事はこちら

https://qiita.com/yuki-endo/items/f79886407add408c72c1

遠藤 優輝

遠藤 優輝

streampackチーム所属。グミが好きです。

cloudpack

cloudpackは、Amazon EC2やAmazon S3をはじめとするAWSの各種プロダクトを利用する際の、導入・設計から運用保守を含んだフルマネージドのサービスを提供し、バックアップや24時間365日の監視/障害対応、技術的な問い合わせに対するサポートなどを行っております。
AWS上のインフラ構築およびAWSを活用したシステム開発など、案件のご相談はcloudpack.jpよりご連絡ください。