📖

MUIのコンポーネント装飾について速度比較する Box編

2022/01/02に公開

はじめに

MUIのコンポーネントカスタマイズについては、基礎システムが多機能なCSS-in-JS ライブラリであるemotionを使っていることもあり、色々な方法があります。
https://mui.com/customization/how-to-customize/
本家でもいくつかの方法が提案されており、
https://mui.com/system/basics/#performance-tradeoff
パフォーマンスについても比較を掲示してくれています。

こちらのパフォーマンス比較、コードも公開されていたので、
https://github.com/mui-org/material-ui/tree/master/benchmark/browser
こちらを利用して、どんな記述ができるのか、比較してみた、という内容になります。
yarn benchmark:browser
で計測が実行できます

今回の比較内容

今回はdivタグのカスタマイズについて比較を行います。
MUIはDialogやTextField等多数のコンポーネントを提供しており、
MUIから追加されたsx propsや、Theme、sytled等いくつかのカスタム方法があり興味深いのですが、今回はMUI特有のTheme設定によるカスタマイズの手前、シンプルなdivの装飾についていくつかの方法を比較してみます。

実行環境

windows10環境
プロセッサIntel(R) Core(TM) i9-9900K
メモリサイズ64.0 GB
node@14.17.3
powershell7.1.2

div比較その1 装飾編

元々のmuiのbenchmarkにあった、下記のようなhoverとmedia queryで装飾するパターンを比較します。
今回試した装飾方法は

  • css+classNameで装飾
  • emotion/styledで装飾
  • MUIのsx propsで装飾
  • mui/system styledで装飾
  • createBoxで装飾
.boxStyle {
  width: 200px;
  height: 200px;
  border-width: 3px;
  border-color: white;
}
.boxStyle:hover {
  background-color: #c51162
};

@media (min-width:0px) { 
  .boxStyle{
    background-color: #3f51b5;
    border-style: dashed;
  }
}

@media (min-width:600px) {
  .boxStyle {
    background-color: rgba(0, 0, 0, 0.87);
    border-style: solid
  }
}

@media (min-width:960px) {
  .boxStyle {
    background-color: #fff;
    border-style: dotted
  }
};

css+className

下記のようなコンポーネントです。
超シンプル。

box-css
import * as React from 'react';
import './box.css'

export default function BoxCss() {
  return (
    <React.Fragment>
      {new Array(1000).fill().map(() => (
        <div
          className="boxStyle"
        >
          test case
        </div>
      ))}
    </React.Fragment>
  );
}
  • 実行速度
    noopとReact componentsはデフォルトのシナリオです。
    noopは一切のコンポーネントをjs側にもたないモノ
    React componentsは装飾のないdivコンポーネントを上記同様1000個描画するモノ
    になります。
noop (baseline) React components Box css
04.22 ±00.16ms 34.42 ±00.36ms 98 ±1%

emotion/styledを使った修飾

こちらはbox-baselineという名前で最初から準備されています。
ちなみに、MUIのコンポーネント類もこれと同様Emotionのcss()でカスタムできます
https://mui.com/guides/interoperability/#the-css-prop

box-baseline
import * as React from 'react';
import emotionStyled from '@emotion/styled';

const Box = emotionStyled('div')(({ css }) => css);

export default function BoxBaseline() {
  return (
    <React.Fragment>
      {new Array(1000).fill().map(() => (
        <Box
          css={{
            width: 200,
            height: 200,
            borderWidth: '3px',
            borderColor: 'white',
            '&:hover': { backgroundColor: '#c51162' },
            '@media (min-width:0px)': { backgroundColor: '#3f51b5', borderStyle: 'dashed' },
            '@media (min-width:600px)': {
              backgroundColor: 'rgba(0, 0, 0, 0.87)',
              borderStyle: 'solid',
            },
            '@media (min-width:960px)': { backgroundColor: '#fff', borderStyle: 'dotted' },
          }}
        >
          test case
        </Box>
      ))}
    </React.Fragment>
  );
}
  • 実行速度
noop (baseline) React components Box emotion
04.22 ±00.16ms 34.42 ±00.36ms 168 ±3%

MUI sx props

こちらもデフォルトからbox-material-uiとして存在しています。
sx propsはワンタイムのカスタム用として用意されたpropsになります。

box-material-ui
import * as React from 'react';
import Box from '@mui/material/Box';

