3. React Responsively, Render Responsibly Yoav Niran
A little about me
Yoav Niran
Senior front-end developer @ Cloudinary
The Media Full Stack
Heavy-duty image & video platform
@poeticGeek
10. React Responsively, Render Responsibly Yoav Niran
The road to performance
1. Data go low
2. Time to leave the nest
3. Spread the love props
4. Just Memoize It TM
5. Open a Window
17. React Responsively, Render Responsibly Yoav Niran
The Journey
1. Data go low
2. Time to leave the nest
3. Spread the love props
4. Just Memoize It TM
5. Open a Window
23. React Responsively, Render Responsibly Yoav Niran
The Journey
1. Data go low
2. Time to leave the nest
3. Spread the love props
4. Just Memoize It TM
5. Open a Window
30. React Responsively, Render Responsibly Yoav Niran
Memoization
“...memoization or memoisation is an optimization technique used
primarily to speed up computer programs by storing the results of
expensive function calls and returning the cached result when the same
inputs occur again…”
https://en.wikipedia.org/wiki/Memoization
Memoization
32. React Responsively, Render Responsibly Yoav Niran
The Journey
1. Data go low
2. Time to leave the nest
3. Spread the love props
4. Just Memoize It TM
5. Open a Window
40. React Responsively, Render Responsibly Yoav Niran
The Journey
1. Data go low
2. Time to leave the nest
3. Spread the love props
4. Just Memoize It TM
5. Open a Window
41. React Responsively, Render Responsibly Yoav Niran
Clue #5 – Letting it all out
class PhotosGrid extends Component {
render() {
...
return (
<div className={cx(styles.container, "...")}>
...
{photos.map((id) => (
<PhotoItem
key={id}
id={id}/>
))}
</div>
);}}
...
PhotosGrid/PhotosGrid.js
42. React Responsively, Render Responsibly Yoav Niran
Fix #5 – Open a window
https://bvaughn.github.io/forward-js-2017
43. React Responsively, Render Responsibly Yoav Niran
Fix #5 – Open a window
https://react-window.now.sh
https://github.com/bvaughn/react-window
49. React Responsively, Render Responsibly Yoav Niran
The road to performance
1. Data go low
2. Time to leave the nest
3. Spread the love props
4. Just Memoize It TM
5. Open a Window
51. React Responsively, Render Responsibly Yoav Niran
Thank y u
https://github.com/yoavniran/react-performance-demo
@poeticGeek
Hinweis der Redaktion
Who here thinks about performance throughout their dev process. And not just when things start to go wrong?
Performance is a tricky businessWe certainly dont want to over-do it but usually we dont even think about it until its really bad and users start to feel the pain
How many here had to re-work their app after serious performance issues were discovered? It isnt pleasant right?
For the purpose of this talk I built this small app that lets me view photos from my Cloudinary account.
At cloudinary we put a big emphasis on delivering high quality media with efficiency and performance. Its a big reason our customers rely on us to deliver media to their web and mobile apps.
As a consequence we take performance very seriously in the apps we build ourselves
In the next 14 minutes or so Id like to show how its possible to quickly get a sense of what is causing slowdowns in your react app and what can be done to fix it.
The app is roughly made out of this component structure
we’ll focus on the PhotosGrid and PhotoItems which are going to prove as performance bottlenecks
Live demo - Show the app and the performance issues
Obviously this is a problem. Selecting one item shouldnt cause this many components to re-render each time. We already see a considerable slowdown with only several hundreds items in the dom. Consider what will happen if we had thousands.
Our click handler is now taking several hundreds of milliseconds to complete. And this is on a strong machine.We really want to be down to just a few tenth of milliseconds at the most or we start to interfere with the browser’s ability to respond quickly to events while also doing its job of running callbacks, doing repaints, reflow, etc. Ideally we would like to keep our interactions at below 16 ms to hit that 60 fps sweetspot.
So this is where we are now and this is where we should end up with
the 5 areas well concentrate on In order to get to our goal
Quick word about how the app’s state is structured.
I have a photos array and in it each object represents a photo item.
In my photos grid component im now getting the entire photos collection
Then im iterating over it and passing the data down to the photo item
But I don’t need all this data at the grid level. So lets refactor so we’re only getting the ids instead
Now we just pass the id to the photo item component and let it do its own selection
This way were moving the data use down the component tree
And in my photo item I added a selector that uses the id it gets to retrieve the photo’s data
Moving the data selection to the photo item instead of the photos grid
The photos grid is still re-rendering and so are all the Photo items
Simply moving the selection of the photo items from our PhotosGrid component wasnt enough.
Notice that the selected flag is stored inside, nested in the photos array
Im using an Immutable data structure
every selection action is updating the nested object
immutable data means its going to create a new object each time.
The issue is of course that changing our state like this can cause every component that relies on this data structure to re-render if were not careful
Separating out the selected state to its own array should ensure that the grid doesn’t simply re-render every time a selection is made
Ok. we’re making progress - PhotosGrid isnt re-rendering. Thats good
But why are all the photo items still re-rendering? This isnt supposed to happen right?
We must remember that both React’s PureComponent and by default Connect from react-redux do a shallow compare on the props sent to our component.
This means that its doing a strict equal on only the first level of the props object.
Our selector currently creates a new prop called “Item” every time our photos array in the app’s state changes and the strict equality check will find its not equal to the previous update
We fix this problem by spreading the Item’s properties into the props we send down to our component.
Now whether its PureComponent or Connect that does the comparison they should find that for all other components nothing changed except for the one photo we selected.
Demo spreading the props in the photo item
Congratulations. Great job! We did it. Or did we?
We got rid of all that unnecessary rendering but i have a sense something isnt completely right yet. Call it a hunch.
Lets use the dev tools performance tab again to take a look at what is going on after we click an item
There are several good memoization libraries out there that can help us do this right. However I prefer Reselect because it does this by default while supporting more advanced usage such as selector composition.
We use Reselect’s createSelector method which accepts other selectors as the building blocks for our selector. In Reselect every selector is memoized so in case the input it receives is the same as before, that function will not be called again and its previous results will be used.
Notice that I create a new selector for every PhotoItem component
Since React is going to render multiple PhotoItem component instances.
Each of those will need its own memoized selector for the caching to work
Demo memoization in place
I promised that we’ll get down to around 16ms and were still several times slower than that
however We’ve done all we can for the photo item component
there is another area we need to look at in order to improve our performance and thats the grid itself
lets take a look at what happens when we go from our photo view back to the grid
what we can see here is that we're naively rendering the entire photo collection whether we have just a few or thousands.
All of them will be rendered to the dom at once!
We need to take a windowing (virtualize) approach which means well only render the items that the user sees
And a few before and after to support a smooth scrolling experience
This prevents us from rendering the entire collection to the dom
Instead of rendering the entire collection we use the fixed sized grid from react-window to render the items
Theres a little more coding involved but its well worth it
Lastly - Lets take a look at our photo select handler once the windowing technique in place.
Lastly - Lets take a look at our photo select handler once the windowing technique in place.
Lets review -
We moved selecting data down the tree
We’ve un-nested our data structure
We’ve spread our props to help shallow compare
We memoized our selector
And we’ve added a window
We did it ! We’re down from around 600 ms to only 10 !!!!!!!