Summary
The answer got a little long. In short, React has an algorithm that compares two tree elements in the DOM to know whether to update them or not. This algorithm will update the DOM tree when:
- The root node of the tree is of different type (e. g.
<div> <a>Oi</a> </div>
and <button> <a>Oi</a> </button>
);
- Some attribute has been modified (e. g.
<div className="a" />
and <div className="b' />
);
- Some component property has been modified (e. g.
<Timer time={10} />
and <Timer time={15} />
);
- Some state of the component has been modified;
- The comparison of child nodes finds differences. This comparison is a recursion on the two trees at the same time, comparing node to node.
And, the relationship with the Hooks mentioned is simply that they are executed in every rendering, but make use of an array of dependencies to define whether the callback passed to these Hooks must be run again or not.
How the re-rendering works?
This topic and every explanation of the algorithm of diff is based on documentation Reconciliation.
Whenever the render()
is executed in a class component, or when a functional component is executed, an element tree is returned. When the state or properties are updated, a new tree is returned. So, React needs to differentiate the two trees to know if it needs to render something.
The re-rendering does not disassemble and reassemble the components. It only updates the differences found according to the rules defined by the diff, explained below.
Algorithm of diff
To identify the differences in a computationally inexpensive way, React created its own algorithm for diff, implementing an O(n) heuristic algorithm based on two premises:
- Two elements of different types will produce different trees;
- The developer can suggest which child elements can be stable in different renderings with a property
key
.
The behavior of diff varies according to the type of the root elements to be compared.
Elements of different types
When the root node of a tree is modified, React will disassemble the entire tree and mount a new one from scratch. For example:
<div>
<Counter />
</div>
<span>
<Counter />
</span>
The above code will make the Counter
be reassembled.
DOM elements of the same type
When updating DOM elements of the same type, React observes the attributes, updating only the modified attributes. For example:
<div className="before" title="stuff" />
<div className="after" title="stuff" />
In this case, React will only modify the className
node. In the case of the attribute style
, React can identify which property has been modified.
Components of the same type
When a component updates, the instance is the same, then the state is maintained between renderings. React updates component instance properties to match the new element.
After this update of the properties, the algorithm of diff is executed by comparing the previous result to the current one.
Note: As the documentation of Reconciliation barely cites the State, I want to quote another documentation here, the State and Lifecycle. This one says, thanks to the call from setState
, React knows there’s been a change of status, and then executes the render()
to check what should be modified on the screen. That’s why you can’t modify the state directly, without calling the function setState()
.
Although the example given is for class components, this information can be taken to the functional components.
Recursion in the children
By default, when there is recursion on the children of a DOM node, React only iterates on both child lists at the same time and generates a mutation whenever there is a difference. For example:
<ul>
<li>primeiro</li>
<li>segundo</li>
</ul>
<ul>
<li>primeiro</li>
<li>segundo</li>
<li>terceiro</li>
</ul>
React will understand that the <li>primeiro</li>
and <li>segundo</li>
are equal, and will add the node <li>terceiro</li>
. Because of this, inserting an element at the beginning or middle of the list will cause more than one element to be rendered unnecessarily.
Keys (key
)
To solve the above problem, React accepts the attribute key
in any element. When children have a key
defined, it is used to check whether they represent the same node or not, and then perform the re-rendering when necessary.
That’s why we use the index
as key
is not always a valid option. You would end up in the same situation of not using key
none.
For more information about the property key
, read What it’s for and how to set the prop "key" in React?
Attention factors
According to the algorithm explained above, there are two points of attention:
The algorithm will not attempt to combine sub-trees of different types of components. If you switch between two types of components with very similar output, it is convenient to make them of the same type. In practice, this is not considered a problem.
The keys (key
) must be stable, predictable and unique. Unstable keys (such as those produced by Math.random()
or Date.now()
) will cause many instances of components and DOM nodes to be unnecessarily recreated, which can cause degradation of performance and loss of status in child components.
The above mentioned Hooks
useEffect
(or componentDidUpdate
)
In accordance with the documentation says, by default, the effects run after each completed rendering, but you can choose to run them only when certain values are changed (based on the dependency array). There is a question that addresses this subject: What is and why should I provide a "dependency array" for React’s Hooks?
useMemo
The useMemo
is executed during rendering and returns a value memoized. It also receives an array of dependencies, causing the value to be recalculated only when one of those dependencies is modified. It is useful for calculations "expensive".
useCallback
Similar to the useMemo
, the useCallback
returns a callback memoized (and not a value). It is useful for times that depend on comparison by reference to avoid a re-rendering.
I wrote a more in-depth response on the useMemo
and useCallback
in What is the difference between React’s useMemo and useCallback?
What they have in common?
All these Hooks are executed at each render. The point is that they accept an array of dependencies to define whether the callback function passed to them should actually be executed or not.
For example, an empty array as in useCallback(() => {}, [])
means it will only be executed once, since no value will be updated in the next renderings (there is no value in the array).
Already in the case useMemo(() => fatorial(count), [count])
, means that whenever there is a rendering and count
have its value modified, the () => fatorial(count)
will be executed.
Then, the dependency array does not cause a re-rendering, but it is used after re-rendering to know if the callback will be executed or not.
is very broad, mixing Oks with Komponent class that are different the way of doing, but, the behavior is the same.
– novic
@novic, at the end of the question I preferred to put the focus on the functional components themselves (which give the relationship with the Hooks), so I do not think that actually occurs this "mixture"...
– Luiz Felipe
The difference is how to do, the styles, that same programming styles, but, the end result is the same, I understood that you might want to point out differences (
há distinção
) but in fact reinforcing the generated code is the same. Maybe the long awaited new version will be different– novic