export default function SxPropBoxMaterialUI() {
  return (
    <React.Fragment>
      {new Array(1000).fill().map(() => (
        <Box
          sx={{
            width: 200,
            height: 200,
            borderWidth: '3px',
            borderColor: 'white',
            '&:hover': { backgroundColor: '#c51162' },
            '@media (min-width:0px)': {
              backgroundColor: '#3f51b5',
              borderStyle: 'dashed'
            },
            '@media (min-width:600px)': {
              backgroundColor: 'rgba(0, 0, 0, 0.87)',
              borderStyle: 'solid',
            },
            '@media (min-width:960px)': {
              backgroundColor: '#fff',
              borderStyle: 'dotted'
            },
          }}
        >
          test case
        </Box>
      ))}
    </React.Fragment>
  );
}
  • 実行速度
noop (baseline) React components BoxSxProps
04.22 ±00.16ms 34.42 ±00.36ms 326 ±12%

mui/system styledで装飾

https://mui.com/system/styled/#introduction
こちらの機能で実装するパターンです。
こちらはデフォルトには無いパターンなので追記してためしてみました。

box-mui-styled
import * as React from 'react';
import { styled } from '@mui/system';

const Box = styled('div')({
  width: 200,
  height: 200,
  borderWidth: '3px',
  borderColor: 'white',
  '&:hover': {
    backgroundColor: '#c51162',
  },
  '@media (min-width:0px)': {
    backgroundColor: '#3f51b5',
    borderStyle: 'dashed'
  },
  '@media (min-width:600px)': {
    backgroundColor: 'rgba(0, 0, 0, 0.87)',
    borderStyle: 'solid',
  },
  '@media (min-width:960px)': {
    backgroundColor: '#fff',
    borderStyle: 'dotted'
  }
})

export default function BoxMuiStyled() {
  return (
    <React.Fragment>
      {new Array(1000).fill().map(() => (
        <Box>
          test case
        </Box>
      ))}
    </React.Fragment>
  );
}
  • 実行速度
noop (baseline) React components BoxMuiStyled
04.22 ±00.16ms 34.42 ±00.36ms 170 ±2%

createBoxで装飾

https://mui.com/system/box/#create-your-own-box-component
createBoxという関数を使うことで、themeを渡したBoxを作ることができます。
objectでbackgroundColorやborderStyleのmediaqueryを記述しています。

box-mui-create-box
import { createBox, createTheme } from '@mui/system';

const customTheme = createTheme({
    breakpoints: {
      values: {
        xs: 0,
        sm: 600,
        md: 900,
        lg: 1200,
        xl: 1536,
      },
  },
});

const Box = createBox({ defaultTheme: customTheme });

export default function BoxCreate() {
  return (
    <>
      {new Array(1000).fill().map(() => (
        <Box
          sx={{
            width: 200,
            height: 200,
            borderWidth: '3px',
            borderColor: 'white',
            backgroundColor: {xs:'#3f51b5', sm: 'rgba(0, 0, 0, 0.87)', md: "#fff"},
            borderStyle: {xs: 'dashed', sm: 'solid', md: 'dotted' },
            '&:hover': { backgroundColor: '#c51162' },
          }}
        >
          test case
        </Box>
      ))}
    </>
  );
}
  • 実行速度
noop (baseline) React components BoxCreate
04.22 ±00.16ms 34.42 ±00.36ms 294 ±5%

速度まとめと感想

各記述方法での速度は早い順に
CSS+className => emotion => MUI styled => MUI createBox => MUI sx props
という結果になりました。
emotionやstyledは一層低い階層でのカスタムをしているためか、早いようです。
CSS-in-JSの記法はemotion以降はほぼ同じなので、div程度のシンプルなタグのカスタマイズをする場合は

  • とにかく速度重視ならCSS
  • 通常はstyled or emotion
  • themeを利用するならsx props

という感じかな、と思いました。
特に今回のようなmedia queryを利用する場合は統一のためにthemeを使うのが楽なように思います。

noop (baseline) React components Box css Box emotion BoxSxProps BoxMuiStyled BoxCreate
04.22 ±00.16ms 34.42 ±00.36ms 98 ±1% 168 ±3% 326 ±12% 170 ±2% 294 ±5%

div比較その2 FlexBox編

MUIのBoxコンポーネントはある程度propsでスタイルを指定できる機能があります。
3つのdivタグを横に並べるBoxを

  • divタグにstyle propsで装飾
  • emotion/styledで装飾
  • MUIのBoxコンポーネントのpropsで装飾
  • MUIのBoxコンポーネントをsx propsで装飾

という方法で作った際の速度を比較してみました。

divタグにstyle propsで装飾

