MUI v4 で使っていた withStyles/makeStyles を v5 の sx/styled() に書き換える


こんにちは、上條(m-0A0)です。
弊社 Gaji-Labo はチームワークを提供するスタートアップの支援を行っています。その中で現在私が参画しているプロジェクトではスタイリングに MUI v4 を使用しており、少しずつ v5 への移行を進めています。v5 は破壊的な変更だったため大規模な本プロジェクトでは移行に時間がかかっており、そうこうしているうちに v6/v7 の情報を見かけるようになりました。今更感はありますが、プロジェクトで行っている移行を備忘録として残しておきたいと思います。

v5 で変わったところ

大きく変わった部分として、デフォルトのスタイリングソリューションが JSS から Emotion に変わったことが挙げられます。そのため v5 では JSS に依存していた独自のスタイリングソリューションである withStyles や makeStyles が非推奨になり、新しく追加された sx や styled() の使用が推奨されています。
v5 でも withStyles/makeStyles は使用できますが、React v18 と互換性が無いため今後の開発を考えると移行した方がよさそうだなと思いました。

マイグレーションの手順は公式ドキュメントをご確認ください。
Migrating to v5: getting started – Material UI

実際に書き換えてみる

MUI の Button コンポーネントをカスタマイズしている独自のコンポーネントを v5 に書き換えて比較してみます。

ボタンコンポーネントの見た目をv4とv5で比較している画像

v4

import {
  Box,
  Button,
  SvgIcon,
  makeStyles,
  withStyles,
} from "@material-ui/core";

import ChevronRight from "@/assets/icons/ChevronRight.svg";

const StyledButton = withStyles({
  root: {
    height: 40,
    padding: "7.5px 16px",
    fontSize: "14px",
    fontFamily: "inherit",
    lineHeight: 1.5,
    textTransform: "none",
    fontWeight: 500,
    backgroundColor: "skyblue",
    color: "black",
  },
})(Button);

const useStyles = makeStyles({
  ButtonOuter: {
    display: "inline-block",
    backgroundColor: "lightgray",
    padding: 16,
  },
});

export function ButtonSampleV4(): JSX.Element {
  const classes = useStyles();
  return (
    <Box className={classes.ButtonOuter}>
      <StyledButton
        endIcon={
          <SvgIcon>
            <ChevronRight />
          </SvgIcon>
        }
      >
        ボタン v4
      </StyledButton>
    </Box>
  );
}

v5

import { Box, Button, SvgIcon } from "@mui/material";
import { styled } from "@mui/system";

import ChevronRight from "@/assets/icons/ChevronRight.svg";

const StyledButton = styled(Button)(() => ({
  "&.MuiButtonBase-root": {
    height: 40,
    padding: "7.5px 16px",
    fontSize: "14px",
    fontFamily: "inherit",
    lineHeight: 1.5,
    textTransform: "none",
    fontWeight: 500,
    backgroundColor: "skyblue",
    color: "black",
  },
}));

export function ButtonSampleV5(): JSX.Element {
  return (
    <Box
      sx={{
        display: "inline-block",
        backgroundColor: "lightgray",
        padding: "16px",
      }}
    >
      <StyledButton
        endIcon={
          <SvgIcon>
            <ChevronRight />
          </SvgIcon>
        }
      >
        ボタン v5
      </StyledButton>
    </Box>
  );
}

変更点は以下です。

  • コンポーネントの import を “@material-ui/core” から “@mui/material” に変更
  • StyledButton を withStyles から styled() に変更
  • makeStyles で定義していた useStyles のスタイルを Box コンポーネントの sx に変更

styled() は 本格的なスタイリング、 sx はその要素にしか使わない軽微なスタイル調整に向いていそうだなと思いました。

動的なスタイルの変更

コンポーネントに渡ってくる props によってスタイルを出し分ける記述も書き換えてみます。isBold props によって font-weight を出し分ける実装をしました。

v4

import {
  Box,
  Button,
  SvgIcon,
  makeStyles,
  withStyles,
} from "@material-ui/core";
import classNames from "classnames";

import ChevronRight from "@/assets/icons/ChevronRight.svg";

