Skip to content

[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

Merged
merged 11 commits into from
Oct 18, 2016

Conversation

lacker
Copy link
Contributor

@lacker lacker commented Oct 12, 2016

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.

  • Put them in "Guides" because they are more guide-y than referencey
  • Cleaned up the reconciliation tone to be more humble as requested by @spicyj
  • Misc english-language cleanup

@lacker
Copy link
Contributor Author

lacker commented Oct 12, 2016

screencapture-localhost-4000-react-docs-reconciliation-html-1476311095191

@lacker
Copy link
Contributor Author

lacker commented Oct 12, 2016

screencapture-localhost-4000-react-docs-web-components-html-1476311113747 1

---

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.
Copy link
Collaborator

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.

Copy link
Contributor Author

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.
Copy link
Collaborator

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.

Copy link
Contributor Author

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.
Copy link
Collaborator

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.

Copy link
Contributor Author

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

Copy link
Collaborator

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.

Copy link
Contributor Author

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.
Copy link
Collaborator

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.

Copy link
Contributor Author

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.
Copy link
Collaborator

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.

Copy link
Contributor Author

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

@lacker
Copy link
Contributor Author

lacker commented Oct 13, 2016

OK, I responded to all feedback

@brigand
Copy link
Contributor

brigand commented Oct 13, 2016

On the web components page, you should probably mention class vs className.

@gaearon
Copy link
Collaborator

gaearon commented Oct 13, 2016

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 />]
Copy link
Collaborator

@gaearon gaearon Oct 13, 2016

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.

Copy link
Collaborator

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)

Copy link
Contributor Author

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.
Copy link
Collaborator

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:

  1. DOM Elements. First explain that if type differs they are replaced. Then explain how updates are applied if type is same.
  2. 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.

Copy link
Contributor Author

@lacker lacker Oct 14, 2016

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?

@lacker
Copy link
Contributor Author

lacker commented Oct 14, 2016

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.
Copy link
Collaborator

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

Copy link
Contributor Author

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.
Copy link
Collaborator

@gaearon gaearon Oct 14, 2016

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.

Copy link
Contributor Author

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:
Copy link
Collaborator

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.

Copy link
Contributor Author

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.
Copy link
Collaborator

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() and componentWillUpdate() 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.)

Copy link
Contributor Author

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>
Copy link
Collaborator

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?

Copy link
Contributor Author

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:
Copy link
Collaborator

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>
```
Copy link
Collaborator

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."

Copy link
Contributor Author

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>
Copy link
Collaborator

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.

Copy link
Contributor Author

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.
Copy link
Collaborator

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."

Copy link
Contributor Author

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: backtick Math.random()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@lacker
Copy link
Contributor Author

lacker commented Oct 14, 2016

OK i have responded to all comments

@hramos hramos changed the title Reconciliation and Web Components docs [New Docs] Reconciliation and Web Components docs Oct 15, 2016
Copy link
Collaborator

@gaearon gaearon left a 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.

Copy link
Collaborator

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".

Copy link
Contributor Author

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.
Copy link
Collaborator

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".

Copy link
Contributor Author

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.
Copy link
Collaborator

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>.

Copy link
Contributor Author

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.
Copy link
Collaborator

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.

Copy link
Contributor Author

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
Copy link
Collaborator

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)

Copy link
Contributor Author

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>
Copy link
Collaborator

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})

Copy link
Contributor Author

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.
Copy link
Collaborator

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

Copy link
Contributor Author

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class => Type

Copy link
Contributor Author

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.
Copy link
Collaborator

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"

Copy link
Contributor Author

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.
Copy link
Collaborator

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".

Copy link
Contributor Author

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

@lacker lacker merged commit 6505c0d into facebook:new-docs Oct 18, 2016
@lacker lacker deleted the porting branch October 18, 2016 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants