state
this
PureComponent
or React.memo
when you can (and, in general, make your components depend only on props and state when you can)We're going to compare these things:
in these ways:
biased by:
class SimpleClassComponent extends React.Component {
render() {
return <div>{this.props.name}</div>
}
}
class SimplePureComponent extends React.PureComponent {
render() {
return <div>{this.props.name}</div>
}
}
const SimpleFsc = ({ name }) => <div>{name}</div>
const SimpleMemo = React.memo(SimpleFsc)
Component
and PureComponent
are pretty easy to read and nearly identical (and, pre 16.8, are your only option if you want state) but FSCs are simpler in terms of being able to avoid the this
keyword and removing the class boilerplate that comes with an ugly polyfill if you're transpiling to ES5.
Okay this should sound like an obviously bad thing that you should try to avoid.
Consider this incredibly contrived example:
class Pure extends React.PureComponent {
render() {
return this.props.foo.bar
}
}
class PureWrapper extends React.Component {
constructor(props) {
super(props)
this.foo = { bar: 1 }
}
render() {
this.foo.bar += 1
return <Pure foo={this.foo} />
}
}
In this case, Pure
will not rerender because the prop foo
hasn't changed (it's the same object in memory). This example is quite ridiculous, but you can run into this with something like Redux if you mutate your global state.
To avoid this, the React documentation suggests:
React.PureComponent
'sshouldComponentUpdate()
only shallowly compares the objects.
If these contain complex data structures, it may produce false-negatives for deeper differences.
Only extendPureComponent
when you expect to have simple props and state, or useforceUpdate()
when you know deep data structures have changed.
Or, consider using immutable objects to facilitate fast comparisons of nested data.Furthermore,React.PureComponent
'sshouldComponentUpdate()
skips prop updates for the whole component subtree.
Make sure all the children components are also "pure".
Having a PureComponent
render children "inline" sidesteps any optimizations you would normally expect:
class Pure extends React.PureComponent {
render() {
return this.props.children
}
}
const PureFsc = React.memo(({ children }) => children)
class PureWrapper extends React.Component {
render() {
return (
<>
<Pure>
<SomeOtherComponent />
</Pure>
<PureFsc>
<SomeOtherComponent />
</PureFsc>
</>
)
}
}
Here, <SomeOtherComponent />
is sugar for React.createElement('SomeOtherComponent')
which returns a new object every time. So in Pure
and PureFsc
, the children
prop is changing (shallowly) every time!
To avoid this, consider caching the children or creating a new pure component that renders the component and its child.
FSCs have a minor gotcha involving rendering PureComponent
children.
If an FSC is passing a function to its PureComponent
children as props, odds are it's recreating that function every render and the pure children have no way of successfully performing a strict equality on props.
Consider the following setup:
class Pure extends React.PureComponent {
render() {
return this.props.fn()
}
}
class PureWrapper extends React.Component {
fn = () => <div>Foo</div>
render() {
return <Pure fn={this.fn} />
}
}
const PureWrapperFsc = () => {
const fn = () => <div>Foo</div>
return <Pure fn={fn} />
}
Here, PureWrapper
, on each render, is passing the same fn
to Pure
so Pure
, being a PureComponent
, will not bother calling its render()
function. PureWrapperFsc
, on the other hand, creates a new function to pass to Pure
every render and so Pure
will also render every time.
The easiest way to avoid this problem is to use a class component, but if fn
doesn't depend on prop
s, you can define it outside your component:
const fn = () => <div>Foo</div>
const PureWrapperFsc = () => <Pure fn={fn} />
All in all, FSCs are the cleanest solution for simple components but regular React Components can have less unexpected side effects.
"The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming." - Donald Knuth
Now that we have that out of the way...
The main power of PureComponent
s is that they will avoid their render()
function if they receive the same props and their internal state hasn't changed.
Let's check out these following examples.
Here, we're rendering a regular React Component:
Notice the console indicating the component's render()
function being called on each click.
Now let's see the same setup with a PureComponent
:
Notice how we don't see any logs for subsequent renders?
That's because PureComponent
s implement the shouldComponentUpdate lifecycle hook to shallowly compare their new props and state to their old props and state.
Because these haven't changed, shouldComponentUpdate()
returns false
and render()
is not called - the previous DOM markup is instantly returned.
* Note the results are the same for FSCs vs React.memo
ized FSCs.
So in general, we expect big savings when using a PureComponent
or React.memo
ized FSC and we're either rendering many components, or components whose render()
function is slow, or a deep tree of components. Let's see what happens!
We're going to follow this repo for comparing components.
We can mess with a few things:
For the following comparisons:
setState
that starts the render and then in componentDidMount. They are an average over 10 rendersAs expected, the render times for non-pure Components are nearly identical regardless of props mutation.
For PureComponent
and React.memo
, we see huge performance savings when rendering lots of complex components whose props aren't changing.
That makes sense - if render()
takes a long time and we can skip all that work, we'll see big gains.
We also see a slight performance boost when choosing functional components over class components.The only surprising situation here occurs when passing lots of props.
It takes roughly 600ms just to pass 1000 props to 1000 children, never mind shallowly compare them.
Given that, we do see a slight performance hit when using PureComponent
and React.memo
because the work of prop-equality-checking is more than the work of render()
.
PureComponent
and React.memo
when properly applied.Learn more about how The Gnar builds React applications.