React performance optimisations
by Kostiantyn Synyshyn
Front-end Developer at Levi Nine

Table of contents

Native events recap

Lazy evaluation

Actual React render process

What does React do to optimise performance?

What should we do (not do) to optimise performance of application?

ย Which one is 'correct'?

<ul>
 <li onclick="forLulz1()">It </li>
 <li onclick="forLulz2()">is</li>
 <li onclick="forLulz3()">useless</li>
</ul>
<ul onclick="forLulz()">
 <li>Not</li>
 <li>so</li>
 <li>useless</li>
</ul>

Native events & confusion

  • Capturing
  • Bubbling
  • stopPropagation()
  • preventDefault()

Event listener API

element.addEventListener('eventGoesHere', callbackGoesHere, {capture :  true | false})

// Example

submitButton.addEventListener('click', (event)=> console.log(event), true)

Example

<html>
  <body>
    <div id="first">
      <div id="second">
        <div id="third">I am event target</div>
      </div>
    </div>
  </body>
</html>

document.getElementById('third').addEventListener(
  'click',
  (e)=> {
    console.log('third was clicked');
  },
  true,
);

Missconception

propagation - is mechanism

capturing, bubbling - are phases
const myClickEventHandler = (event) => {
	event.stopPropagation()
  	alert('I will stop propagation,but do not know at which phase yet')
}

 // will call all parents event listeners with {capture : true }, but won't let the event propagate any further 
element.addEventListener('click',myClickEventHandler, true)


 

PS: according to w3c spec there is the third one - target phase

Demo

preventDefault()

- just prevents default behaviour (action) for element.

Lazy

- only compute value when needed

Program evaluation method

Lazy (pull model) Eager (push model)
expressions are not evaluated when they are bound to variables, but their evaluation is deferredย until their results are needed by other computations expressions are evaluated as declared
Memory usage becomes unpredictable Memory usage is predictable
f :: Int -> Int -> Int ->
f x y = y
// f (1 + 2) 3 = 3 || (1+2) is never evaluated

const data =  (1 + 2) + 3 // gets evaluated immediately

How react optimises work

Event listeners attaching

NOPE

Event listeners creating FIRST!

const handledNativeEvents = ['click', 'change', 'touch', 'scroll', 'dbclick', ...]
const reactEventHandlerPropByEventName = {
   'click': 'onClick',
   'dblclick': 'onDoubleClick',
   ...
}
const prioritiesByEventName = {
  'click': 0, // DiscreteEvent
  'drag': 1,  // UserBlockingEvent
  'load': 2,  // ContinuousEvent
  ...
};
 - react "knows" about native events obviously
react creates a map
 - react prioritizes events by types
import { StrictMode } from "react";
import ReactDOM from "react-dom";

import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <App />
  </StrictMode>,
  rootElement
);
Event handlers registrations, are made during the creation of the root Fiber node, where React is initialized.
...
handledNativeEvents.forEach(eventName => {
      rootDomNode.addEventListener(eventName, myEventListener);
      rootDomNode.addEventListener(eventName, myEventListener, true);
  }

...
Root - is the root cause of everything in React
React puts on DOM nodes a reference to the Fiber node under a dynamic key named [internalInstanceKey] and the props under the key [internalPropsKey].
everything is on Root, and all events are dispatchEvents.

Demo

What is rendering anyway?

view = React(data)
Render !== DOM updates

From components to elements

function Greetings(){
  return [
            <StyledHeader color="rebeccapurple">
              Hello from functional component 
            </StyledHeader>
            <div>
              <span>I will be a text node soon ๐Ÿ˜Ž</span>
            </div>
         ]
}
return [
          React.createElement(
          /* type */ StyledHeader,
          /* props */ { color: 'rebeccapurple' },
          /* children */ 'Hello from functional component'),

          React.createElement(
          'div',
           null,
          	React.createElement(
             	'span', 
              	 null, 
             	'I will be a text node soon ๐Ÿ˜Ž'))  
       ]

=

{
    type: StyledHeader,
    props: {
      color: 'rebeccapurple',
      children: 'Hello from functional component',
    },
    key: null,
    ref: null,
    $$typeof: Symbol.for('react.element')
  },
    
  {
    type: 'div',
    props: {
      children:  {
            {
            type: 'span',
            props: {
              children: 'I will be a text node soon ๐Ÿ˜Ž'
            },
            key: null,
            ref: null,
            $$typeof: Symbol.for('react.element')
            }
      },
    },
    key: null,
    ref: null,
    $$typeof: Symbol.for('react.element')
    }

React element

๐Ÿง˜โ€โ™€๏ธ ๐Ÿง˜๐Ÿปโ€โ™€๏ธ ๐Ÿง˜๐Ÿผโ€โ™€๏ธ ๐Ÿง˜๐Ÿฝโ€โ™€๏ธ ๐Ÿง˜๐Ÿพโ€โ™€๏ธ ๐Ÿง˜๐Ÿฟโ€โ™€๏ธ ๐Ÿง˜โ€โ™‚๏ธ ๐Ÿง˜๐Ÿปโ€โ™‚๏ธ ๐Ÿง˜๐Ÿผโ€โ™‚๏ธ

Components tree you've written using JSX
Elements react created after calling render
Corresponding fiber nodes

We are callable( )

We are IMMUTABLE

We are mutable

Rules

  • Do same work faster
  • Do less work

Some tips

  1. Memoize it properly(useMemo)
  2. Pull fetch on topย 
  3. Assure the reference transparency (useCallback & do not recreate what could be statically imported/used)
  4. flushSync()

Useful links

Summary

Thank you!

Q&A

React performance optimisations

By Kostiantyn Synyshyn

React performance optimisations

React performance optimisations

  • 49