React Performance
git clone https://github.com/ericmasiello/react-performance-talk
cd react-performance-talk
npm i
npm start
Running the code
What we’ll cover
- How to run less code
- How to run code less often
- How to measure your changes
Run less code
Render as fast as possible by running the least code as possible
Code Splitting ✂️
// Dynamic import
import('/some-module.js').then(
module => {
// do stuff with the module's exports
},
error => {
// there was some error loading the module...
},
)
// Static import
import SomeModule from '/some-module.js';
React.lazy()
// smiley-face.js
import React from 'react'
function SmileyFace() {
return <div>😃</div>
}
export default SmileyFace
// ------------------------------------------
// app.js
import React from 'react'
const SmileyFace = React.lazy(() => import('./smiley-face'))
function App() {
return (
<div>
<React.Suspense fallback={<div>loading...</div>}>
<SmileyFace />
</React.Suspense>
</div>
)
}
Exercise 1
- Visit http://localhost:3000
- Click "Code Splitting"
- Dev Tools ➡ Performance ➡ Network: Slow 3G
- Open src/exercises/01/index.js
- Code split MomentBoy
Extra Credit:
Figure out how to precache MomentBoy before the user clicks on "Show"
Run the least amount of code as possible
- Code split on parts of your codebase that are not essential to the next state
- Common split points:
- Routes
- "Expensive" code that is executed upon user interaction (e.g. a click)
- Do not blindly code split everything
Lifecycle of a React app
-
"Render" phase
-
"Reconciliation" phase
-
"Commit" phase
What triggers a component to (re)render?
-
State change
-
New props passed
-
Subscribed context updates
-
Parent component (re)renders
Always remember
-
Rerenders are not necessarily expensive
-
Rerenders do not necessarily lead to a commit
-
Measure, measure, measure!
Run code less often
Optimizing render & reconciliation
- useMemo()
- React.memo()
Hooks
- 🤗 Allow greater sharing and composability of business logic
- 😡 Calculations performed within render will be performed on every single render regardless of whether the inputs for the calculations change
function Distance({x, y}) {
const distance = calculateDistance(x, y)
return (
<div>
The distance between {x} and {y} is {distance}.
</div>
)
}
useMemo
Allows expensive functions to only re-evaluate when dependencies changes
function Distance(props) {
const {x, y} = props;
const distance = React.useMemo(
() => calculateDistance(x, y),
[x, y]
);
return (
<div>
The distance between {x} and {y} is {distance}.
</div>
);
}
Exercise 2
- Visit http://localhost:3000
- Click "useMemo"
- Disable Slow 3G
- Dev Tools ➡ Performance ➡ CPU: 6X Slowdown
- Click "Force Rerender" 🤭
- Discover the slow code by recording the performance in Chrome (hint: use Bottom-Up view)
- Open src/exercises/02/index.js
- Optimize the expensive calculation with useMemo
A note about useMemo
- useMemo doesn't make slow operations faster
- Consider using a Web Worker to move slow code into a separate non-blocking thread
useMemo with Context
Can be used to ensure a value's reference does not change
const Theme = React.createContext({});
function ThemeProvider(props) {
const [color, setColor] = useState('#333');
const [bgColor, setBgColor] = useState('#fff');
const themeValue = useMemo(() => ({
color,
setColor,
bgColor,
setBgColor,
}), [color, bgColor]);
return (
<Theme.Provider value={themeValue}>
{props.children}
</Theme.Provider>
);
}
Optimizing re-renders
- shouldComponentUpdate()
- React.PureComponent {}
- React.memo()
* Components that render {children} are difficult to optimize using the above: https://github.com/facebook/react/issues/8669
React.memo()
import React from 'react';
const Name = React.memo(
function Name(props) {
return (
<p>Your name: {props.first} {props.last}</p>
);
}
);
export default Name;
Exercise 3
- Visit http://localhost:3000
- Click "React.memo"
- Fill out the first name, last name, and description. Click update.
- Use the React Profiler click Record.
- Click Force Rerender
- Open src/exercises/03/index.js
- Optimize the Name and Description components
Remember
- Do not mindlessly wrap all components in React.memo()
- In some cases can make app slower
- Verify there's an actual performance benefit
- Always weigh the complexity of these bailout methods relative to their performance benefit
- Use React Devtools to profile (utilize CPU 6x slowdown)
Notes on React DevTools Profiler
- Gray with Slashes: the component and all its children did not rerender
- Gray: component did not rerender but some of its children did
- Blue ➡ Blue/Green: relatively fast render
- Yellow ➡ Orange: getting slow
Other optimization opportunities
- Separate React contexts by use case
- Utilize web workers for slow processes
- Utilize windowing techniques to avoid unnecessary React commits (see React Window)
Summary
- Utilize React Profiler to assess relative performance of components
- Utilize Chrome's audit tool to measure more granular performance
- React runs slower in development than in production mode
- Use the "6x slowdown" and "Slow 3G" options when evaluating app performance
Thanks 👏
React Performance
By Eric Masiello
React Performance
- 585