-
Notifications
You must be signed in to change notification settings - Fork 48.3k
[New Docs] Reconciliation and Web Components docs #7962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
--- | ||
|
||
React's key design decision is to make the API seem like it re-renders the whole app on every update. This makes writing applications a lot easier but is also an incredible challenge to make it tractable. This article explains how with powerful heuristics we managed to turn a O(n<sup>3</sup>) problem into a O(n) one. | ||
|
||
React's key design decision is to make the API seem like it re-renders the whole app on every update. This makes writing applications a lot easier but it is also an interesting challenge to make it tractable. This article explains how React uses heuristics to turn an O(n<sup>3</sup>) problem into an O(n) problem. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do something like:
React provides a declarative API so that you don't have to worry about exactly what changes on every update. This makes writing applications a lot easier but it might not be obvious how this is implemented within React. This article explains the choices we made in React's "diffing" algorithm so that component updates are both predictable while being fast enough for high-performance apps.
Otherwise this file looks good, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok i swapped out w this paragraph
permalink: docs/web-components.html | ||
--- | ||
|
||
React and [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components) are built to solve different problems. Web Components provide strong encapsulation for reusable components, while React provides a declarative library that keeps the DOM in sync with your data. The two goals are complementary. As a developer, you are free to use React in your Web Components, or to use WebComponents in React, or both. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a paragraph?
Most people who use React don't use Web Components at all. Unless you are using or creating third-party UI components, Web Components are likely not useful for building an app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok added
@@ -20,14 +17,12 @@ Since an optimal algorithm is not tractable, we implement a non-optimal O(n) alg | |||
1. Two components of the same class will generate similar trees and two components of different classes will generate different trees. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's replace "class" with "type" here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replaced
|
||
## Pair-wise diff | ||
|
||
In order to do a tree diff, we first need to be able to diff two nodes. There are three different cases being handled. | ||
|
||
|
||
### Different Node Types | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use "element" instead of "node" here and below? While technically reconciliation also affects "nodes" (booleans and text), this article talks specifically about how we treat elements. It was written before modern terminology was introduced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
@@ -52,7 +47,6 @@ It is very unlikely that a `<Header>` element is going to generate a DOM that is | |||
|
|||
As a corollary, if there is a `<Header>` element at the same position in two consecutive renders, you would expect to see a very similar structure and it is worth exploring it. | |||
|
|||
|
|||
### DOM Nodes | |||
|
|||
When comparing two DOM nodes, we look at the attributes of both and can decide which of them changed in linear time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can we use className instead of id attribute? You'd almost never use id attribute in React, and one can potentially confuse it with key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
@@ -73,21 +67,19 @@ renderB: <div style={{'{{'}}fontWeight: 'bold'}} /> | |||
|
|||
After the attributes have been updated, we recurse on all the children. | |||
|
|||
|
|||
### Custom Components | |||
|
|||
We decided that the two custom components are the same. Since components are stateful, we cannot just use the new component and call it a day. React takes all the attributes from the new component and calls `componentWillReceiveProps()` and `componentWillUpdate()` on the previous one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first sentence in this paragraph is confusing. When did we decide that? I would try to merge some of the sentences about custom components in "Different Node Types" with this section, maybe by moving them and the associated example here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah this doesnt make sense, ok i futzed around with this paragraph
OK, I responded to all feedback |
On the web components page, you should probably mention class vs className. |
Yes let's add this, it's a common confusion. Web Components use "class" because we can't "squat" className attribute as web components may have arbitrary attributes. Weird yes. |
|
||
```xml | ||
renderA: <div /> | ||
renderB: <span /> | ||
=> [removeNode <div />], [insertNode <span />] | ||
=> [removeElement <div />], [insertElement <span />] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's write this as removeDOMNode
, insertDOMNode
. That's exactly the confusion I'm talking about. We are comparing elements but result operations happen on DOM nodes.
I'm not even sure this style of explaining it works very well. We don't use this notation of "produced operations" anywhere else. I would personally write it as sentences: "For example, if we first render a <div />
but later render a <span />
at the same spot, React DOM will remove the div
DOM node and replace it with a newly created span
DOM node." Same below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I don't have a strong opinion about this. If you're happy with notation keep it, but please make operation names unambiguously refer to DOM nodes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmmm I can't say I'm happy with this notation. This doc makes me want a diagram of "what is the virtual dom". This is all about transforming one tree into another. What tree are we transforming? The virtual doms? The real dom? Some virtual tree that has both virtual dom and real dom squished together, like should I think of it as a tree of elements and some elements happen to have pointers to DOM nodes? Hrmph. I'll try to rewrite this part....
### Custom Components | ||
|
||
We decided that the two custom components are the same. Since components are stateful, we cannot just use the new component and call it a day. React takes all the attributes from the new component and calls `componentWillReceiveProps()` and `componentWillUpdate()` on the previous one. | ||
The last case is comparing two custom components of the same type. Since components are stateful, we must keep the old instance around. React takes all the attributes from the new component and calls `componentWillReceiveProps()` and `componentWillUpdate()` on the previous one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not quite what I meant.
Can we reorder cases and merge them?
I would like to see:
- DOM Elements. First explain that if type differs they are replaced. Then explain how updates are applied if type is same.
- Component Elements. First explain that if type differs old one is unmounted and new one is mounted. Then explain how componentWillReceiveProps is called if type is the same.
There is no need to mention custom components in section on DOM, and there is no need to have three sections. Two is enough and they should be symmetrical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmm, what about the case where one is a DOM element and one is a component element? Like if you have a component ShimButton which always just renders to a <button/>
, and then if you have a second component SpitefulButton which first renders to <ShimButton/>
, and then at some time later rerenders directly to <button/>
. React will just throw away the first tree and rerender the <button/>
element... right?
OK - I went through and fixed up a lot of "Reconciliation". Plz take another look |
|
||
## Motivation | ||
|
||
When you use React, at a single point in time you can think of the `render()` function as creating a tree of React elements. On the next state or props update, that `render()` function will return a different tree of React elements. The React framework then needs to figure out a way to efficiently convert the first tree into the second tree. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: "The React framework" -> React
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
## The Diffing Algorithm | ||
|
||
When diffing two trees, React first compares the types of the two root elements. If the root elements have different types, React just throws away the first tree and builds the second tree from scratch. When the root elements have the same type, React first converts the root element, and then recurses. The specifics are different depending on whether the elements are DOM elements or component elements. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
React first converts the root element, and then recurses.
Since there is no "conversion" taking place, maybe say something like:
When the root elements have the same type, React just updates component's props or DOM attributes, and then recurses.
We can be vague about where how these props are updated because we explain it in detail later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok i reworded this
|
||
### DOM Elements | ||
|
||
When comparing two React DOM elements, React looks at the attributes of both, keeps the same underlying DOM element, and only updates the changed attributes. For example: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a section in the beginning that React will destroy the old DOM node and create a new one if their types don't match. For example, <span>
is replaced with a <button>
. Any components below also get unmounted, and their state is destroyed. For example, replacing <div><Counter /></div>
with <span><Counter /></span>
will reset Counter
's state and re-mount it.
After that, we explain what happens if type is the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved stuff around so that there's three parts - different types, same type of DOM element, same type of component element, I think this satisfies all comments
|
||
### Custom Components | ||
|
||
When a component updates, the instance stays the same, so that state is maintained across renders. React takes all the attributes from the new component element and calls `componentWillReceiveProps()` and `componentWillUpdate()` on the previous one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, let's first add a section that describes what happens when types are different. The old one receives componentWillUnmount()
and the whole tree is destroyed, the next one receives componentWillMount()
and componentDidMount()
. The state associated with the old one gets lost.
Then explain what happens during an update.
React takes all the attributes from the new component element and calls
componentWillReceiveProps()
andcomponentWillUpdate()
on the previous one.
This is incorrect, lifecycle methods don't live on an element. Element is just a plain object description (<Foo />
or { type: Foo }
), there are no methods on it. React doesn't call any methods on elements.
If you define component as a class, React internally maintains its instance. This is the one that has all the methods and has this.props
. Functional components don't have instances.
When React sees that App
component returned <Foo prop={42} />
(or { type: Foo, props: { prop: 42 } }
) from render()
the first time, it creates an instance of Foo
and assigns its this.props
by reading them from the <Foo prop={42} />
element.
If App
component returns <Foo prop={43} />
the next time, React will know that a Foo
instance already exists, corresponding to the previous element "on the same spot". So instead of recreating the instance, it will update its this.props
to point to new element's props ({ prop: 42 }
), and call componentWillReceiveProps()
on it.
Elements and instances are different things. Elements describe the UI. They are short-lived and immutable. Instances are things that React creates to represent "stable" pieces of UI and hold state associated with them. Over its lifetime, and instance can "receive" several elements as it gets updated. React will not destroy the instance as long as the parent component keeps returning an element with the same type (and key) at the same spot.
(I don't ask that you write all of this, but I hope this helps figuring out how to phrase it correctly.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK - i think "instance" and "element" are now used correctly
Inserting an element at the beginning has worse performance. For example, converting between these two trees works poorly: | ||
|
||
```xml | ||
<div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can we use ul
and li
for this example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
### Keys | ||
|
||
In order to solve this issue, React supports an optional `key` attribute. When children have keys, React uses the key to match children in the original tree with children in the subsequent tree. For example, adding a `key` to our inefficient example above can make the tree conversion efficient: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not quite optional when you render an array because React loudly warns you about missing key
.
<span key={2015}>Duke</span> | ||
<span key={2016}>Villanova</span> | ||
</div> | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add something like "Now React knows that an element with 2014
is the newly inserted one, but elements with keys of 2015
and 2016
just moved."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
```xml | ||
<div> | ||
<span key={2015}>Duke</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make keys strings so it's obvious they are supposed to be strings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
</div> | ||
``` | ||
|
||
In practice, finding a key is not really hard. Most of the time, the element you are going to display already has a unique id. When that's not the case, you can add a new ID property to your model or hash some parts of the content to generate a key. The key only has to be unique among its siblings, not globally unique. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add "As a last resort, you can pass item's index in the array as a key. This can work well if the items are never re-ordered, but reorders will be slow."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
1. The algorithm will not try to match sub-trees of different component classes. If you see yourself alternating between two components classes with very similar output, you may want to make it the same class. In practice, we haven't found this to be an issue. | ||
|
||
2. Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many elements to be unnecessarily recreated, which can cause performance degradation and lost state in child components. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: backtick Math.random()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
OK i have responded to all comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks pretty good. I'm leaving these final comments and don't want to delay you further
## Motivation | ||
|
||
When you use React, at a single point in time you can think of the `render()` function as creating a tree of React elements. On the next state or props update, that `render()` function will return a different tree of React elements. React then needs to figure out a way to efficiently convert the first tree into the second tree. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"to efficiently convert the first tree into the second tree" makes it sound like React mutates the tree you provide to it. Maybe "how to efficiently update the UI to match the most recent tree".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. I took these words. Yeah I found this confusing when I first read it - to me the way that reconciliation makes the most sense is to think of there as being three trees. There is the "element tree" which the developer is creating when they return JSX, there is the DOM which the browser knows about, and there is this "secret internal tree" which only React uses, React creates it when you first do a ReactDOM.render, and the secret internal tree has nodes where each node corresponds to some element and might correspond to something in the DOM. And reconciliation is basically a recursive algorithm on the internal tree. But this is so not the terminology that we are publicly using, I don't really want to go attempt to rewrite everything to refer to three trees right now. That's how I'd explain reconcilation on a whiteboard though. Anyway I just updated this sentence to not imply React is mutating the tree returned by render().
|
||
If we used this in React, displaying 1000 elements would require in the order of one billion comparisons. This is far too expensive. Instead, React implements a heuristic O(n) algorithm based on two assumptions: | ||
|
||
1. Two elements of different types can be treated as totally different trees. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe replace "can be treated as..." with "will likely produce different trees".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
### Elements Of Different Types | ||
|
||
Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mention an example? Like, from <div>
to <img>
, or from <Article>
to <Comment>
, or from <Button>
to <p>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch. | ||
|
||
When tearing down a tree, old DOM elements are destroyed. Component instances receive `componentWillUnmount()`. When building up a new tree, new DOM elements are inserted into the DOM. Component instances receive `componentWillMount()` and then `componentDidMount()`. Any state associated with the old tree is lost. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proposal: let's say "DOM node" in this document when we refer to browser DOM node. This will help remove some ambiguity with the "element" word which we'll use for React elements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
|
||
This will destroy the old `Counter` and remount a new one. | ||
|
||
### DOM Elements Of The Same Type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(This would still be "elements" then)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
|
||
```xml | ||
<ul> | ||
<li key={'2015'}>Duke</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The notation is weird. Let's just use string attributes here (="") and then add a single line example with the words like "Most often, the key would come from your data, such as an ID:" (and the example does something like lib key={todo.id})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
Now React knows that the element with key `'2014'` is the new one, and the elements with the keys `'2015'` and `'2016'` have just moved. | ||
|
||
In practice, finding a key is usually not hard. Most of the time, the element you are going to display already has a unique id. When that's not the case, you can add a new ID property to your model or hash some parts of the content to generate a key. The key only has to be unique among its siblings, not globally unique. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: let's consistently capitalize ID
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
Because React relies on heuristics, if the assumptions behind them are not met, performance will suffer. | ||
|
||
1. The algorithm will not try to match subtrees of different component classes. If you see yourself alternating between two components classes with very similar output, you may want to make it the same class. In practice, we haven't found this to be an issue. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Class => Type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
1. The algorithm will not try to match subtrees of different component classes. If you see yourself alternating between two components classes with very similar output, you may want to make it the same class. In practice, we haven't found this to be an issue. | ||
|
||
2. Keys should be stable, predictable, and unique. Unstable keys (like those produced by `Math.random()`) will cause many elements to be unnecessarily recreated, which can cause performance degradation and lost state in child components. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Many elements to be" => "many component instances and DOM nodes to be"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
React and [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components) are built to solve different problems. Web Components provide strong encapsulation for reusable components, while React provides a declarative library that keeps the DOM in sync with your data. The two goals are complementary. As a developer, you are free to use React in your Web Components, or to use WebComponents in React, or both. | ||
|
||
Most people who use React don't use Web Components at all. Unless you are using or creating third-party UI components, Web Components are likely not useful for building an app. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not the kind of message we want to send. Web components are a sensitive topic. We want to say "React users don't commonly use Web Components", not "Web Components aren't useful".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW @spicyj specifically requested adding this exact paragraph 😛 Well, I rewrote it to be more polite towards web components, I believe everyone will be happy now
These are pretty advanced content. I just had some minor updates. But if folks have deeper feedback on these docs I'm happy to deal with that as well.