Transcript
This transcript was autogenerated. To make changes, submit a PR.
You hi Conf 42,
thanks for attending my talk. React is killing your performance
and it's your fault. So a little bit
about me I've been a
developer professionally for the last ten years
or so, and in
the last year I've been a co founder of
a company called Lifecycle IO, and we
create live, ephemeral environments directly
from code. So for every code change, every commit, in every branch,
we create automatically a new environment. And the purpose of
those environments is to allow stakeholders who
are not developers, such as designers or
product managers, to have a way to
interact and experience with the product at a very early
stage, before any deployment to any
existing environment like staging or production.
So those stakeholders will have a
way to give feedback and experience the
product at a very early stage like we as developers
are used to, since we usually have a local
environment running on our computer when we develop.
So the same kind of experience just for other stakeholders.
So please check us out at lifecycle IO and
today we're going to talk about performance
in reacts. So let's start by reminding
ourselves why do we use react?
And I just want to say that I'm
going to address mostly examples
of browser like the DOM,
the document object model. But the same principles
can be applied into every implementation of reacts like React native.
So react is used to
basically write complex web applications in a declarative
way, right? So instead of having
explicitly and imperatively
use the DOM as an APIs and
give comments to it, we can describe in a declarative way
complex UI elements. For example, this element
here is changing every time
I click the button. So in
order to do that in regular
JavaScript, this is actually typescript here,
we need to very explicitly,
in an imperative way,
call the DOM API like document create element
and tell it what to do.
And instead of that we can use react
in a declarative way, and by declarative meaning
that we described the outcome we want to use.
So we actually use JSX and
those tags here and
use react state management solution,
and we change this element according to the state,
which is changed by the button click.
So this is a very nice way of writing code, and we all love
it. So that's why react is very, very popular.
And unfortunately that
comes with a price, because to enjoy
this declarative way of programming,
reacts has to do some work behind the scene at
runtime. What react does it make sure
that what we described in
a declarative way, meaning what we
told him the outcome should be, it makes sure it actually happens.
And to do that, it comparison between
the given state of the Dom and the
description we provided it.
So the actual comparison runs at
runtime. So it has an overhead.
And let's have
a very quick and very simplified overview
of that algorithm that runs every time the
app gets updated. Just so
we have a sense of what it's doing. But the important thing
to take from it is that it doesn't
come free, right?
It's doing some work. And even if this work is very
efficient and it's written in the best way possible,
it exists, right? So it can
sometimes hurt us
if we don't know how it works.
So this is a representation of the
tree. So a react application is basically a tree of elements,
right? So every element can has its children,
and each of those children can have their children.
So basically it's a tree data structure.
Every element has a parent. So this
here is the drawing of the current react
tree, right? So this can be like the app
component, and maybe here is a left pane
and this is the right pane, those are buttons or something.
Every react application can be described in that way.
So let's say an update happened. So when
we say an update happened in a react application, we mean
that either one of the components
prop has changed or its internal state
has changed. So that triggers a render
and that render function goes across and creates a new
tree. And this is
our new tree. And we can see that the new tree does
not have that c element here, and that element value
has changed, right? So react what it does, it takes the
previous tree and this current tree and it computes the
difference, the diff, right? So the magic
is that it only takes the diff and then
from it it creates the necessary DOM
updates, as we saw in the explicit
code example. Right? So it
produces only the necessary DOM
API calls. So this is great,
but it actually needs to run
that tree comparison algorithm every time something changes.
And as we said, it creates an overhead and it's better to
avoid it if we can.
So we don't have performance
issues, right? So let's talk
about applications,
and let's start by saying that
I things that generally only optimize
when you need to. So I
think that programmers that are using
React should basically know what's going on behind the scene, but we shouldn't
really optimize every single
thing ahead of time.
The better approach is to maybe keep in mind that
those stuff happens, but only optimize it when they cause a problem.
Because sometimes optimizing stuff
can actually hurt you. It can create bugs and can create a
complex code and it can, even if they are not done
right, decrease the performance,
which is not good. Obviously it's the opposite of what we want.
And also another thing is that
when trying to optimize a front end application,
whether it's in the dom or mobile app
or any other things, usually the most expensive
stuff in terms of what I experienced
is stuff that having to do with DOm changes.
So it's not like react's fault,
it's some other things
that we do that causes the performance degradation.
So we might want to make sure that we
eliminated the other causes of
the performance degradation before we start optimizing reacts.
And another tip
here is using the devtools to
investigate. So react has a very good extension,
reacts devtools, which you can add to your browser
and use that to investigate. And we'll see an example for
it later on.
So first thing we
might want to check when we come across
a poorly performant component is checking
whether we're running it in dev mode. When we run
react apps, we usually do it in
development time using a local development
server. And that development server,
it actually does a lot of stuff behind the scene,
like for example to provide us warnings and
so on. And that stuff has
performance impact.
So when we build the application with a production
flag, it's way more performant. So this is an example here. I created this
application, this component, and what it does,
it only creates some random
circles with the random colors and random position on
the screen. And it does that every frame. So I
don't know if you can see it, but it's not running very smoothly. It's not
60 FPS. So we can actually check how
smooth it actually is by going over here to
the devtools. And I click control
P and then FPS.
And I have an FPS meter. So yeah, it's not even
close to 60, it's 11.4, right.
And actually this is entire presentation, it runs
in a dev server, right? So this is
a development server using
webpack, using create react app.
So also I build things app
ahead of time and serve it using just
a very simple HTTP server. So if I
go to this exact URL,
but in the port of, I think it was 80
80, I get the same thing,
but built for production. So I can already
see that it's way faster. But let's be
scientific about it. And we can see it's like
almost twice as fast,
maybe not twice, but sometimes it's close to twice,
maybe 50%, but between 50 and 100% more
frames per second, which is a lot.
And it's actually very noticeable,
at least in my screen. So first
thing I recommend doing, because I always forget
that, is checking whether you're running
in dev mode.
Another thing which is a very
common way of optimizing things in react, is using react
memo.
1. Second, let me get back to the
slide I've been at. Right,
so react memo is
a way to tell react not
to under stuff. And how does that work?
If a component is pure,
and by a pure component, we mean that given
the same state and props, it will produce the
exact same thing. So given
that what
react does, if we encapsulate
that component using a thing called
react memo, which reacts is giving us,
we can avoid renders.
Because what it does
is every time it gets rendered, it compares the
props currently and the previous props.
And if those has not been changed,
then it just doesn't trigger a render. Because since
it's a pure component, we know that only when the
props has changed, then it should be rerendered.
So if it's not
encapsulated using reacts memo, it will rerender it,
even though props are maybe the
same.
So this is an example here. This is a component that
it can be dragged, right? I can drag it with my
mouse and it's very slow.
And let's see why. If I go back
to the code here, this is
the component. And we
see that we have this wrapper component,
this div here, which is the things that's being dragged.
And inside there's things. Long triggering component
with the text. And the slow rendering, as its name
suggests, is very slow.
I just did an artificial way of
making it slow just by doing nothing for a certain amount of
time. But it's just for the sake of demonstration.
Imagine that you're like computing something or
getting stuff on the network or doing I o stuff during
the render function.
But the thing is that this component is
actually pure, right? Because this
output only changes by this prop, the change
the text prop. So it
only changes if the prop, the text prop
is changed. So we can actually use reacts memo.
So let's do it over here. React memo.
And then parentheses save
it. Hopefully it broke
refresh, right? So as
you can see, it's very smooth. Very smooth.
We can actually check that again using the FPS. Better.
Let's do it real quick.
And it refreshed.
Now what? Never mind.
But we can just see it in the presentation.
If I drag it right now, it's very smooth. If I take out the react
memo, it should be. Let's update,
let's refresh. It should be. Yeah, it's way slower because
again, react memo compares this
text. And since we
update the wrapper every time we drag, which is basically 60
times per seconds, every time I drag my mouth around the
screen, it updates this one because it's a child.
But this prop never changes. So it's
a waste to rerender this entire thing.
Great, so let's move on.
Another thing we can do is not mutating
prop values. So the
comparison done by react memo is shallow,
meaning that it
compares object by reference, right? So if you have
a prop called o, which is an
object, it compares its reference, it doesn't compare its internal
values. So if
you mutate internal values,
it wouldn't help you. Using react memo, it will actually cause
bugs in your uI.
So if you're using react
memo, you should not change
the internal values. So this is an example here.
So size is actually an object, right? It has
the height and width values.
And in this case,
since I wrapped it in rec memo, it will actually cause a bug
because it doesn't compare the
internal values of the size, only the reference of it.
So in order to fix that,
we create a new object. Every time we change the props,
we use the spread operator, we create a new object. And now when
it compares the prop, it compares it by
reference. Since it's a new object, it will
trigger a rerender and will fix the bug.
So react memo is great and it helps in a
lot of cases, but it's not always the best thing to do.
We can sometimes do better than that because
in some cases prop are always
different. There are components
like components that uses children, for example,
that don't matter what you do, the children will
always under because the
children are being created every
time by react as a new element.
And in that case,
the comparison function will always fail, which actually will
damage the performance a little bit because it adds
an extra layer of checking the props.
You can avoid that in that case by making sure
that this component is
memoized. You can use memo if
you want, but if you're doing it like that, then keep in
mind that children are always new objects.
So using react
memo wouldn't actually help in this case.
For example,
this component here, whenever I
click the button, it set this discount, right?
And discount.
This is a component that accept
children, right? So if I click
this button, I see that it renders every time,
even though I wrap the entire thing in your act memo.
So a better thing that we
can do sometimes is wrapping decoupling
from children instead of using memo.
So finding which component needs to
be memoized and wrap it with react memo. It can be annoying.
Sometimes you need to add
a little bit of boilerplate and you need to mess with it.
And sometimes the flow of the code is
not right. Instead of that
we can actually decouple the
child from its parents. So in this case,
let's see an example.
This is the draggable component
we saw before. We can use
the children property over here
and instead
of doing it
inside, because this slow component is
not directly coupled to this draggable, this can be a generic
draggable component. We can use it for every
kind of draggable thing we want. We can actually plug in
here every kind of component we would like.
So you shouldn't limit it only to the slow rendering component,
right. So it will also,
by generalizing it will solve the performance issue
since this wouldn't
trigger a rerender for the slow triggering surrendering
component every time we run it, because the
children is passed to it as a prop, right.
So for example, I can create const
slow rendering draggable
and we can take the
draggable here and as a child
we can provide it with a slow running component.
This will solve the
performant issue.
The reason that I think it's not compiling
is because it's a forward ref component,
but it doesn't matter, I need to provide it with the ref. Right,
like this. But the concept is the same.
We just decouple this
component from its inner child.
So whenever this is being rerendered
it doesn't affect this one.
Another thing we should keep in mind is
we should avoid passing state around.
So let's see an example
here. This is a component that changes.
Actually I think this is.
Yeah,
slow rendering. Yeah,
I want to unoptimize it for a second so we can see
that things example should be slower.
Right, let's go back to that slide.
Here we go. So when I click this button,
it renders this drag me component
because of the way I programming this component.
So let's see how it works.
Yeah, here we go.
It's actually already optimized,
so let me stop for a second. The recording.
So another example here is avoiding
passing state around. So the idea is that
if component is
not actually using a state that is being provided
by a parent component,
we should avoid passing it around since
each change of the state triggers a new render.
So let me demonstrate what I mean in
this case. There is a button here
that changes color. It changes this text color
randomly every time I press the button. And we can see it's very slow.
And the reason it's slow is because I plugged in this component, which is,
as you can recall, is a very slow rendered component.
So pressing that button takes
a long time until things is getting updated. So let's
see what's going on in this code.
Every time I click the button, it sets the color to
some random values. And this is the state of
the color, right? And this component
is a child component of this entire thing.
So every time this state is being changed,
this entire thing is being rerendered, including the
slow rendering. But in fact,
the slow triggering component shouldn't be affected
by all of this, because they
don't have any connection between them. We can decouple those, we can do like
this const, let's say random
color,
and then we can take this wrap
it around, react fragment,
use the state,
right, and put it over
here.
Yeah, I forgot to return something.
Return.
And now we should
see, if I go back,
we should see that exact same
component is being rendered very fast,
because now whenever I change the state,
it's only using the rendering color component,
and the slow rendering is not being affected by the changes
of the state.
So in summary,
react is an overhead when it's
providing us with that declarative nice way of programming.
It does that by running an
algorithm at runtime, and that algorithm has some performance
impact. Optimize only when
needed, because most of the times
those optimizations,
they don't matter unless they do.
Unless you actually have something that is not working
properly in your application.
Don't forget to use the production build before you start to optimize.
Use react, memo, decouple the children from
the wrapper, and keep the state as low
as possible as we did in the last example. And that's
it. Thank you for listening,
and have a great rest of the
conference.