MUIのコンポーネント装飾について速度比較する Box編
はじめに
MUIのコンポーネントカスタマイズについては、基礎システムが多機能なCSS-in-JS ライブラリであるemotionを使っていることもあり、色々な方法があります。
本家でもいくつかの方法が提案されており、 パフォーマンスについても比較を掲示してくれています。こちらのパフォーマンス比較、コードも公開されていたので、
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
下記のようなコンポーネントです。
超シンプル。
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()でカスタムできます
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になります。
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で装飾
こちらはデフォルトには無いパターンなので追記してためしてみました。
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で装飾
objectでbackgroundColorやborderStyleのmediaqueryを記述しています。
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で装飾
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で装飾
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で装飾
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で装飾
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の編集
新しくベンチマークをとりたいコンポーネントを追加する場合は
- scenarios以下にフォルダを追加
- scripts/benchmark.jsの150行目あたりcasesに追記
することで編集ができます。
また、計測の繰り返し回数は100行目あたりのrunMeasures内、forループの回数を変更すれば変更できます。
Discussion