When trying to improve performance the most important and value step in the process is identifying and diagnosing bottlenecks. You can attempt to guess at how to write high-performance code from the start, and to a small extent that can help, but with the complexity of the web ecosystem today that is very unlikely to be sufficient. There are just too many layers and components in the system to always get it right. So inevitably you will run in to performance bottlenecks.
When trying to improve performance the most important and valuable step in the process is identifying and diagnosing bottlenecks. You can attempt to guess at how to write high-performance code from the start, and to a small extent that can help, but with the complexity of the web ecosystem today that is very unlikely to be sufficient. There are just too many layers and components in the system to always get it right. So inevitably you will run in to performance bottlenecks.
There are six main steps to solving performance bottlenecks, with some parts done in a cycle:
There are six main steps to solving performance bottlenecks, with some parts done in a cycle:
@ -12,7 +12,9 @@ While React isn't technically a living organism that can tell us what to do, it
## Components of React
## Components of React
Conceptually React is very simple. It starts by walking a tree of components and building up a tree of their output. Then it compares that tree to the tree currently in the browser's DOM to find any differences between them. When it finds differences it updates the browser's DOM to match its internal tree.
The primary elements that make up any React program are its components. A `component` in React maintains local state and "renders" output to eventually be included in the browser's DOM. A tree of components is then created whenever a component outputs other components.
So, conceptually, React's core algorithm is very simple: it starts by walking a tree of components and building up a tree of their output. Then it compares that tree to the tree currently in the browser's DOM to find any differences between them. When it finds differences it updates the browser's DOM to match its internal tree.
But what does that actually look like? If your app is janky does that explanation point you towards what is wrong? No. It might make you wonder if maybe it is too expensive to re-render the tree or if maybe the diffing React does is slow but you won't really know. When I was initially testing out different bread recipes I had guesses at why it wasn't working but I didn't really figure it out until I had a deeper understanding of how making bread worked. It's time we build up our understanding of how React works so that we can start to answer our questions with solid answers.
But what does that actually look like? If your app is janky does that explanation point you towards what is wrong? No. It might make you wonder if maybe it is too expensive to re-render the tree or if maybe the diffing React does is slow but you won't really know. When I was initially testing out different bread recipes I had guesses at why it wasn't working but I didn't really figure it out until I had a deeper understanding of how making bread worked. It's time we build up our understanding of how React works so that we can start to answer our questions with solid answers.
@ -109,8 +111,8 @@ That is: an object with two properties: `type` and `props`. The `props` property
{format: "javascript"}
{format: "javascript"}
```
```
function createElement(node) {
function createElement(node) {
// if array (not text, number, or other primitive)
// if array (our representation of an element)
if (typeof node === 'object') {
if (Array.isArray(node)) {
const [ tag, props, children ] = node;
const [ tag, props, children ] = node;
return {
return {
type: tag,
type: tag,
@ -136,7 +138,7 @@ Our `createElement` has two main parts: complex elements and primitive elements.
That's it. Now we have everything we need to actually begin the process of rendering our tree to the DOM!
That's it. Now we have everything we need to actually begin the process of rendering our tree to the DOM!
## Render
## Render: Putting Elements on the Screen
There are now only two major puzzles remaining in our quest for our own React. The next piece is: `render`. How do we go from our JSM tree of nodes, to actually displaying something on screen? To do that we will explore the `render` method.
There are now only two major puzzles remaining in our quest for our own React. The next piece is: `render`. How do we go from our JSM tree of nodes, to actually displaying something on screen? To do that we will explore the `render` method.
@ -191,7 +193,7 @@ The `render` method starts by creating the DOM element. Then we need to set its
Now we can go all the way from our JSX-like notation to a rendered tree in the browser's DOM! But so far we can only add things to our tree. To be able to remove and modify the tree we need one more part: reconciliation.
Now we can go all the way from our JSX-like notation to a rendered tree in the browser's DOM! But so far we can only add things to our tree. To be able to remove and modify the tree we need one more part: reconciliation.
## Reconciliation
## Reconciliation, or How React Diffs
A tale of two trees. These are the two trees that people most often talk about when talking about React's "secret sauce": the virtual DOM and the browser's DOM tree. This idea is what originally set React apart. React's reconciliation is what allows you to program declaratively. Reconciliation is what makes it so we no longer have to manually update and modify the DOM whenever our own internal state changes. In a lot of ways, it is what makes React, React.
A tale of two trees. These are the two trees that people most often talk about when talking about React's "secret sauce": the virtual DOM and the browser's DOM tree. This idea is what originally set React apart. React's reconciliation is what allows you to program declaratively. Reconciliation is what makes it so we no longer have to manually update and modify the DOM whenever our own internal state changes. In a lot of ways, it is what makes React, React.
@ -230,7 +232,7 @@ Notice that in every case, except deletion, we still call `render` on the elemen
Now, to get started with our render method we must make some modifications to our previous render method. First, we need to be able to store and retrieve the previous render tree. Then we need to add code to compare parts of the tree to decide if we can re-use DOM elements from the previous render tree. And last, we need to return a tree of elements that can be used in the next render as a comparison and to reference the DOM elements that we create. These new element objects will have the same structure as our current elements but we will add two new properties: `domElement` and `parent`. `domElement` is the DOM element associated with our synthetic element and `parent` is a reference to the parent DOM element.
Now, to get started with our render method we must make some modifications to our previous render method. First, we need to be able to store and retrieve the previous render tree. Then we need to add code to compare parts of the tree to decide if we can re-use DOM elements from the previous render tree. And last, we need to return a tree of elements that can be used in the next render as a comparison and to reference the DOM elements that we create. These new element objects will have the same structure as our current elements but we will add two new properties: `domElement` and `parent`. `domElement` is the DOM element associated with our synthetic element and `parent` is a reference to the parent DOM element.
Here we begin by adding a global object that will store our last render tree, keyed by the `container`.
Here we begin by adding a global object that will store our last render tree, keyed by the `container`. `container` refers to the browser's DOM element that will be the parent for all of the React derived DOM elements. This parent DOM element can only be used to render one tree of elements at a time so it works well to use as a key for `renderTrees`.
{format: "javascript"}
{format: "javascript"}
```
```
@ -361,7 +363,7 @@ It's very important to understand the algorithm used here because this is essent
There are a few things to note here. First, it is important to pay attention to when React will be removing a DOM element from the tree and adding a new one as this is when the related lifecycle events or hooks are invoked. And invoking those lifecycle methods or hooks, and the whole process of tearing down and building up a component is expensive. So again, if you use a bad key, like the algorithm here simulates, you'll be hitting a major performance bottleneck since React will not only be replacing DOM elements in the browser but also tearing down and rebuilding the trees of child components.
There are a few things to note here. First, it is important to pay attention to when React will be removing a DOM element from the tree and adding a new one as this is when the related lifecycle events or hooks are invoked. And invoking those lifecycle methods or hooks, and the whole process of tearing down and building up a component is expensive. So again, if you use a bad key, like the algorithm here simulates, you'll be hitting a major performance bottleneck since React will not only be replacing DOM elements in the browser but also tearing down and rebuilding the trees of child components.
## Fibers
## Fibers: Splitting up Render
The actual React implementation used to look very similar to what we've built so far, but with React 16 this has changed dramatically with the introduction of Fibers. Fibers are a name that React gives to discrete units of work during the render process. And the React reconciliation algorithm was changed to be based on small units of work instead of one large, potentially long-running call to `render`. This means that React is now able to process just part of the render phase, pause to let the browser take care of other things, and resume again. This is the underlying change the enables the experimental Concurrent Mode as well as running most hooks without blocking the render.
The actual React implementation used to look very similar to what we've built so far, but with React 16 this has changed dramatically with the introduction of Fibers. Fibers are a name that React gives to discrete units of work during the render process. And the React reconciliation algorithm was changed to be based on small units of work instead of one large, potentially long-running call to `render`. This means that React is now able to process just part of the render phase, pause to let the browser take care of other things, and resume again. This is the underlying change the enables the experimental Concurrent Mode as well as running most hooks without blocking the render.