以前 Material UI
と Emotion
を使った環境構築について記事を書いたのですが、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
を以下に変更します。
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
が推奨になったようです。以下の書き方でスタイルが反映されます。
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
というの新しいスタリングの方法が導入されました。ユーティリティファーストな書き方ができます。
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
を使っていきます)。
/** @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 の設定を上書きします。
module.exports = {
babel: {
presets: [
[
"@babel/preset-react",
{ runtime: "automatic", importSource: "@emotion/react" },
],
],
plugins: ["@emotion/babel-plugin"],
},
};
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;
Theme を Material UI と Emotion で共有する
Emotion の css prop
で Material UI の Theme を使えるようにします。
まずは 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 型を継承します。
import { Theme as MUTheme } from "@mui/material/styles";
declare module "@emotion/react" {
export interface Theme extends MUTheme {}
}
App.tsx
を以下のように書き換えます。Emotion からも Material UI の
Theme が使えるようになっていますね。
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)でファイルを作り、以下のように書きます。
Theme
と ThemeOptions
の両方で型を拡張します。
import { Theme, ThemeOptions } from "'@mui/material/styles";
declare module "@mui/material/styles" {
interface Theme {
headerHeight: number;
}
interface ThemeOptions {
headerHeight: number;
}
}
すでにある Theme を拡張したいとき
基本的には、「Theme に新規項目を加えたいとき」と同じです。
以下は typography
に font-size
を計算する新しい関数を追加しています。
const theme = createTheme({
typography: {
size: (n: number) => n * 4,
},
});
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 で確認できます。
以下のように型定義されているので、必要そうなところを拡張するといった感じです。
/** 略 **/
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 の拡張を行えていることが確認できると思います。
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();
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;
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 を定義します。
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 を設定できます。
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