@ -232,9 +232,9 @@ else if (element.type !== prevElement.type)
Notice that in every case, except deletion, we still call `render` on the element's children. While its possible that the children will be able to reuse their associated DOM elements, `render` will still be run on them.
Notice that in every case, except deletion, we still call `render` on the element's children. While its possible that the children will be able to reuse their associated DOM elements, `render` will still be run on them.
Now, to get started with our render method we must make some modifications to our previous render methods. 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 need to re-render something or if we can re-use it from the previous render tree.
Now, to get started with our render method we must make some modifications to our previous render methods. 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 need to re-render something or 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 elements 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 are 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`.
{format: "javascript"}
{format: "javascript"}
```
```
@ -247,9 +247,7 @@ function render(element, container) {
}
}
```
```
TODO note that we are adding parent and domElement properties.
Now that we have stored our last render tree we can go ahead and update our render method with the heuristics for reusing the DOM elements. We name it `render_internal` because it is what controls the rendering but takes an additional argument now: the `prevElement`. `preElement` is a reference to the corresponding `element` from the previous render and contains a reference to its associated DOM element and parent DOM element. If it's the first render or if we are rendering a new node or branch to the tree than `prevElement` will be `undefined`. If, however, `element` is `undefined` and `prevElement` is defined then we know we need to delete a node that previously existed.
Now that we have a way to see what we rendered last time we can go ahead and update our render method with the heuristics.
{format: "javascript"}
{format: "javascript"}
```
```
@ -262,7 +260,7 @@ function render_internal(element, container, prevElement) {
domElement = createDOMElement(element);
domElement = createDOMElement(element);
} else if (element.type === prevElement.type) {
} else if (element.type === prevElement.type) {
domElement = prevElement.domElement;
domElement = prevElement.domElement;
} else {
} else { // types don't match
removeDOMElement(prevElement);
removeDOMElement(prevElement);
domElement = createDOMElement(element);
domElement = createDOMElement(element);
}
}
@ -285,6 +283,12 @@ function render_internal(element, container, prevElement) {
}
}
```
```
The only time we can't set DOM properties on our element and render its children is when we are deleting an existing DOM element. We use this observation to group the calls for `setDOMProps` and `renderChildren`. Choosing when to append a new DOM element to the container is also part of the heuristics. If we can reuse an existing DOM element then we do but if the element type has changed or if there was no corresponding existing DOM element then and only then do we append a new DOM element. This ensures the actual DOM tree isn't being swapped out every time we render, only the elements that change are. When a new DOM element is appended to the tree React would invoke `componentDidMount`.
Next up we'll go through all the auxiliary methods that complete the implementation.
Removing a DOM element is straightforward; we just `removeChild` on the parent element. In React, before removing the element, it invoke `componentWillUnmount`.
{format: "javascript"}
{format: "javascript"}
```
```
function removeDOMElement(prevElement) {
function removeDOMElement(prevElement) {
@ -292,6 +296,8 @@ function removeDOMElement(prevElement) {
}
}
```
```
In creating a new DOM element we just need to branch if we are creating a text element since the browser API differs slightly. We also populate the text element's value as the API requires the first argument to be specified even though later on when we set props we will set it again. This is where React would invoke `componentWillMount`.
{format: "javascript"}
{format: "javascript"}
```
```
function createDOMElement(element) {
function createDOMElement(element) {
@ -301,6 +307,8 @@ function createDOMElement(element) {
}
}
```
```
To set the props on an element we first clear all the existing props and then loop through the current props, setting them accordingly. Of course we filter out the `children` prop since we use that elsewhere and it isn't intended to be set directly.
{format: "javascript"}
{format: "javascript"}
```
```
function setDOMProps(element, domElement, prevElement) {
function setDOMProps(element, domElement, prevElement) {
@ -319,6 +327,12 @@ function setDOMProps(element, domElement, prevElement) {
}
}
```
```
TODO note that React is more intelligent settings props, it only updates the ones that need to update
TODO this algorithm doesn't correctly handle event handler props but we're ignoring that for simplicity
For rendering children we use two loops. The first loop removes any elements that are no longer being used. This would happen when the number of children is decreased. The second loop starts at the first child and then iterates through all of the current children calling `render_internal` on each child. When `render_internal` is called the corresponding previous element in that position is passed to `render_internal` or `undefined` if there is no corresponding element, like when the list of children has grown.
TODO don't figure event handlers are handled specially
It's very important to understand the algorithm used here because this is essentially what happens in React when incorrect keys are used, like a list index. And this is why keys are so critical to high performance (and correct) React code. For example, in our algorithm here, if you removed an item from the front of the list you may cause every element in the list to be created anew in the DOM if the types no longer match up. Later on, in the chapter on keys, we will update this algorithm to incorporate keys. It's actually only a minor difference in determining which `child` gets paired with which `prevChild`. Otherwise this is effectively the same algorithm React uses when rendering lists of children.
## Commit Phase
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 are invoked. And invoking those lifecycle methods, and the whole process of tearing down and building up a component is expensive. So again you can see how a bad key would lead to another performance bottleneck since React will be doing this on all or many of the elements in a list frequently.
## Fibers
## Fibers
The actual React implementation used to look very similar to what we've gone through 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. 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.
But even with such a large change, the underlying algorithms for deciding how and when to render components is the same. And when not running in Concurrent Mode the effect is still the same as React does the render phase in one block still. So using a simplified interpretation that doesn't include all the complexities of breaking up the process in to chunks enables us to see more clearly how the process as a whole works. At this point bottlenecks are much more likely to occur from the underlying algorithms and not from the Fiber specific details. In the chapter on Concurrent Mode we will go in to this more.