flexbox-div
import * as React from 'react';

export default function FlexBoxDiv() {
  return (
    <React.Fragment>
      {new Array(1000).fill().map(() => (
        <div
          style={{
            alignItems:"center",
            justifyContent:"space-around",
            display:"flex",
            flexDirection:"row",
            width:"500px",
            height:"200px"
          }}
        >
          <div>
            test No.1
          </div>
          <div>
            test No.2
          </div>
          <div>
            test No.3
          </div>
        </div>
      ))}
    </React.Fragment>
  );
}
  • 実行速度
noop (baseline) React components FlexBoxDiv
04.21 ±00.12ms 34.42 ±00.47ms 293 ±4%

emotion/styledで装飾

divタグにstyle propsで装飾

flexbox-emotion
import * as React from 'react';
import emotionStyled from '@emotion/styled';

const Box = emotionStyled('div')(({ css }) => css);

export default function FlexBoxBaseline() {
  return (
    <React.Fragment>
      {new Array(1000).fill().map(() => (
        <Box
          css={{
            width: 500,
            height: 200,
            display: "flex",
            flexDirection: "row",
            justifyContent: "space-around",
            alignItems: "center"
          }}
        >
          <div>
            test No.1
          </div>
          <div>
            test No.2
          </div>
          <div>
            test No.3
          </div>
        </Box>
      ))}
    </React.Fragment>
  );
}
  • 実行速度
noop (baseline) React components FlexBoxEmotion
04.21 ±00.12ms 34.42 ±00.47ms 308 ±4%

MUIのBoxコンポーネントのpropsで装飾

flexbox-mui
import * as React from 'react';
import Box from '@mui/material/Box';

export default function FlexBoxMui() {
  return (
    <React.Fragment>
      {new Array(1000).fill().map(() => (
        <Box
          alignItems="center"
          justifyContent="space-around"
          display="flex"
          flexDirection="row"
          width="500px"
          height="200px"
        >
          <div>
            test No.1
          </div>
          <div>
            test No.2
          </div>
          <div>
            test No.3
          </div>
        </Box>
      ))}
    </React.Fragment>
  );
}
  • 実行速度
noop (baseline) React components FlexBoxMui
04.21 ±00.12ms 34.42 ±00.47ms 404 ±7%

MUIのBoxコンポーネントをsx propsで装飾

flexbox-mui-sx
import * as React from 'react';
import Box from '@mui/material/Box';

export default function FlexBoxMuiSx() {
  return (
    <React.Fragment>
      {new Array(1000).fill().map(() => (
        <Box
          sx={{
            alignItems:"center",
            justifyContent:"space-around",
            display:"flex",
            flexDirection:"row",
            width:"500px",
            height:"200px"
          }}
        >
          <div>
            test No.1
          </div>
          <div>
            test No.2
          </div>
          <div>
            test No.3
          </div>
        </Box>
      ))}
    </React.Fragment>
  );
}
  • 実行速度
noop (baseline) React components FlexBoxMuiSx
04.21 ±00.12ms 34.42 ±00.47ms 403 ±6%

速度まとめと感想

各記述方法での速度は早い順に
div+style => emotion => MUI sx props => MUI
という結果になりました。
意外にもdiv+styleが悪くない(というか一番早い)結果でした
また、MUIのBoxのpropsはsx propsと性能が変わらないようです(これもびっくり)

noop (baseline) React components FlexBoxDiv FlexBoxEmotion FlexBoxMui FlexBoxMuiSx
04.22 ±00.16ms 34.42 ±00.36ms 293 ±4% 308 ±4% 404 ±7% 403 ±6%

おわりに

MUIになって色々な記述方法ができるようになり、どれが一番良いのか悩ましかったため、今回のような比較を実施しました。
MUI固有のコンポーネントを使わない限りはemotionでstyleを記載するのが後々も使いまわせて、CSS-in-JSのメリットも活かせる、ということになりそうです。
また、Box(div)程度のワンタイムカスタマイズではthemeを使わない限りはstyleを利用したほうがよい、というのが新たな知見だったかなと思います。
今後はより複雑なMUIのコンポーネントのカスタマイズについても扱いたいと思います。

おまけ:benchmarkの編集

新しくベンチマークをとりたいコンポーネントを追加する場合は

  1. scenarios以下にフォルダを追加
  2. scripts/benchmark.jsの150行目あたりcasesに追記

することで編集ができます。
また、計測の繰り返し回数は100行目あたりのrunMeasures内、forループの回数を変更すれば変更できます。

Discussion