Rendering in React. What is the life of a component?

One of the most important theoretical topics, understanding which makes it easier to work with the React library, is the process of rendering components. How does React understand that it is time to update the DOM tree? How does re-rendering affect performance and how to improve it? What happens under the hood of React when we decide to display a component on the page? What role do hooks play in all this? To answer these questions, it is necessary to understand such concepts as rendering, component life cycle, reconciliation, side effects and some others.

Article outline:

1) Rendering in the context of React

2) Component life cycle

2.1) Mounting components

2.2) Update component

2.3) Unmounting the component

3) Some questions for self-testing

1. Rendering in the context of React

What does the term mean to you? rendering? We are used to the fact that in the general sense of this word it is meant a visual representation of some calculated model (scene render, 3D model render, video render, etc.). That is, rendering is a visual representation obtained as a result of some calculations. So without calculations there will be no rendering, it sounds obvious.

But React has its own understanding of the rendering process. Rendering in React is calculation what should be displayed. When React renders components, it doesn't actually display them, it just knows what elements the component should consist of by accessing it and parsing the code it returns.

Let's assume we have a functional component like this:

const SomeComponent = () => {  
  
  return (
      <div>      
        <h1>Text of SomeComponent</h1>   
      </div>
  )  
}

Let's also assume that React has started the process of rendering a component (we won't go into the reasons that caused React to render this component yet). What's going on under the hood?

SomeComponent is a function. React calls it. Calling this function returns the component's JSX markup, which React starts parsing. React understands what real DOM elements this component contains. Once React understands what DOM nodes are contained in the JSX code, React transforms this JSX into a function call. React.createElement()which will return a virtual DOM object representing the component.

So the JSX above will be transpiled in the next call:

const SomeComponent = React.createElement(
  ‘div’, // тип элемента  
  null, // пропсы, в данном случае их нет, поэтому null  
  React.CreateElement(    
    ‘h1’, // тип элемента    
    null,  // пропсы, в данном случае их нет, поэтому null    
    ‘Text of SomeComponent’ // дочерний элемент h1 - текст  
  )
);

This way, React gets an object representation of the component that will help it build the virtual DOM. This process is rendering.

Obviously, getting a component's representation is only half the battle. What does React do next? Well, what happens next is a process called commit'om. During commit React inserts the given component object into the real DOM. This process is managed by the React submodule, react-DOM. To efficiently insert and remove virtual DOM objects from the virtual DOM, React also initializes the process reconciliationsduring which it compares (differentiates from English diffing) the current Virtual DOM with the previous one. Comparing two trees allows it to effectively decide which DOM nodes to update, delete, add. O commit And reconciliation I mention these processes in passing, since it is enough to simply know that they exist and what they are.

From the above explanation, you can see the following connection: each render starts a chain of processes:

render -> reconciliation (diffing) -> commit

And now there should be no question about why React is so good at efficiently constructing the DOM, and why this operation can be both efficient and expensive (if done incorrectly).

Now the question is: when does rendering happen? What triggers it? To do this, you need to get familiar with the component life cycle.

2. Component life cycle

The lifecycle of a component is the sequence of stages that a component goes through from the moment it is created until the moment it is removed from the DOM.

It consists of 3 stages:

  • Mount (mounting)

  • Update (update)

  • Unmount

2.1 Mounting components

The phase during which a component is first added to the DOM.

Let's take the following component as an example:

const SomeComponent = () => {  
  const [inputValue, setInputValue] = useState(() => getInitialValue());  
  
  const ref = useRef(null);  
  
  useEffect(() => {   
    … // какие-нибудь побочные эффекты (запрос данных с api, подписка на events и тд)
    return () => {     
      … // clean-up функция    
    };  
  }, [inputValue]);  
  
  useLayoutEffect(() => {    
    …    
    return () => {      
      … // clean-up функция    
    }  
  })  
  
  const heavy = useMemo(() => {    
    return doSomethingHeavy(inputValue);  
  }, [inputValue]);  
  
  const id=”textInput”;    
  
  return (    
    <div>
      <label htmlFor={id}>label for input</label>
      <input
        id={id}        
        value={inputValue}        
        onChange={(e) => setInputValue(e.target.value)}        
        placeholder=”Type something to update”        
        ref={ref}        
        autoFocus        
        onFocus={(e) => {console.log(“onFocus”);}}       
      />      
      <Child heavy={heavy} />     
    </div>    
  )
};

Let me explain the UI of the presented component: the component returns a controllable input and one child component Child.

Now I will describe the chain of processes that occur during the initial mounting of this component.

1) The first step in the component cycle is to mount it into the DOM. The rendering process begins: React calls the component to parse its JSX.

