My team uses React MaterialUI library. To provide consistent UI Pattern and make it easy for us to customise MaterialUI's component, we wrap each MaterialUI's component in our own component. For example:
const style = {} // our project custom style for ListItemText
const OurListItemText = ({primary, secondary, classes}: Props) => (
<MuiListItemText
primary={primary}
secondary={secondary}
className={classes.text}
/>
) // we only expose primary and secondary props of the original MuiListItemText.
// Team members are blocked from customising other MUIListItemText's props
export default withStyles(styles)(OurListItemText)
MuiListItemText
is the original MaterialUI's component, while OurListItemText
is our wrapper component. In our project, only OurListItemText
is allowed to be used.
As the snippet above, OurListItemText
does nothing but forward the props to MuiListItemText
. However, this affects the performance quite a lot:
ListItemText
bar on the top is from OurListItemText
while the one below is from MuiListItemText
. If we use MuiListItemText
directly, it could be ~50% faster (we have tried), which is noticeable when we have 100 ListItemText
. Removing withStyles
HOC improves a bit, but not significantly.
ListItemText
is only one example, we have similar performance issue on other wrapped components. (2 Typography
in the graph above is another pair of our-wrapper-component and MUI's-original-component)
How to improve the performance of those simple props-forwarding-components?
To wrap one component into another with React, we add the children prop into the parent component. We create the Wrapper component that accepts the children prop. And we put children in a div. Next, we create the Child component that takes the name prop and renders some text.
1. Memoization using useMemo() and UseCallback() Hooks. Memoization enables your code to re-render components only if there's a change in the props. With this technique, developers can avoid unnecessary renderings and reduce the computational load in applications.
Elements are the smallest building blocks of React apps. An element describes what you want to see on the screen: const element = <h1>Hello, world</h1>; Unlike browser DOM elements, React elements are plain objects, and are cheap to create.
List virtualization, or windowing, is a technique to improve performance when rendering a long list of data. This technique only renders a small subset of rows at any given time and can dramatically reduce the time it takes to re-render the components, as well as the number of DOM nodes created.
wrapping a React component adds one extra level of full React lifecycle (i.e. the wrapper needs to be mounted). Is it possible to avoid this?
You can avoid the lifecycles by avoiding JSX and calling the functions directly instead.
Eg.
{Component({ data: 1, children: 'Hello' })}
instead of
<Component data={1}>Hello</Component>
This blog post claimed to achieve 45% speed improvement with their test case.
This syntax might not be as readable/understandable though.
Some quote from Dan Abramov regarding this issue:
We're looking at optimizations like this in the context of Prepack but there's nothing that's going to be immediately useable in the next couple of months. Within a year or two we might have something.
Note that unless you're creating thousands of elements, the performance difference won't be noticeable. Also unless your components are very flat and simple, the "pure win" from this optimization will likely be much less relevant in practice.
I wouldn't wait for Prepack to do the optimisation though since the timeline is uncertain and the resulting optimisation might not be identical to this.
As for the significance of the performance improvement, it'd depend on your project and the only way to be certain is to try out and see the improvement for yourself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With