名称未設定

タイトルはそのうち考えます

MUI(Material UI v5)でEmotionを利用する

React + TypeScriptの環境でMUIとEmotionを併用する際についてのメモです

MUIとEmotionのインストール

Reactの環境構築は割愛します

MUIとEmotionをまとめてインストール

$ npm install @mui/material @emotion/react @emotion/styled

or

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

その他必要なパッケージがあれば適宜

Emotionのcss propsを利用する

/* @jsxImportSource @emotion/react */
import React from 'react";
import { css } from '@emotion/react';
import Button from '@mui/material/Button';
 
const style = css({
  background: 'red'
});
 
const App = (): JSX.Element => {
  return (
    <Button variant="contained" css={style}>
      ボタン
    </Button>
  );
};
 
export default App;

こんな感じ

おまじないを消す

Emotionをtsx(jsx)で利用する際はimport時に
/** @jsx jsx */ or /* @jsxImportSource @emotion/react */
といったおまじないを記述する必要があります。やってられません

なんでおまじないがいるの?って話はググってください

こんなことはやってられないので公式の解決策を利用します

$ npm add @emotion/babel-preset-css-prop

or

$ yarn add @emotion/babel-preset-css-prop

追加後、 .babelrc かwebpackの babel-loaderpresets@emotion/babel-preset-css-prop を追加します

{
  test: /\.(js|mjs|jsx|ts|tsx)$/,
  loader: require.resolve('babel-loader'),
  options: {
    ...,
    presets: [
      ...,
      '@emotion/babel-preset-css-prop',
    ],

こんな感じ。これにて解決

MUIのThemeをEmotionで利用する

MaterialUIのThemeをそのままEmotionで利用することができます

import { Box, Button } from '@mui/material';
import { createTheme } from '@mui/material';
import { Theme, ThemeProvider } from '@emotion/react';

const theme = createTheme();

const style = (theme: Theme) =>
  css({
    background: theme.palette.primary.main,
  });

const App = (): JSX.Element => {
  return (
    <ThemeProvider theme={theme}>
      <Box>
        <Button variant="outlined" css={style}>hoge</Button>
      </Box>
    </ThemeProvider>
  );
}

export default App;

ただしこれではMUI由来のThemeの項目を利用しようとすると型エラーが出ていしまいます

そこでEmotionのThemeをMUIのThemeの型を継承する形で上書きします

import { Theme as MUITheme } from '@mui/material/styles';
import theme from '@/Themes/Theme';

declare module '@emotion/react' {
  export interface Theme extends MUITheme {}
}

こんな感じ
これで型エラーもなくpaletteやtypographyを利用できます

拡張

themeに項目を追加したいケースもあります

const theme = createTheme(theme, {
  width: {
    small: '5rem',
    medium: '10rem',
    large: '20rem',
    full: '100%'
  }
});

その場合は先程の上書きした型に項目を追加してください

declare module '@emotion/react' {
  export interface Theme extends MUITheme {
    width: {
      small: string;
      medium: string;
      large: string;
      full: string;
    }
  }
}

こんな感じ

styledの利用

styledもMUIと同様にThemeを用いながら使用できます

import React from 'react';
import { Box, Button } from '@mui/material';
import { styled as muiStyled } from '@mui/material/styles';
import styled from '@emotion/styled';

const StyledBox = styled(Box)(({ theme: Theme }) => ({
  background: theme.palette.primary.main,
}));
// propsの型を定義して利用することも可能
const PropsStyledBox = styled(Box)<{ color: string }>(props => ({
  background: props.theme.palette.primary.main,
  color: props.color,
}));
const MUIStyledBox = muiStyled(Box)(({ theme: Theme }) => ({
  color: 
  background: theme.palette.primary.dark,
}));

const Test = (): JSX.Element => {
  return (
    <Box>
      <Box>
        normal box
      </Box>
      <StyledBox>
        emotion styled box
      </StyledBox>
      <PropsStyledBox color="red"}>
        emotion props styled box
      </StyledBox>
      <MUIStyledBox>
        mui styled box
      </MUIStyledBox>
  </Box>
  );
}

export default Test;

注意点 

sxで上書きする際の挙動がMUIとEmotionで異なります

import React from 'react';
import { Box, Button } from '@mui/material';
import { styled as muiStyled } from '@mui/material/styles';
import styled from '@emotion/styled';

const StyledBox = styled(Box)(({ theme: Theme }) => ({
  background: theme.palette.primary.main,
  display: 'flex',
  justifyContent: 'center'
}));
const MUIStyledBox = muiStyled(Box)(({ theme: Theme }) => ({
  background: theme.palette.primary.dark,
  display: 'flex',
  justifyContent: 'center'
}));

const Test = (): JSX.Element => {
  return (
    <Box>
      <Box>
        normal box
      </Box>
      <StyledBox sx={{display: "flex", justifyContent: "flex-start"}}>
        emotion styled boxは justifyContent: centerになる
      </StyledBox>
      <MUIStyledBox sx={{display: "flex", justifyContent: "flex-start"}}>
        mui styled boxはjustifyContent: flex-startになる
      </MUIStyledBox>
  </Box>
  );
}

export default Test;

MUIはsxでの定義が優先されますがEmotionはstyledの定義が優先されます
お気をつけを

おわり

備忘録代わりに殴り書きしました
そのままコピペしても使えない部分が結構ありそうですがご容赦