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

  1. Visit http://localhost:3000
  2. Click "Code Splitting"
  3. Dev Tools ➡ Performance ➡ Network: Slow 3G
  4. Open src/exercises/01/index.js
  5. 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

  1. "Render" phase

  2. "Reconciliation" phase

  3. "Commit" phase

What triggers a component to (re)render?

  1. State change

  2. New props passed

  3. Subscribed context updates

  4. 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

  1. Visit http://localhost:3000
  2. Click "useMemo"
  3. Disable Slow 3G
  4. Dev Tools ➡ Performance ➡ CPU: 6X Slowdown
  5. Click "Force Rerender" 🤭
  6. Discover the slow code by recording the performance in Chrome (hint: use Bottom-Up view)
  7. Open src/exercises/02/index.js
  8. 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

  1. Visit http://localhost:3000
  2. Click "React.memo"
  3. Fill out the first name, last name, and description. Click update.
  4. Use the React Profiler click Record.
  5. Click Force Rerender
  6. Open src/exercises/03/index.js
  7. 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

  • 426