So let's start by asking, what is rendering? Rendering is the process of React asking your components to describe what they want the UI to look like now, based on their current props and state. And then taking that and applying any necessary updates to the DOM.
Now I'm going to be talking in terms of React, DOM, and the web, but the same principles apply for any other React render, like React Native or React 3 Fiber. When we write components, we have them return JSX tags like angle brackets, my component. At compile time, those get converted into function calls to React.createElement, which in turn returns objects that have the type, props, and children. And these form a tree of objects that describe what the components should look like now.
React will call your components, collect this tree of objects, and then diff the current tree of elements against the last tree of elements that it rendered last time. And this process is called reconciliation. Each render pass can be divided into two different phases. The first phase is the render phase. And this is where React loops over the component tree, asks all of the components, what do you want the UI to look like now, and then collects all that to form the final tree.
Now the render phase can be split into multiple steps. And in fact, as of React 18, React might render a few components, pause, let the browser update, render a few more, maybe handle some incoming data like a key press action and a text input. And then once all of the components have been rendered, it moves on to the commit phase. During the commit phase, React has figured out which changes need to be applied to the DOM and it executes all of those changes synchronously in one sequence. It also then runs commit phase life cycles like the UseLayoutEffectHook or ComponentDidMount and DidUpdate in class components. Then after a short delay, it will run the UseEffectHooks later. And this gives the browser a chance to paint in between the DOM updates and the UseEffects running.
Every React render pass starts with some form of set state being called. For function components, it's the setters from the UseState hook and the dispatch method from UseReducer. For class components, it's this.setState or this.forceUpdate. You can also trigger renders by re-running the top-level ReactDom.Render method. Or there's also the new UseSyncExternalStore hook, which listens for updates from external libraries like Redux. Prior to UseSyncExternalStore, libraries like React Redux still had to call setState in some form inside. Function components don't actually have a force update method, but you can do basically the same thing by creating a UseReducer hook that just increments a counter every time.
Now it's very important to understand that the default behavior of React is that every time a parent component renders, React will recursively render all of the children inside this component. And I think this is where a lot of people get confused. So let's say we have a tree of four components, A, B, C, and D. And we call setState inside of the B component. React queues a re-render.
Comments