Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
0
Help us understand the problem. What are the problem?

posted at

updated at

Organization

Material UI v5 と Emotion の環境構築

以前 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 の設定を上書きするときに使えるライブラリになります。

$ 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

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
0
Help us understand the problem. What are the problem?