Reputation: 21436
As per MUI's own doco, and this answer - components using sx
render significantly slower than components using other styling mechanisms.
On the surface, it looks like sx
is just an alternate convenience API for doing the same thing - so I wouldn't expect it to have such a different performance profile.
My question is: Why is the rendering of a component using sx
so much slower - what's it doing so differently? Is it a whole different styling engine or something?
I'm curious about the possibility of optimising it, or coming up with a compromise that retains most of the usability but omits whatever feature is causing the slowdown.
Please note, this question is about "why is the performance so different" - not "why do you think the difference doesn't matter".
Upvotes: 17
Views: 6575
Reputation: 80986
As I started to dig into this, I realized that I needed to measure the performance of different scenarios in order to have any confidence in my understanding of the performance aspects of the sx
prop.
The performance information in the MUI documentation was gathered using benchmarks that can be found here: https://github.com/mui/material-ui/tree/v5.13.2/benchmark/browser.
I believe an earlier version of those benchmarks was based on some variation of this repository: https://github.com/mnajdova/react-native-web. The react-native-web repo was used as a starting point because of its "benchmarks" package which contains a useful framework for measuring the performance of different React element rendering/styling approaches.
I created my own version here: https://github.com/ryancogswell/mui-style-benchmarks. You can use this as a starting point to dig into this further. Below are the measurements I made and my conclusions.
This test renders 639 elements with approximately 17 CSS properties each except for the cases ("..._minimal", "..._medium") which reduce the number of CSS properties to show the performance impact.
Styling Implementation | Time in ms | Implementation Desc |
---|---|---|
inline-styles | 22.78 | No styling engine, just use style prop |
mui_sx_full | 36.89 | MUI Box sx prop with 17 CSS properties |
mui_sx_medium | 24.09 | MUI Box sx prop with 9 CSS properties |
mui_sx_minimal | 18.15 | MUI Box sx prop with 4 CSS properties |
mui_styled_box | 22.38 | MUI styled MUI Box with 17 CSS properties |
mui_styled_box_minimal | 17.90 | MUI styled MUI Box with 4 CSS properties |
tss_react_makestyles | 17.10 | makeStyles from tss-react with 17 CSS properties |
mui_styled | 16.93 | MUI styled div with 17 CSS properties |
mui_styled_minimal | 13.77 | MUI styled div with 4 CSS properties |
emotion_styled | 16.69 | Emotion styled div with 17 CSS properties |
emotion_styled_minimal | 12.76 | Emotion styled div with 4 CSS properties |
emotion_css | 12.58 | Emotion css div with 17 CSS properties |
styled
(e.g. import {styled} from '@mui/material/styles'
) only adds a small amount of overhead
to Emotion's styled
.styled
.styled
, Emotion css
, MUI styled
, and the MUI sx
prop are all more expensive when there are
more CSS properties passed to the styling engine.sx
prop degrades more quickly than the styled
API as more
CSS properties are passed to it. With 17 CSS properties the performance is much worse than the styled
API (2x).sx
prop performs just fine for a small number (e.g. < 5) of CSS properties. Particularly, if you
are already using a MUI component in a given situation, there is no meaningful performance difference
between wrapping it with styled
or using the sx
prop if you are just using a small number
of CSS properties.sx
prop slowness?Is it a whole different styling engine or something?
It is not a different styling engine. The output of the work done for the sx
prop is fed into the styled
API of the main styling engine (e.g. Emotion or styled-components); so using the sx
prop with the Box
component is guaranteed to be slower than the equivalent styles using styled
on a div
because the sx
prop still uses styled
in the end but does additional work first.
sx
prop?styled
API calls styleFunctionSx in order to transform the CSS properties in the sx
prop to the form expected by the styling engine.styleFunctionSx
traverses all the CSS properties in the sx
propThe net effect is that for each CSS property there are a number of lookups and function calls to see if the CSS property needs to be transformed even in the cases where the value passes through without changes.
I'm curious about the possibility of optimising it, or coming up with a compromise that retains most of the usability but omits whatever feature is causing the slowdown.
I'm sure that performance improvements are possible for the sx
prop, but I don't think there is any single silver bullet for easily making it faster. Instead it will probably require a large number of little changes that are each barely measurable, but cumulatively provide decent improvement. The challenge is to make those changes without simultaneously making the code more complex and/or harder to maintain and/or more error prone.
The main compromise that "retains most of the usability" is to use Emotion's css prop directly. It can be used directly on elements in a similar fashion as the sx
prop -- you just lose the shorthand notations and theme lookups that the sx
prop provides. The theme lookups (e.g. for colors or spacing units) are easy to get directly from the theme by using the useTheme hook in the component. The theme.breakpoints API can be used instead of the breakpoint shorthands; though the sx
breakpoint features are much nicer from a DX standpoint.
My personal approach is to use tss-react
for most styles (this allowed for straightforward migration from the JSS-based makeStyles/useStyles
while moving from v4 to v5) and then to use the sx
prop for some one-off styles on components that only appear a small number of times on a given page.
Upvotes: 47