nathanielhall / react-performance

Notes taken while researching "React Performance" along with a few code examples

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

React Performance Notes

Below are notes taken while researching 'React Performance' over the past quarter. All resources used are posted at the bottom of the page. This includes many articles, videos, and tutorials.

Topics

Overview

  • Performance optimizations have a cost and are intended for special cases only

Code Splitting

  • Code Splitting is a feature which can create multiple bundles that can be dynamically loaded at runtime.

  • Code-splitting your app can help you “lazy-load” just the things that are currently needed by the user, and reducing the amount of code needed during the initial load.

import('/some-module.js').then(
  (module) => {
    // do stuff with the module's exports
  },
  (error) => {
    // there was some error loading the module...
  }
)
  • React has built-in support for loading modules as React components.
const LineChart = React.lazy(() => import('./line-chart'))

function App() {
 return (
   <div>
     <React.Suspense fallback={<div>loading...</div>}>
       <LineChart />
     </React.Suspense>
   </div>
 )
  • Tools to help determine need/benefit of code splitting

    • Dev tools "Coverage" feature

    • Webpack Bundle Analyzer

    • Webpack Prefetching/Preloading Modules

      • prefetch: resource is probably needed for some navigation in the future
      • preload: resource will also be needed during the current navigation
      • Magic Comments
import(/* webpackPrefetch: true */ './path/to/LoginModal.js')
<link rel="prefetch" href="login-modal-chunk.js">

Avoiding Unnecessary Renders

  • Very often, problems are related to unnecessary rendering

  • React does not control renders by default

  • Areas where re-renders could matter

    • Rendering charts
    • Parent components that control many children
  • How to determine unnecessary renders

  • How to control renders

    • Pure Component

      • when prop are not changing fequently
      • when component isn't very complex

      Example

      export class ClassComponent extends React.PureComponent {
        // code here
      }
      
      export const FunctionalComponent = React.memo((props) => {
        // code here
      })
    • Must watch how props are being passed (follow best practices)

      • example that breaks reference equality
      export const Panel = ({ width, onPanelClose, ...props }) => (
        <CustomChart
          title="something"
          chartStyle={{ color: 'red', width }}
          onClose={() => {
            console.log('closing')
            onPanelClose()
          }}
        />
      )

      A classical solution?

      export class Panel extends React.PureComponent {
        constructor(props) {
          super(props)
          this.state = {
            chartStyle: { color: 'red', width: props.width }
          }
        }
      
        componentDidUpdate(prevProps) {
          const { width } = this.props
          if (width !== prevProps.width)
            this.setState({ chartWidth: { color: 'red', width } })
        }
      
        closePanel = () => {
          console.log('close this')
          this.props.onPanelClose()
        }
      
        render() {
          return (
            <CustomChart
              title="something"
              chartStyle={this.state.chartStyle}
              onClose={this.closePanel}
            />
          )
        }
      }

      Using hooks

      export const Panel = React.memo(({ width, onPanelClose, ...props }) => {
        const chartStyle = useMemo(() => ({ color: 'red', width }), [width])
      
        const closePanel = useCallback(() => {
          console.log('close this')
          onPanelClose()
        }, [onPanelClose])
      
        return (
          <CustomChart
            title="something"
            chartStyle={childStyle}
            onClose={closePanel}
          />
        )
      })

      Redux Example (breaking reference equality)

      const unfriend = (name) => ({ type: 'UNFRIEND', name })
      
      const mapStateToProps = (state, ownProps) => {
        const { friendNames } = ownProps
        const friends = friendNames.map((name) => state.user[name])
        const numFriendRequests = state.friendRequests.length
        return {
          friends,
          numFriendRequests
        }
      }
      
      const mapDispatchToProps = (dispatch) => ({
        unfriend: (name) => dispatch(unfriend(name))
      })
      
      export const FriendsComponent = connect(
        mapStateToProps,
        mapDispatchToProps
      )(FriendList)

      Redux Example using memoization

      const mapStateToProps = (state, ownProps) => {
        const { friendNames } = ownProps
        const friends = memoize(
          () => friendNames.map((name) => state.user[name]),
          [friendNames, state.user]
        )
        const numFriendRequests = state.friendRequests.length
      
        return {
          friends,
          numFriendRequests
        }
      }
      
      const mapDispatchToProps = { unfriend }
  • Prop Filtering

    • restrict props passed to the component so it doesn't update because of a prop it doesn't use

    example

    const PropFilteredFriendList = ({ friends, friendNames, ...props }) => (
      <FriendList friends={friends} />
    )

Slow Renders

Fix the slow render before you fix the re-render

  • useMemo

    • Calculations performed within render will be performed every render, regardless of change. The same goes for functional component.

    • Hook that memorizes the output of a function

    • This is different from useEffect in that useEffect is intended for side effects, while functions in useMemo are supposed to be pure w/o side effects

      const CustomChart = (x, y) => {
        const data = processData(x, y)
        return <Chart data={data} />
      }
      const CustomChart = (x, y) => {
        const data = React.useMemo(() => processData(x, y), [x, y])
        return <Chart data={data} />
      }
  • Web Workers

    • Javascript is single-threaded. This means that any javascript environment will not run multiple lines of javascript in the same process simultaneously.
    • App runs on the main thread, web workers runs on separate thread
    • Issues: serialization costs with communication
  • WASM (web assembly)

    • open standard that defines a portable binary-code format for executable programs, and a corresponding textual assembly language, as well as interfaces for facilitating interactions between such programs and their host environment
    • Compilation target for languages such as C,C++, rust

Long Lists

React does a great job at batching DOM updates and only updating what needs to be changed. However, if you need to make HUGE updates to the DOM there isn't much React can do to help.

List "virtualization" focuses on just rendering items visible to the user. This works by maintaining a window and moving that window around your list.

  • Tradeoffs
    • Not searchable with ctrl-F
    • accessibility challenges
    • heavy lists can sometimes show flashing or artifacts if rendering can't keep up

Fixing death by a thousand cuts

Death by a thousand cuts means so many components are updated when state changes that it becomes a performance bottleneck.

Fixing "perf death by a thousand cuts" by colocating state

Example

export const Application = () => {
  const [dog, setDog] = React.useState('')
  const [time, setTime] = React.useState(200)
  return (
    <div>
      <DogName time={time} dog={dog} onChange={setDog} />
      <SlowComponent time={time} onChange={setTime} />
    </div>
  )
}

State Colocation will make your React app faster

Moving the state as close to the component using it works in the example above. However, there may be times where we need to lift the state to make it accessible by multiple components. What can be done to improve performance in this scenario?

Separating the state:

Optimizing Redux

Optimizing Context

  • Potentially use React.useMemo to memoize the context value
  • Separate the contexts (state in one context provider, and dispatch function in another context provider)

Tools for Measuring Performance

Definitions

  • React lifecycle
    • The “render” phase: create React elements React.createElement
    • The “reconciliation” phase: compare previous elements with the new ones
    • The “commit” phase: update the DOM (if needed).
  • Pure Component
    • renders the same output for the same state and props
  • Memoization
    • optimization technique that stores the results of expensive function calls and returns the cached reuslt when the same inputs occur again
  • Shallow comparison
    • a shallow comparison will check that primitives have the same value (eg, 1 equals 1 or that true equals true) and that the references are the same between more complex javascript values like objects and arrays.

Resources

About

Notes taken while researching "React Performance" along with a few code examples


Languages

Language:JavaScript 93.3%Language:HTML 6.7%