During rendering:

  • The local values ​​declared in the component (the id variable) are calculated.

  • Side effects found in useEffect() will be placed in a list.

  • On initial render, hooks are initialized in the order they are declared in the component.

  • For useState(), useReducer() and useRef() hooks, React will save the initial states and ignore them during subsequent component updates. To improve performance, React allows you to pass an initial state retrieval function (getInitialValue()) to useReducer and useState. It will call this retrieval function exactly once during component mounting.

  • The result returned by the useMemo() hook will also be calculated. Until inputValue changes, the doSomtehingHeavy() function will not be called, which improves performance for expensive calculations.

In this component I did not declare hooks such as useCallback(), useDefferedValue().

If they are present:

  • useCallback() will save a reference to the function declared in it. This is necessary to avoid recreating a reference to the same function every time the component is updated. This helps to avoid unnecessary re-renders of components that depend on this function.

  • useDefferedValue() will return the value passed to it. I won't go into details of this hook, I'll just say that this hook is somewhat similar to debounce.

  • React recursively renders the child components of the original (does the same actions with them).

2) React has rendered the component, received its representation, and is moving into the commit phase.

– The resulting DOM nodes are inserted into the real DOM tree.

To ensure that the DOM element has been created, an autoFocus and onFocus handler are attached to the input. The browser will automatically focus on the created element and call the handler as soon as the element appears in the DOM.

– refs allow access to DOM nodes. During the first render, these nodes are not yet in the DOM, so they are currently undefined or null. Refs can be passed to elements and manipulated, and a callback can be placed in a ref that will be called after the element is added to the DOM.

3) React nets refs.

After commit, access to DOM nodes appears. All refs receive their value (they stop being null or undefined).

4) Layout effects will start.

After commit, but before paint, Layout effects will run. Nothing should interfere with the browser rendering, so they are useful if you need to perform any actions right during the application paint.

5) The DOM is being rendered by the browser.

6) After a short delay, the side effects declared in useEffect() are run.

Hidden text

1) Render component, parse JSX -> calculate local values ​​-> calculate states -> calculate and memoize variables (if any) -> recursively render child components.

2) Inserting elements into the DOM (commit).

3) Updating refs to DOM nodes.

4) Launch Layout effects.

5) Paint the DOM tree.

6) Triggering side effects after a delay.

2.2 Component Update

The phase during which a component is re-rendered in response to changes.

Before explaining the component update step, there are two things that need to be clarified:

1) The component can be updated for 2 reasons:

– Props updated

– His state has been updated

2) Re-rendering a component inevitably leads to recursive re-rendering of its child components, even if their props have not changed (unless they are memoized with React.memo()).

Let's take the same component given above for clarity.

The chain of processes is as follows:

1) As usual, the component rendering process begins.

– New props, local variables and states are re-evaluated.

– React compares hook dependencies for changes with dependencies received during the previous render. Hooks with no dependencies or with changed dependencies will be called again. Hooks with no changed dependencies will be skipped.

– The component returns new JSX.

2) DOM update. Another commit occurs, which updates the DOM tree. Rendering does not always update the entire DOM. It only updates the elements that need it.

3) Before updating the DOM, refs will be set to null again. Unstable ref callbacks will be called again, while callbacks saved with useCallback() will not be called.

4) cleanup functions of useLayoutEffect() that were returned during the previous render will work with states or props that have values ​​from previous renders.

5) After commit, React will visit the refs again.

6) Layout effects are called again (according to the state of their dependencies).

7) DOM repaint occurs.

8) The cleanup functions returned by useEffect during the previous render are called. They will also have “outdated” props or states (that’s why we can’t immediately see the new state if we first update it in the same effect and then try to output it via console.log()).

9) Another side effect call will occur if the dependencies in useEffect() have changed.

Hidden text

1) Rendering -> recalculation of states, local variables, props -> calling hooks with changed dependencies -> rendering child components.

2) Update DOM.

3) refs are set to zero.

4) Cleanup functions of Layout effects are called with “stale” values.

5) After updating DOM refs get new values.

6) Calling Layout effects.

7) Redrawing the DOM.

8) Calling cleanup functions of side effects with “stale” values.

9) Causing side effects if dependencies are changed.

2.2 Unmount component

The stage of unmounting and removing the component from the DOM.

1) The final cleanup functions of Layout effects are run.

2) Refs are annotated because the DOM elements they refer to are removed.

3) DOM nodes are removed.

4) Finally, the side effect cleanup functions are run one last time.

You can't describe it any shorter here, the shortest stage, so much the better! And from theory to questions.

3. Questions for self-checking

1) What is rendering in React? Why do we need reconciliation and commit phases?

2) What does the lifecycle of a React component consist of?

3) Under what circumstances is the re-rendering process triggered?

4) What helps prevent unnecessary re-renders of child components?


This is where the article ends and I will be glad to hear your opinion and receive constructive criticism. If you notice any inaccuracies or errors, be sure to report them – I will be glad to make corrections and discuss)

Also, for a complete understanding, I recommend going through this sitewhich clearly shows the stages of the component life cycle. I took the example of the component from there. Also, when writing the article, I was partially guided by this powerful article. It describes in detail the processes that occur under the hood of React.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *