React: A Dive into Virtual DOM and Efficient Rendering

React: A Dive into Virtual DOM and Efficient Rendering

ยท

5 min read

React employs a performance optimization technique known as the Virtual DOM. This involves maintaining an in-memory representation, or Virtual DOM, which is a simplified version of the actual Document Object Model (DOM).

Instead of directly manipulating the real DOM whenever there's a UI change, React first updates the Virtual DOM. Subsequently, it engages in a process called "diffing," where it compares the updated Virtual DOM with a snapshot taken before the update.

This comparison pinpoints the specific changes made. React then selectively applies these changes to the real DOM, updating only what is necessary. This strategy improves performance by minimizing direct interactions with the real DOM, which can be resource-intensive.

Certainly! Let's go through the code examples with explanations:

Buy Me A Coffee

Virtual DOM in React

// Example of Virtual DOM usage in React
const element = (
  <div>
    <h1>Hello, Virtual DOM!</h1>
    <p>This is a React component.</p>
  </div>
);

// Rendering the element to the Virtual DOM
ReactDOM.render(element, virtualDOMContainer);

Explanation: In this example, a React element is created using JSX syntax, representing a simple component with a heading and a paragraph. The ReactDOM.render function then renders this element to the virtual DOM container. The virtual DOM is an in-memory representation of the component structure.

Drawbacks of Direct Real DOM Updates

Performance Costs

Direct updates lead to multiple "reflows" (layout recalculations) and "repaints" (visual changes). Frequent reflows and repaints are resource-intensive, potentially causing sluggish interactions and animations.

// Directly modifying the real DOM
const directUpdate = () => {
  const element = document.getElementById('example');
  element.innerHTML = '<h1>Hello, Direct Update!</h1>';
  // Causes re-rendering and layout recalculations
};

Explanation: This example shows a function that directly updates the content of an HTML element with the ID 'example'. However, this approach can lead to performance costs as it triggers re-rendering and layout recalculations in the real DOM.

Reflows and Repaints

Direct updates lead to multiple "reflows" (layout recalculations) and "repaints" (visual changes). Frequent reflows and repaints are resource-intensive, potentially causing sluggish interactions and animations.

// Example causing reflows and repaints
const updateWithReflow = () => {
  const element = document.getElementById('example');
  element.style.width = '200px'; // Causes reflow
  element.style.color = 'blue'; // Causes repaint
};

Explanation: This function directly modifies the styles of an HTML element, causing a reflow (recalculation of layout) when changing the width and a repaint (visual change) when changing the color. Frequent reflows and repaints can impact performance.

Batching and Efficiency

The Virtual DOM enables React to batch multiple updates, applying them collectively instead of individually. This batching minimizes reflows and repaints, enhancing efficiency and speed.

// Batching updates with the Virtual DOM
const batchedUpdates = () => {
  const element1 = <p>First update</p>;
  const element2 = <p>Second update</p>;

  // React batches these updates before applying to the DOM
  ReactDOM.render(element1, virtualDOMContainer);
  ReactDOM.render(element2, virtualDOMContainer);
};

Explanation: Here, two different elements are rendered to the virtual DOM container. React can batch these updates before applying them to the real DOM. Batching helps reduce the number of reflows and repaints, making the process more efficient.

The Diffing Algorithm

// Example of the diffing algorithm in action
const previousElement = <p>Hello, React!</p>;
const updatedElement = <p>Hello, React with Diffing!</p>;

// React internally performs diffing to identify changes
ReactDOM.render(updatedElement, virtualDOMContainer);

Explanation: This example demonstrates the diffing algorithm in action. The ReactDOM.render function internally performs a comparison between the previous and updated elements to identify the changes. It then selectively updates only the parts of the virtual DOM that have changed.

Steps in Rendering and Committing Changes

Step 1: Trigger a Render

Components render during the initial mount and subsequent updates triggered by state changes. Updating a component's state through setState() queues a render.

// Triggering a render with state update
class MyComponent extends React.Component {
  constructor() {
    super();
    this.state = { count: 0 };
  }

  handleClick = () => {
    // Updating state triggers a render
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}

Explanation: This class represents a React component with state (count). The handleClick method updates the state, triggering a render of the component. React automatically handles the rendering process when the state changes.

Step 2: Rendering the Component

Rendering involves React calling components. The root component is called during the initial render, and subsequently, components with changed state and their children are called recursively.

// Rendering components in React
ReactDOM.render(<MyComponent />, rootContainer);

Explanation: The ReactDOM.render function is used to render the MyComponent class to the specified rootContainer. This is the initial rendering of the component, and subsequent renders will be triggered by state changes.

Pitfall: Purity of Render Function

The render function must be a pure function without side effects. Side effects should be handled in useEffect. Impure render functions can lead to unpredictable behavior, prompting the use of React StrictMode during development.

// Impure render function causing unpredictable behavior
class ImpureComponent extends React.Component {
  render() {
    // Impure action directly modifying state
    this.setState({ data: 'Updated data' });

    return <p>{this.state.data}</p>;
  }
}

Explanation: In this example, the render function of ImpureComponent directly modifies the state using setState, which is considered impure. This can lead to unpredictable behavior and is against the best practices. Side effects should be handled in useEffect rather than in the render function.

Step 3: React Commits Changes to the DOM

React uses appendChild() for the initial display. For subsequent updates, it determines the minimum changes required and selectively updates only the relevant parts of the DOM.

// Committing changes to the DOM in React
const updatedContent = <p>New content</p>;

// React commits only the necessary changes to the DOM
ReactDOM.render(updatedContent, rootContainer);

Explanation: The ReactDOM.render function is used to commit changes to the DOM. In this example, only the necessary changes specified by the updatedContent element will be applied to the real DOM. React optimizes this process by updating only the parts of the DOM that need to be changed.

Buy Me A Coffee

Did you find this article valuable?

Support Revive Coding by becoming a sponsor. Any amount is appreciated!

ย