const StyledButton = withStyles({
  root: {
    height: 40,
    padding: "7.5px 16px",
    fontSize: "14px",
    fontFamily: "inherit",
    lineHeight: 1.5,
    textTransform: "none",
    fontWeight: 500,
    backgroundColor: "skyblue",
    color: "black",
    "&.isBold": { // 追加
      fontWeight: 700,
    },
  },
})(Button);

const useStyles = makeStyles({
  ButtonOuter: {
    display: "inline-block",
    backgroundColor: "lightgray",
    padding: 16,
  },
});

type Props = { // 追加
  isBold?: boolean;
};

export function ButtonSampleV4({ isBold }: Props): JSX.Element {
  const classes = useStyles();
  return (
    <Box className={classNames(classes.ButtonOuter)}>
      <StyledButton
        classes={{ // 追加
          root: isBold ? "isBold" : undefined,
        }}
        endIcon={
          <SvgIcon>
            <ChevronRight />
          </SvgIcon>
        }
      >
        ボタン v4
      </StyledButton>
    </Box>
  );
}

// 使う側
<ButtonSampleV4 isBold />
v4 は font-weight: 700 を指定した新しいクラスが生成される

v5

import { Box, Button, SvgIcon } from "@mui/material";
import { styled } from "@mui/system";

import ChevronRight from "@/assets/icons/ChevronRight.svg";

type Props = { // 追加
  isBold?: boolean;
};

const StyledButton = styled(Button)<Props>(({ isBold }) => ({
  "&.MuiButtonBase-root": {
    height: 40,
    padding: "7.5px 16px",
    fontSize: "14px",
    fontFamily: "inherit",
    lineHeight: 1.5,
    textTransform: "none",
    backgroundColor: "skyblue",
    color: "black",
    ...(isBold && { // 追加
      fontWeight: 700,
    }),
  },
}));

export function ButtonSampleV5({ isBold }: Props): JSX.Element {
  return (
    <Box
      sx={{
        display: "inline-block",
        backgroundColor: "lightgray",
        padding: "16px",
      }}
    >
      <StyledButton
        isBold={isBold} // 追加
        endIcon={
          <SvgIcon>
            <ChevronRight />
          </SvgIcon>
        }
      >
        ボタン v5
      </StyledButton>
    </Box>
  );
}

// 使う側
<ButtonSampleV5 isBold />
v5 はMuiButtonBase-root の中に font-weight:700 が追加される

v4 では classes を使って isBold クラスを付与しているのに対して、v5 では styled() の中で isBold による出し分けの実装が可能でした。StyledButton の中で完結できるため、コードの見通しが良くなりそうです。

まとめ

v5 へのマイグレーションは単純なパッケージのアップデートで済まないのが辛いところだと思います。この v4 => v5 の移行コストの高さは公式でも言及されており、今後予定されているメジャーアップデートは2024年中に v6/v7 と小さい粒度で頻繁に行う方針にシフトしているそうです。
v6/v7 について気になる方はぜひ弊社メンバーが作成したスライドも併せてご覧いただけると幸いです。

Overview(v6.0.0-alpha.5) – Material UI

マイグレーション(特に今回のような破壊的なマイグレーション)は負債としてプロジェクトに残りがちな課題です。事業成長のためには開発効率も大切なので、Gaji-Labo はそのような技術面でのサポートも行っています。お困りのことがあればぜひお気軽にご相談ください。

Gaji-Labo は Next.js, React, TypeScript 開発の実績と知見があります

フロントエンド開発の専門家である私たちが御社の開発チームに入ることで、バックエンドも含めた全体の開発効率が上がります。

「既存のサイトを Next.js に移行したい」
「人手が足りず信頼できるエンジニアを探している」
「自分たちで手を付けてみたがいまいち上手くいかない」

フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にご相談ください。

オンラインでのヒアリングとフルリモートでのプロセス支援にも対応しています。

Next.js, React, TypeScript の相談をする!

投稿者 Kamijo Momoka

フロントエンドエンジニア。
HTML/CSS/JavaScript/WordPressでのサイト制作からNext.js/TypeScriptなどを使ったWebアプリ開発、FigmaでのUIデザインまで広く経験しています。 デザインエンジニアと名乗るのが夢です。