Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/_data/nav_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
title: JSX In Depth
- id: typechecking-with-proptypes
title: Typechecking With PropTypes
- id: reconciliation
title: Reconciliation
- id: web-components
title: Web Components
- id: react-without-es6
title: React Without ES6
- id: react-without-jsx
Expand Down
6 changes: 0 additions & 6 deletions docs/_data/nav_docs_old.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@
title: DOM Differences
- id: special-non-dom-attributes
title: Special Non-DOM Attributes
- id: reconciliation
title: Reconciliation
- id: webcomponents
title: Web Components
- id: glossary
title: React (Virtual) DOM Terminology
- title: Old Guides
items:
- id: reusable-components
Expand Down
56 changes: 0 additions & 56 deletions docs/docs-old/ref-09-webcomponents.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,49 +1,44 @@
---
id: reconciliation
title: Reconciliation
permalink: docs-old/reconciliation.html
prev: special-non-dom-attributes.html
next: webcomponents.html
permalink: docs/reconciliation.html
---

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 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 predictable while being fast enough for high-performance apps.

## Motivation

Generating the minimum number of operations to transform one tree into another is a complex and well-studied problem. The [state of the art algorithms](http://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf) have a complexity in the order of O(n<sup>3</sup>) where n is the number of nodes in the tree.
Generating the minimum number of operations to transform one tree into another is a complex and well-studied problem. The [state of the art algorithms](http://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf) have a complexity in the order of O(n<sup>3</sup>) where n is the number of elements in the 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().

This means that displaying 1000 nodes would require in the order of one billion comparisons. This is far too expensive for our use case. To put this number in perspective, CPUs nowadays execute roughly 3 billion instructions per second. So even with the most performant implementation, we wouldn't be able to compute that diff in less than a second.
This means that displaying 1000 elements would require in the order of one billion comparisons. This is far too expensive for our use case. To put this number in perspective, CPUs nowadays execute roughly 3 billion instructions per second. So even with the most performant implementation, we wouldn't be able to compute that diff in less than a second.

Since an optimal algorithm is not tractable, we implement a non-optimal O(n) algorithm using heuristics based on two assumptions:

1. Two components of the same class will generate similar trees and two components of different classes will generate different trees.
1. Two components of the same type will generate similar trees and two components of different types will generate different trees.
2. It is possible to provide a unique key for elements that is stable across different renders.

In practice, these assumptions are ridiculously fast for almost all practical use cases.

In practice, these assumptions are valid for almost all practical use cases.

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

In order to do a tree diff, we first need to be able to diff two elements. There are three different cases being handled.

### Different Node Types
### Different Element 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

If the node type is different, React is going to treat them as two different sub-trees, throw away the first one and build/insert the second one.
If the element type is different, React is going to treat them as two different sub-trees, throw away the first one and build/insert the second one.

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

```

The same logic is used for custom components. If they are not of the same type, React is not going to even try at matching what they render. It is just going to remove the first one from the DOM and insert the second one.

```xml
renderA: <Header />
renderB: <Content />
=> [removeNode <Header />], [insertNode <Content />]
=> [removeElement <Header />], [insertElement <Content />]
```

Having this high level knowledge is a very important aspect of why React's diff algorithm is both fast and precise. It provides a good heuristic to quickly prune big parts of the tree and focus on parts likely to be similar.
Expand All @@ -52,15 +47,14 @@ 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 Elements

### DOM Nodes

When comparing two DOM nodes, we look at the attributes of both and can decide which of them changed in linear time.
When comparing two DOM elements, we look at the attributes of both and can decide which of them changed in linear time.

```xml
renderA: <div id="before" />
renderB: <div id="after" />
=> [replaceAttribute id "after"]
renderA: <div className="before" />
renderB: <div className="after" />
=> [replaceAttribute className "after"]
```

Instead of treating style as an opaque string, a key-value object is used instead. This lets us update only the properties that changed.
Expand All @@ -73,61 +67,56 @@ 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.
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?


The previous component is now operational. Its `render()` method is called and the diff algorithm restarts with the new result and the previous result.


## List-wise diff

### Problematic Case

In order to do children reconciliation, React adopts a very naive approach. It goes over both lists of children at the same time and generates a mutation whenever there's a difference.
In order to do children reconciliation, React adopts a naive approach. It goes over both lists of children at the same time and generates a mutation whenever there's a difference.

For example if you add an element at the end:
For example, if you add an element at the end:

```xml
renderA: <div><span>first</span></div>
renderB: <div><span>first</span><span>second</span></div>
=> [insertNode <span>second</span>]
=> [insertElement <span>second</span>]
```

Inserting an element at the beginning is problematic. React is going to see that both nodes are spans and therefore run into a mutation mode.
Inserting an element at the beginning is problematic. React is going to see that both elements are spans and therefore run into a mutation mode.

```xml
renderA: <div><span>first</span></div>
renderB: <div><span>second</span><span>first</span></div>
=> [replaceAttribute textContent 'second'], [insertNode <span>first</span>]
=> [replaceAttribute textContent 'second'], [insertElement <span>first</span>]
```

There are many algorithms that attempt to find the minimum sets of operations to transform a list of elements. [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) can find the minimum using single element insertion, deletion and substitution in O(n<sup>2</sup>). Even if we were to use Levenshtein, this doesn't find when a node has moved into another position and algorithms to do that have much worse complexity.
There are many algorithms that attempt to find the minimum sets of operations to transform a list of elements. [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) can find the minimum using single element insertion, deletion and substitution in O(n<sup>2</sup>). Even if we were to use Levenshtein, this doesn't find when an element has moved into another position and algorithms to do that have much worse complexity.

### Keys

In order to solve this seemingly intractable issue, an optional attribute has been introduced. You can provide for each child a key that is going to be used to do the matching. If you specify a key, React is now able to find insertion, deletion, substitution and moves in O(n) using a hash table.

In order to solve this issue, React supports an optional `key` attribute. You can provide for each child a key that is going to be used to do the matching. If you specify a key, React is now able to find insertion, deletion, substitution and moves in O(n) using a hash table.

```xml
renderA: <div><span key="first">first</span></div>
renderB: <div><span key="second">second</span><span key="first">first</span></div>
=> [insertNode <span>second</span>]
=> [insertElement <span>second</span>]
```
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


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. Remember that the key only has to be unique among its siblings, not globally unique.


## Trade-offs

It is important to remember that the reconciliation algorithm is an implementation detail. React could re-render the whole app on every action; the end result would be the same. We are regularly refining the heuristics in order to make common use cases faster.

In the current implementation, you can express the fact that a sub-tree has been moved amongst its siblings, but you cannot tell that it has moved somewhere else. The algorithm will re-render that full sub-tree.

Because we rely on two heuristics, if the assumptions behind them are not met, performance will suffer.
Because React relies on heuristics, if the assumptions behind them are not met, performance will suffer.

1. The algorithm will not try to match sub-trees of different components 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 nodes to be unnecessarily re-created, which can cause performance degradation and lost state in child components.

2. Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many elements to be unnecessarily re-created, which can cause performance degradation and lost state in child components.
47 changes: 47 additions & 0 deletions docs/docs/web-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
id: web-components
title: Web Components
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.

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


## Using Web Components in React

```javascript
class HelloMessage extends React.Component {
render() {
return <div>Hello <x-search>{this.props.name}</x-search>!</div>;
}
}
```

> Note:
>
> Web Components often expose an imperative API. For instance, a `video` Web Component might expose `play()` and `pause()` functions). To access the imperative APIs of a Web Component, you will need to use a ref to interact with the DOM node directly. If you are using third-party Web Components, the best solution is to write a React component that behaves as a wrapper for your Web Component.
>
> Events emitted by a Web Component may not properly propagate through a React render tree.
> You will need to manually attach event handlers to handle these events within your React components.


## Using React in your Web Components

```javascript
var proto = Object.create(HTMLElement.prototype, {
attachedCallback: {
value: function() {
var mountPoint = document.createElement('span');
this.createShadowRoot().appendChild(mountPoint);

var name = this.getAttribute('name');
var url = 'https://www.google.com/search?q=' + encodeURIComponent(name);
ReactDOM.render(<a href={url}>{name}</a>, mountPoint);
}
}
});
document.registerElement('x-search', {prototype: proto});
```

You can also check out this [complete Web Components example on GitHub](https://github.com/facebook/react/tree/master/examples/webcomponents).