@samitouri / QOS-React / commits / d0cc73a957

Add Implementation Notes and amend Codebase Overview (#7830)

* Add more to Codebase Overview * WIP * Start a reconciler overview * Add a few more sections * todo * WIP * Finish it * Whitespace * Minor tweaks * Minor tweaks

Dan Abramov committed Oct 10, 2016 at 17:29 UTC d0cc73a95715ab67f15059ea71b43e987388b460
5 files changed +1059 -9
docs/_data/nav_contributing.yml
+2
@@ -4,5 +4,7 @@
4 title: How to Contribute
5 - id: codebase-overview
6 title: Codebase Overview
7 + - id: implementation-notes
8 + title: Implementation Notes
9 - id: design-principles
10 title: Design Principles
docs/contributing/codebase-overview.md
+153 -3
@@ -4,7 +4,7 @@ title: Codebase Overview
4 layout: contributing
5 permalink: contributing/codebase-overview.html
6 prev: how-to-contribute.html
7 -next: design-principles.html
7 +next: implementation-notes.html
8 ---
9
10 This section will give you an overview of the React codebase organization, its conventions, and the implementation.
@@ -48,7 +48,7 @@ React itself was extracted from Facebook codebase and uses Haste for historical
48
49 When we compile React for npm, a script copies all the modules into [a single flat directory called `lib`](https://unpkg.com/react@15/lib/) and prepends all `require()` paths with `./`. This way Node, Browserify, Webpack, and other tools can understand React build output without being aware of Haste.
50
51 -**If you're reading React source on GitHub and want to quickly jump to any file, press "t".**
51 +**If you're reading React source on GitHub and want to jump to a file, press "t".**
52
53 This is a GitHub shortcut for searching the current repo for fuzzy filename matches. Start typing the name of the file you are looking for, and it will show up as the first match.
54
@@ -78,6 +78,22 @@ We don't have a top-level directory for unit tests. Instead, we put them into a
78
79 For example, a test for [`setInnerHTML.js`](https://github.com/facebook/react/blob/87724bd87506325fcaf2648c70fc1f43411a87be/src/renderers/dom/client/utils/setInnerHTML.js) is located in [`__tests__/setInnerHTML-test.js`](https://github.com/facebook/react/blob/87724bd87506325fcaf2648c70fc1f43411a87be/src/renderers/dom/client/utils/__tests__/setInnerHTML-test.js) right next to it.
80
81 +### Shared Code
82 +
83 +Even though Haste allows to import any module from anywhere in the repository, we follow a convention to avoid cyclic dependencies and other unpleasant surprises. By convention, a file may only import files in the same folder or in subfolders below.
84 +
85 +For example, files inside [`src/renderers/dom/client`](https://github.com/facebook/react/tree/master/src/renderers/dom/client) may import other files in the same folder ot below.
86 +
87 +However they can't import modules from [`src/renderers/dom/server`](https://github.com/facebook/react/tree/master/src/renderers/dom/server) because it is not a child directory of [`src/renderers/dom/client`](https://github.com/facebook/react/tree/master/src/renderers/dom/client).
88 +
89 +There is an exception to this rule. Sometimes we *do* need to share functionality between two groups of modules. In this case we hoist it up to a folder called `shared` inside their closest common ancestor folder.
90 +
91 +For example, code shared between [`src/renderers/dom/client`](https://github.com/facebook/react/tree/master/src/renderers/dom/client) and [`src/renderers/dom/server`](https://github.com/facebook/react/tree/master/src/renderers/dom/server) lives in [`src/renderers/dom/shared`](https://github.com/facebook/react/tree/master/src/renderers/dom/shared).
92 +
93 +By the same logic, if [`src/renderers/dom/client`](https://github.com/facebook/react/tree/master/src/renderers/dom/client) needs to share a utility with something in [`src/renderers/native`](https://github.com/facebook/react/tree/master/src/renderers/native), this utility would be in [`src/renderers/shared`](https://github.com/facebook/react/tree/master/src/renderers/shared).
94 +
95 +This convention is not enforced but we check for it during a pull request review.
96 +
97 ### Warnings and Invariants
98
99 React codebase uses the `warning` module to display warnings:
@@ -141,6 +157,136 @@ if (__DEV__) {
157 }
158 ```
159
160 +### JSDoc
161 +
162 +Some of the internal and public methods are annotated with [JSDoc annotations](http://usejsdoc.org/):
163 +
164 +```js
165 +/**
166 + * Updates this component by updating the text content.
167 + *
168 + * @param {ReactText} nextText The next text content
169 + * @param {ReactReconcileTransaction} transaction
170 + * @internal
171 + */
172 +receiveComponent: function(nextText, transaction) {
173 + // ...
174 +},
175 +```
176 +
177 +We try to keep existing annotations up-to-date but we don't enforce them. We don't use JSDoc in the newly written code, and instead use Flow to document and enforce types.
178 +
179 +### Flow
180 +
181 +We recently started introducing [Flow](https://flowtype.org/) checks to the codebase. Files marked with the `@flow` annotation in the license header comment are being typechecked.
182 +
183 +We accept pull requests [adding Flow annotations to existing code](https://github.com/facebook/react/pull/7600/files). Flow annotations look like this:
184 +
185 +```js
186 +ReactRef.detachRefs = function(
187 + instance: ReactInstance,
188 + element: ReactElement | string | number | null | false,
189 +): void {
190 + // ...
191 +}
192 +```
193 +
194 +When possible, new code should use Flow annotations.
195 +You can run `npm run flow` locally to check your code with Flow.
196 +
197 +### Classes and Mixins
198 +
199 +React was originally written in ES5. We have since enabled ES2015 features with [Babel](http://babeljs.io/), including classes. However most of React code is still written in ES5.
200 +
201 +In particular, you might see the following pattern quite often:
202 +
203 +```js
204 +// Constructor
205 +function ReactDOMComponent(element) {
206 + this._currentElement = element;
207 +}
208 +
209 +// Methods
210 +ReactDOMComponent.Mixin = {
211 + mountComponent: function() {
212 + // ...
213 + }
214 +};
215 +
216 +// Put methods on the prototype
217 +Object.assign(
218 + ReactDOMComponent.prototype,
219 + ReactDOMComponent.Mixin
220 +);
221 +
222 +module.exports = ReactDOMComponent;
223 +```
224 +
225 +The `Mixin` in this code has no relation to React `mixins` feature. It is just a way of grouping a few methods under an object. Those methods may later get attached to some other class. We use this pattern in a few places although we try to avoid it in the new code.
226 +
227 +Equivalent code in ES2015 would like this:
228 +
229 +```js
230 +class ReactDOMComponent {
231 + constructor(element) {
232 + this._currentElement = element;
233 + }
234 +
235 + mountComponent() {
236 + // ...
237 + }
238 +}
239 +
240 +module.exports = ReactDOMComponent;
241 +```
242 +
243 +Sometimes we [convert old code to ES2015 classes](https://github.com/facebook/react/pull/7647/files). However this is not very important to us because there is an [ongoing effort](#fiber-reconciler) to replace the React reconciler implementation with a less object-oriented approach which wouldn't use classes at all.
244 +
245 +### Dynamic Injection
246 +
247 +React uses dynamic injection in some modules. While it is always explicit, it is still unfortunate because it hinders understanding of the code. The main reason it exists is because React originally only supported DOM as a target. React Native started as a React fork. We had to add dynamic injection to React to let React Native override some behaviors.
248 +
249 +You may see modules declaring their dynamic dependencies like this:
250 +
251 +```js
252 +// Dynamically injected
253 +var textComponentClass = null;
254 +
255 +// Relies on dynamically injected value
256 +function createInstanceForText(text) {
257 + return new textComponentClass(text);
258 +}
259 +
260 +var ReactHostComponent = {
261 + createInstanceForText,
262 +
263 + // Provides an opportunity for dynamic injection
264 + injection: {
265 + injectTextComponentClass: function(componentClass) {
266 + textComponentClass = componentClass;
267 + },
268 + },
269 +};
270 +
271 +module.exports = ReactHostComponent;
272 +```
273 +
274 +The `injection` field is not handled specially in any way. But by convention, it means that this module wants to have some (presumably platform-specific) dependencies injected into it at runtime.
275 +
276 +In React DOM, [`ReactDefaultInjection`](https://github.com/facebook/react/blob/4f345e021a6bd9105f09f3aee6d8762eaa9db3ec/src/renderers/dom/shared/ReactDefaultInjection.js) injects a DOM-specific implementation:
277 +
278 +```js
279 +ReactHostComponent.injection.injectTextComponentClass(ReactDOMTextComponent);
280 +```
281 +
282 +In React Native, [`ReactNativeDefaultInjection`](https://github.com/facebook/react/blob/4f345e021a6bd9105f09f3aee6d8762eaa9db3ec/src/renderers/native/ReactNativeDefaultInjection.js) injects its own implementation:
283 +
284 +```js
285 +ReactHostComponent.injection.injectTextComponentClass(ReactNativeTextComponent);
286 +```
287 +
288 +There are multiple injection points in the codebase. In the future, we intend to get rid of the dynamic injection mechanism and wire up all the pieces statically during the build.
289 +
290 ### Multiple Packages
291
292 React is a [monorepo](http://danluu.com/monorepo/). Its repository contains multiple separate packages so that their changes can be coordinated together, and documentation and issues live in one place.
@@ -227,6 +373,10 @@ During an update, the stack reconciler "drills down" through composite component
373
374 It is important to understand that the stack reconciler always processes the component tree synchronously in a single pass. While individual tree branches may [bail out of reconciliation](/react/docs/advanced-performance.html#avoiding-reconciling-the-dom), the stack reconciler can't pause, and so it is suboptimal when the updates are deep and the available CPU time is limited.
375
376 +#### Learn More
377 +
378 +**The [next section](/react/contributing/implementation-notes.html) describes the current implementation in more details.**
379 +
380 ### Fiber Reconciler
381
382 The "fiber" reconciler is a new effort aiming to resolve the problems inherent in the stack reconciler and fix a few long-standing issues.
@@ -259,4 +409,4 @@ Additionally, we provide a standalone build called `react-with-addons.js` which
409
410 ### What Next?
411
262 -Learn the [design principles](/react/contributing/design-principles.html) guiding development of React in the next section.
412 +Read the [next section](/react/contributing/implementation-notes.html) to learn about the current implementation of reconciler in more detail.
docs/contributing/how-to-contribute.md
+10 -6
@@ -80,7 +80,8 @@ The core team is monitoring for pull requests. We will review your pull request
80 3. If you've changed APIs, update the documentation.
81 4. Ensure the test suite passes (`npm test`).
82 5. Make sure your code lints (`npm run lint`).
83 -6. If you haven't already, complete the CLA.
83 +6. Run the [Flow](https://flowtype.org/) typechecks (`npm run flow`).
84 +7. If you haven't already, complete the CLA.
85
86 ### Contributor License Agreement (CLA)
87
@@ -104,6 +105,7 @@ Then, you can run several commands:
105 * `npm test` runs the complete test suite.
106 * `npm test -- --watch` runs an interactive test watcher.
107 * `npm test <pattern>` runs tests with matching filenames.
108 +* `npm run flow` runs the [Flow](https://flowtype.org/) typechecks.
109 * `npm run build` creates a `build` folder with all the packages.
110
111 We recommend running `npm test` (or its variations above) to make sure you don't introduce any regressions as you work on your change. However it can be handy to try your build of React in a real project.
@@ -142,16 +144,18 @@ However, there are still some styles that the linter cannot pick up. If you are
144 * Write "attractive" code
145 * Do not use the optional parameters of `setTimeout` and `setInterval`
146
145 -### License
147 +### Introductory Video
148
147 -By contributing to React, you agree that your contributions will be licensed under its BSD license.
149 +You may be interested in watching [this short video](https://www.youtube.com/watch?v=wUpPsEcGsg8) (26 mins) which gives an introduction on how to contribute to React.
150
151 ### Meeting Notes
152
153 React team meets once a week to discuss the development of React, future plans, and priorities. You can find the meeting notes in a [dedicated repository](https://github.com/reactjs/core-notes/).
154
153 -### What Next?
155 +### License
156
155 -You may be interested in watching [this short video](https://www.youtube.com/watch?v=wUpPsEcGsg8) (26 mins) which gives an introduction on how to contribute to React.
157 +By contributing to React, you agree that your contributions will be licensed under its BSD license.
158 +
159 +### What Next?
160
157 -Read the next sections to learn more about [understanding the codebase](/react/contributing/codebase-overview.html), and the [design principles](/react/contributing/design-principles.html) guiding the development of React.
161 +Read the [next section](/react/contributing/codebase-overview.html) to learn how the codebase is organized.
docs/contributing/implementation-notes.md new
+894
@@ -0,0 +1,894 @@
1 +---
2 +id: implementation-notes
3 +title: Implementation Notes
4 +layout: contributing
5 +permalink: contributing/implementation-notes.html
6 +prev: codebase-overview.html
7 +next: design-principles.html
8 +---
9 +
10 +This section is a collection of implementation notes for the [stack reconciler](/react/contributing/codebase-overview.html#stack-reconciler).
11 +
12 +It is very technical and assumes a strong understanding of React public API as well as how it's divided into core, renderers, and the reconciler. If you're not very familiar with the React codebase, read [the codebase overview](/react/contributing/codebase-overview.html) first.
13 +
14 +The stack reconciler is powering all the React production code today. It is located in [`src/renderers/shared/stack/reconciler`](https://github.com/facebook/react/tree/master/src/renderers/shared/stack) and is used by both React DOM and React Native.
15 +
16 +### Video: Building React from Scratch
17 +
18 +[Paul O'Shannessy](https://twitter.com/zpao) gave a talk about [building React from scratch](https://www.youtube.com/watch?v=_MAD4Oly9yg) that largely inspired this document.
19 +
20 +Both this document and his talk are simplifications of the real codebase so you might get a better understanding by getting familiar with both of them.
21 +
22 +### Overview
23 +
24 +The reconciler itself doesn't have a public API. [Renderers](/react/contributing/codebase-overview.html#stack-renderers) like React DOM and React Native use it to efficiently update the user interface according to the React components written by the user.
25 +
26 +### Mounting as a Recursive Process
27 +
28 +Let's consider the first time you mount a component:
29 +
30 +```js
31 +ReactDOM.render(<App />, rootEl);
32 +```
33 +
34 +React DOM will pass `<App />` along to the reconciler. Remember that `<App />` is a React element, that is, a description of *what* to render. You can think about it as a plain object:
35 +
36 +```js
37 +console.log(<App />);
38 +// { type: App, props: {} }
39 +```
40 +
41 +The reconciler will check if `App` is a class or a function.
42 +
43 +If `App` is a function, the reconciler will call `App(props)` to get the rendered element.
44 +
45 +If `App` is a class, the reconciler will instantiate an `App` with `new App(props)`, call the `componentWillMount()` lifecycle method, and then will call the `render()` method to get the rendered element.
46 +
47 +Either way, the reconciler will learn the element `App` "rendered to".
48 +
49 +This process is recursive. `App` may render to a `<Greeting />`, `Greeting` may render to a `<Button />`, and so on. The reconciler will "drill down" through user-defined components recursively as it learns what each component renders to.
50 +
51 +You can imagine this process as a pseudocode:
52 +
53 +```js
54 +function isClass(type) {
55 + // React.Component subclasses have this flag
56 + return (
57 + Boolean(type.prototype) &&
58 + Boolean(type.prototype.isReactComponent)
59 + );
60 +}
61 +
62 +// This function takes a React element (e.g. <App />)
63 +// and returns a DOM or Native node representing the mounted tree.
64 +function mount(element) {
65 + var type = element.type;
66 + var props = element.props;
67 +
68 + // We will determine the rendered element
69 + // by either running the type as function
70 + // or creating an instance and calling render().
71 + var renderedElement;
72 + if (isClass(type)) {
73 + // Component class
74 + var publicInstance = new type(props);
75 + // Set the props
76 + publicInstance.props = props;
77 + // Call the lifecycle if necessary
78 + if (publicInstance.componentWillMount) {
79 + publicInstance.componentWillMount();
80 + }
81 + // Get the rendered element by calling render()
82 + renderedElement = publicInstance.render();
83 + } else {
84 + // Component function
85 + renderedElement = type(props);
86 + }
87 +
88 + // This process is recursive because a component may
89 + // return an element with a type of another component.
90 + return mount(renderedElement);
91 +
92 + // Note: this implementation is incomplete and recurses infinitely!
93 + // It only handles elements like <App /> or <Button />.
94 + // It doesn't handle elements like <div /> or <p /> yet.
95 +}
96 +
97 +var rootEl = document.getElementById('root');
98 +var node = mount(<App />);
99 +rootEl.appendChild(node);
100 +```
101 +
102 +>**Note:**
103 +>
104 +>This really *is* a pseudo-code. It isn't similar to the real implementation. It will also cause a stack overflow because we haven't discussed when to stop the recursion.
105 +
106 +Let's recap a few key ideas in the example above:
107 +
108 +* React elements are plain objects representing the component type (e.g. `App`) and the props.
109 +* User-defined components (e.g. `App`) can be classes or functions but they all "render to" elements.
110 +* "Mounting" is a recursive process that creates a DOM or Native tree given the top-level React element (e.g. `<App />`).
111 +
112 +### Mounting Host Elements
113 +
114 +This process would be useless if we didn't render something to the screen as a result.
115 +
116 +In addition to user-defined ("composite") components, React elements may also represent platform-specific ("host") components. For example, `Button` might return a `<div />` from its render method.
117 +
118 +If element's `type` property is a string, we are dealing with a host element:
119 +
120 +```js
121 +console.log(<div />);
122 +// { type: 'div', props: {} }
123 +```
124 +
125 +There is no user-defined code associated with host elements.
126 +
127 +When the reconciler encounters a host element, it lets the renderer take care of mounting it. For example, React DOM would create a DOM node.
128 +
129 +If the host element has children, the reconciler recursively mounts them following the same algorithm as above. It doesn't matter whether children are host (like `<div><hr /></div>`), composite (like `<div><Button /></div>`), or both.
130 +
131 +The DOM nodes produced by the child components will be appended to the parent DOM node, and recursively, the complete DOM structure will be assembled.
132 +
133 +>**Note:**
134 +>
135 +>The reconciler itself is not tied to the DOM. The exact result of mounting (sometimes called "mount image" in the source code) depends on the renderer, and can be a DOM node (React DOM), a string (React DOM Server), or a number representing a native view (React Native).
136 +
137 +If we were to extend the code to handle host elements, it would look like this:
138 +
139 +```js
140 +function isClass(type) {
141 + // React.Component subclasses have this flag
142 + return (
143 + Boolean(type.prototype) &&
144 + Boolean(type.prototype.isReactComponent)
145 + );
146 +}
147 +
148 +// This function only handles elements with a composite type.
149 +// For example, it handles <App /> and <Button />, but not a <div />.
150 +function mountComposite(element) {
151 + var type = element.type;
152 + var props = element.props;
153 +
154 + var renderedElement;
155 + if (isClass(type)) {
156 + // Component class
157 + var publicInstance = new type(props);
158 + // Set the props
159 + publicInstance.props = props;
160 + // Call the lifecycle if necessary
161 + if (publicInstance.componentWillMount) {
162 + publicInstance.componentWillMount();
163 + }
164 + renderedElement = publicInstance.render();
165 + } else if (typeof type === 'function') {
166 + // Component function
167 + renderedElement = type(props);
168 + }
169 +
170 + // This is recursive but we'll eventually reach the bottom of recursion when
171 + // the element is host (e.g. <div />) rather than composite (e.g. <App />):
172 + return mount(renderedElement);
173 +}
174 +
175 +// This function only handles elements with a host type.
176 +// For example, it handles <div /> and <p /> but not an <App />.
177 +function mountHost(element) {
178 + var type = element.type;
179 + var props = element.props;
180 + var children = props.children || [];
181 + if (!Array.isArray(children)) {
182 + children = [children];
183 + }
184 + children = children.filter(Boolean);
185 +
186 + // This block of code shouldn't be in the reconciler.
187 + // Different renderers might initialize nodes differently.
188 + // For example, React Native would create iOS or Android views.
189 + var node = document.createElement(type);
190 + Object.keys(props).forEach(propName => {
191 + if (propName !== 'children') {
192 + node.setAttribute(propName, props[propName]);
193 + }
194 + });
195 +
196 + // Mount the children
197 + children.forEach(childElement => {
198 + // Children may be host (e.g. <div />) or composite (e.g. <Button />).
199 + // We will also mount them recursively:
200 + var childNode = mount(childElement);
201 +
202 + // This line of code is also renderer-specific.
203 + // It would be different depending on the renderer:
204 + node.appendChild(childNode);
205 + });
206 +
207 + // Return the DOM node as mount result.
208 + // This is where the recursion ends.
209 + return node;
210 +}
211 +
212 +function mount(element) {
213 + var type = element.type;
214 + if (typeof type === 'function') {
215 + // User-defined components
216 + return mountComposite(element);
217 + } else if (typeof type === 'string') {
218 + // Platform-specific components
219 + return mountHost(element);
220 + }
221 +}
222 +
223 +var rootEl = document.getElementById('root');
224 +var node = mount(<App />);
225 +rootEl.appendChild(node);
226 +```
227 +
228 +This is working but still far from how the reconciler is really implemented. The key missing ingredient is support for updates.
229 +
230 +### Introducing Internal Instances
231 +
232 +The key feature of React is that you can re-render everything, and it won't recreate the DOM or reset the state:
233 +
234 +```js
235 +ReactDOM.render(<App />, rootEl);
236 +// Should reuse the existing DOM:
237 +ReactDOM.render(<App />, rootEl);
238 +```
239 +
240 +However, our implementation above only knows how to mount the initial tree. It can't perform updates on it because it doesn't store all the necessary information, such as all the `publicInstance`s, or which DOM `node`s correspond to which components.
241 +
242 +The stack reconciler codebase solves this by making the `mount()` function a method and putting it on a class. There are drawbacks to this approach, and we are going in the opposite direction in the [ongoing rewrite of the reconciler](/react/contributing/codebase-overview.html#fiber-reconciler). Nevertheless this is how it works now.
243 +
244 +Instead of separate `mountHost` and `mountComposite` functions, we will create two classes: `DOMComponent` and `CompositeComponent`.
245 +
246 +Both classes have a constructor accepting the `element`, as well as a `mount()` method returning the mounted node. We will replace a top-level `mount()` function with a factory that instantiates the correct class:
247 +
248 +```js
249 +function instantiateComponent(element) {
250 + var type = element.type;
251 + if (typeof type === 'function') {
252 + // User-defined components
253 + return new CompositeComponent(element);
254 + } else if (typeof type === 'string') {
255 + // Platform-specific components
256 + return new DOMComponent(element);
257 + }
258 +}
259 +```
260 +
261 +First, let's consider the implementation of `CompositeComponent`:
262 +
263 +```js
264 +class CompositeComponent {
265 + constructor(element) {
266 + this.currentElement = element;
267 + this.renderedComponent = null;
268 + this.publicInstance = null;
269 + }
270 +
271 + getPublicInstance() {
272 + // For composite components, expose the class instance.
273 + return this.publicInstance;
274 + }
275 +
276 + mount() {
277 + var element = this.currentElement;
278 + var type = element.type;
279 + var props = element.props;
280 +
281 + var publicInstance;
282 + var renderedElement;
283 + if (isClass(type)) {
284 + // Component class
285 + publicInstance = new type(props);
286 + // Set the props
287 + publicInstance.props = props;
288 + // Call the lifecycle if necessary
289 + if (publicInstance.componentWillMount) {
290 + publicInstance.componentWillMount();
291 + }
292 + renderedElement = publicInstance.render();
293 + } else if (typeof type === 'function') {
294 + // Component function
295 + publicInstance = null;
296 + renderedElement = type(props);
297 + }
298 +
299 + // Save the public instance
300 + this.publicInstance = publicInstance;
301 +
302 + // Instantiate the child internal instance according to the element.
303 + // It would be a DOMComponent for <div /> or <p />,
304 + // and a CompositeComponent for <App /> or <Button />:
305 + var renderedComponent = instantiateComponent(renderedElement);
306 + this.renderedComponent = renderedComponent;
307 +
308 + // Mount the rendered output
309 + return renderedComponent.mount();
310 + }
311 +}
312 +```
313 +
314 +This is not much different from our previous `mountComposite()` implementation, but now we can save some information, such as `this.currentElement`, `this.renderedComponent`, and `this.publicInstance`, for use during updates.
315 +
316 +Note that an instance of `CompositeComponent` is not the same thing as an instance of the user-supplied `element.type`. `CompositeComponent` is an implementation detail of our reconciler, and is never exposed to the user. The user-defined class is the one we read from `element.type`, and `CompositeComponent` creates an instance of it.
317 +
318 +To avoid the confusion, we will call instances of `CompositeComponent` and `DOMComponent` "internal instances". They exist so we can associate some long-lived data with them. Only the renderer and the reconciler are aware that they exist.
319 +
320 +In contrast, we call an instance of the user-defined class a "public instance". The public instance is what you see as `this` in the `render()` and other methods of your custom components.
321 +
322 +The `mountHost()` function, refactored to be a `mount()` method on `DOMComponent` class, also looks familiar:
323 +
324 +```js
325 +class DOMComponent {
326 + constructor(element) {
327 + this.currentElement = element;
328 + this.renderedChildren = [];
329 + this.node = null;
330 + }
331 +
332 + getPublicInstance() {
333 + // For DOM components, only expose the DOM node.
334 + return this.node;
335 + }
336 +
337 + mount() {
338 + var element = this.currentElement;
339 + var type = element.type;
340 + var props = element.props;
341 + var children = props.children || [];
342 + if (!Array.isArray(children)) {
343 + children = [children];
344 + }
345 +
346 + // Create and save the node
347 + var node = document.createElement(type);
348 + this.node = node;
349 +
350 + // Set the attributes
351 + Object.keys(props).forEach(propName => {
352 + if (propName !== 'children') {
353 + node.setAttribute(propName, props[propName]);
354 + }
355 + });
356 +
357 + // Create and save the contained children.
358 + // Each of them can be a DOMComponent or a CompositeComponent,
359 + // depending on whether the element type is a string or a function.
360 + var renderedChildren = children.map(instantiateComponent);
361 + this.renderedChildren = renderedChildren;
362 +
363 + // Collect DOM nodes they return on mount
364 + var childNodes = renderedChildren.map(child => child.mount());
365 + childNodes.forEach(childNode => node.appendChild(childNode));
366 +
367 + // Return the DOM node as mount result
368 + return node;
369 + }
370 +}
371 +```
372 +
373 +The main difference after refactoring from `mountHost()` is that we now keep `this.node` and `this.renderedChildren` associated with the internal DOM component instance. We will also use them for applying non-destructive updates in the future.
374 +
375 +As a result, each internal instance, composite or host, now points to its child internal instances. To help visualize this, if a functional `<App>` component renders a `<Button>` class component, and `Button` class renders a `<div>`, the internal instance tree would look like this:
376 +
377 +```js
378 +[object CompositeComponent] {
379 + currentElement: <App />,
380 + publicInstance: null,
381 + renderedComponent: [object CompositeComponent] {
382 + currentElement: <Button />,
383 + publicInstance: [object Button],
384 + renderedComponent: [object DOMComponent] {
385 + currentElement: <div />,
386 + node: [object HTMLDivElement],
387 + renderedChildren: []
388 + }
389 + }
390 +}
391 +```
392 +
393 +In the DOM you would only see the `<div>`. However the internal instance tree contains both composite and host internal instances.
394 +
395 +The composite internal instances need to store:
396 +
397 +* The current element.
398 +* The public instance if element type is a class.
399 +* The single rendered internal instance. It can be either a `DOMComponent` or a `CompositeComponent`.
400 +
401 +The host internal instances need to store:
402 +
403 +* The current element.
404 +* The DOM node.
405 +* All the child internal instances. Each of them can be either a `DOMComponent` or a `CompositeComponent`.
406 +
407 +If you're struggling to imagine what how an internal instance tree is structured in more complex applications, [React DevTools](https://github.com/facebook/react-devtools) can give you a close approximation, as it highlights host instances with grey, and composite instances with purple:
408 +
409 + <img src="/react/img/docs/implementation-notes-tree.png" width="500" alt="React DevTools tree" />
410 +
411 +To complete this refactoring, we will introduce a function that mounts a complete tree into a container node, just like `ReactDOM.render()`. It returns a public instance, also like `ReactDOM.render()`:
412 +
413 +```js
414 +function mountTree(element, containerNode) {
415 + // Create the top-level internal instance
416 + var rootComponent = instantiateComponent(element);
417 +
418 + // Mount the top-level component into the container
419 + var node = rootComponent.mount();
420 + containerNode.appendChild(node);
421 +
422 + // Return the public instance it provides
423 + var publicInstance = rootComponent.getPublicInstance();
424 + return publicInstance;
425 +}
426 +
427 +var rootEl = document.getElementById('root');
428 +mountTree(<App />, rootEl);
429 +```
430 +
431 +### Unmounting
432 +
433 +Now that we have internal instances that hold onto their children and the DOM nodes, we can implement unmounting. For a composite component, unmounting calls a lifecycle hook and recurses.
434 +
435 +```js
436 +class CompositeComponent {
437 +
438 + // ...
439 +
440 + unmount() {
441 + // Call the lifecycle hook if necessary
442 + var publicInstance = this.publicInstance;
443 + if (publicInstance) {
444 + if (publicInstance.componentWillUnmount) {
445 + publicInstance.componentWillUnmount();
446 + }
447 + }
448 +
449 + // Unmount the single rendered component
450 + var renderedComponent = this.renderedComponent;
451 + renderedComponent.unmount();
452 + }
453 +}
454 +```
455 +
456 +For `DOMComponent`, unmounting tells each child to unmount:
457 +
458 +```js
459 +class DOMComponent {
460 +
461 + // ...
462 +
463 + unmount() {
464 + // Unmount all the children
465 + var renderedChildren = this.renderedChildren;
466 + renderedChildren.forEach(child => child.unmount());
467 + }
468 +}
469 +```
470 +
471 +In practice, unmounting DOM components also removes the event listeners and clears some caches, but we will skip those details.
472 +
473 +We can now add a new top-level function called `unmountTree(containerNode)` that is similar to `ReactDOM.unmountComponentAtNode()`:
474 +
475 +```js
476 +function unmountTree(containerNode) {
477 + // Read the internal instance from a DOM node:
478 + // (This doesn't work yet, we will need to change mountTree() to store it.)
479 + var node = containerNode.firstChild;
480 + var rootComponent = node._internalInstance;
481 +
482 + // Unmount the tree and clear the container
483 + rootComponent.unmount();
484 + containerNode.innerHTML = '';
485 +}
486 +```
487 +
488 +In order for this to work, we need to read an internal root instance from a DOM node. We will modify `mountTree()` to add the `_internalInstance` property to the root DOM node. We will also teach `mountTree()` to destroy any existing tree so it can be called multiple times:
489 +
490 +```js
491 +function mountTree(element, containerNode) {
492 + // Destroy any existing tree
493 + if (containerNode.firstChild) {
494 + unmountTree(containerNode);
495 + }
496 +
497 + // Create the top-level internal instance
498 + var rootComponent = instantiateComponent(element);
499 +
500 + // Mount the top-level component into the container
501 + var node = rootComponent.mount();
502 + containerNode.appendChild(node);
503 +
504 + // Save a reference to the internal instance
505 + node._internalInstance = rootComponent;
506 +
507 + // Return the public instance it provides
508 + var publicInstance = rootComponent.getPublicInstance();
509 + return publicInstance;
510 +}
511 +```
512 +
513 +Now, running `unmountTree()`, or running `mountTree()` repeatedly, removes the old tree and runs the `componentWillUnmount()` lifecycle hook on components.
514 +
515 +### Updating
516 +
517 +In the previous section, we implemented unmounting. However React wouldn't be very useful if each prop change unmounted and mounted the whole tree. The goal of the reconciler is to reuse existing instances where possible to preserve the DOM and the state:
518 +
519 +```js
520 +var rootEl = document.getElementById('root');
521 +
522 +mountTree(<App />, rootEl);
523 +// Should reuse the existing DOM:
524 +mountTree(<App />, rootEl);
525 +```
526 +
527 +We will extend our internal instance contract with one more method. In addition to `mount()` and `unmount()`, both `DOMComponent` and `CompositeComponent` will implement a new method called `receive(nextElement)`:
528 +
529 +```js
530 +class CompositeComponent {
531 + // ...
532 +
533 + receive(nextElement) {
534 + // ...
535 + }
536 +}
537 +
538 +class DOMComponent {
539 + // ...
540 +
541 + receive(nextElement) {
542 + // ...
543 + }
544 +}
545 +```
546 +
547 +Its job is to do whatever is necessary to bring the component (and any of its children) up to date with the description provided by the `nextElement`.
548 +
549 +This is the part that is often described as "virtual DOM diffing" although what really happens is that we walk the internal tree recursively and let each internal instance receive an update.
550 +
551 +### Updating Composite Components
552 +
553 +When a composite component receives a new element, we run the `componentWillUpdate()` lifecycle hook.
554 +
555 +Then we re-render the component with the new props, and get the next rendered element:
556 +
557 +```js
558 +class CompositeComponent {
559 +
560 + // ...
561 +
562 + receive(nextElement) {
563 + var prevProps = this.currentElement.props;
564 + var publicInstance = this.publicInstance;
565 + var prevRenderedComponent = this.renderedComponent;
566 + var prevRenderedElement = prevRenderedComponent.currentElement;
567 +
568 + // Update *own* element
569 + this.currentElement = nextElement;
570 + var type = nextElement.type;
571 + var nextProps = nextElement.props;
572 +
573 + // Figure out what the next render() output is
574 + var nextRenderedElement;
575 + if (isClass(type)) {
576 + // Component class
577 + // Call the lifecycle if necessary
578 + if (publicInstance.componentWillUpdate) {
579 + publicInstance.componentWillUpdate(prevProps);
580 + }
581 + // Update the props
582 + publicInstance.props = nextProps;
583 + // Re-render
584 + nextRenderedElement = publicInstance.render();
585 + } else if (typeof type === 'function') {
586 + // Component function
587 + nextRenderedElement = type(nextProps);
588 + }
589 +
590 + // ...
591 +```
592 +
593 +Next, we can look at the rendered element's `type`. If the `type` has not changed since the last render, the component below can also be updated in place.
594 +
595 +For example, if it returned `<Button color="red" />` the first time, and `<Button color="blue" />` the second time, we can just tell the corresponding internal instance to `receive()` the next element:
596 +
597 +```js
598 + // ...
599 +
600 + // If the rendered element type has not changed,
601 + // reuse the existing component instance and exit.
602 + if (prevRenderedElement.type === nextRenderedElement.type) {
603 + prevRenderedComponent.receive(nextRenderedElement);
604 + return;
605 + }
606 +
607 + // ...
608 +```
609 +
610 +However, if the next rendered element has a different `type` than the previously rendered element, we can't update the internal instance. A `<button>` can't "become" an `<input>`.
611 +
612 +Instead, we have to unmount the existing internal instance and mount the new one corresponding to the rendered element type. For example, this is what happens when a component that previously rendered a `<button />` renders an `<input />`:
613 +
614 +```js
615 + // ...
616 +
617 + // If we reached this point, we need to unmount the previously
618 + // mounted component, mount the new one, and swap their nodes.
619 +
620 + // Find the old node because it will need to be replaced
621 + var prevNode = prevRenderedComponent.getHostNode();
622 +
623 + // Unmount the old child and mount a new child
624 + prevRenderedComponent.unmount();
625 + var nextRenderedComponent = instantiateComponent(nextRenderedElement);
626 + var nextNode = renderedComponent.mount();
627 +
628 + // Replace the reference to the child
629 + this.renderedComponent = renderedComponent;
630 +
631 + // Replace the old node with the new one
632 + // Note: this is renderer-specific code and
633 + // ideally should live outside of CompositeComponent:
634 + prevNode.parentNode.replaceChild(prevNode, nextNode);
635 + }
636 +}
637 +```
638 +
639 +To sum this up, when a composite component receives a new element, it may either delegate the update to its rendered internal instance, or unmount it and mount a new one in its place.
640 +
641 +There is another condition under which a component will re-mount rather than receive an element, and that is when the element's `key` has changed. We don't discuss `key` handling in this document because it adds more complexity to an already complex tutorial.
642 +
643 +Note that we needed to add a method called `getHostNode()` to the internal instance contract so that it's possible to locate the platform-specific node and replace it during the update. Its implementation is straightforward for both classes:
644 +
645 +```js
646 +class CompositeComponent {
647 + // ...
648 +
649 + getHostNode() {
650 + // Ask the rendered component to provide it.
651 + // This will recursively drill down any composites.
652 + return this.renderedComponent.getHostNode();
653 + }
654 +}
655 +
656 +class DOMComponent {
657 + // ...
658 +
659 + getHostNode() {
660 + return this.node;
661 + }
662 +}
663 +```
664 +
665 +### Updating Host Components
666 +
667 +Host component implementations, such as `DOMComponent`, update differently. When they receive an element, they need to update the underlying platform-specific view. In case of React DOM, this means updating the DOM attributes:
668 +
669 +```js
670 +class DOMComponent {
671 + // ...
672 +
673 + receive(nextElement) {
674 + var node = this.node;
675 + var prevElement = this.currentElement;
676 + var prevProps = prevElement.props;
677 + var nextProps = nextElement.props;
678 + this.currentElement = nextElement;
679 +
680 + // Remove old attributes.
681 + Object.keys(prevProps).forEach(propName => {
682 + if (propName !== 'children' && !nextProps.hasOwnProperty(propName)) {
683 + node.removeAttribute(propName);
684 + }
685 + });
686 + // Set next attributes.
687 + Object.keys(nextProps).forEach(propName => {
688 + if (propName !== 'children') {
689 + node.setAttribute(propName, nextProps[propName]);
690 + }
691 + });
692 +
693 + // ...
694 +```
695 +
696 +Then, host components need to update their children. Unlike composite components, they might contain more than a single child.
697 +
698 +In this simplified example, we use an array of internal instances and iterate over it, either updating or replacing the internal instances depending on whether the received `type` matches their previous `type`. The real reconciler also takes element's `key` in the account and track moves in addition to insertions and deletions, but we will omit this logic.
699 +
700 +We collect DOM operations on children in a list so we can execute them in batch:
701 +
702 +```js
703 + // ...
704 +
705 + // These are arrays of React elements:
706 + var prevChildren = prevProps.children || [];
707 + if (!Array.isArray(prevChildren)) {
708 + prevChildren = [prevChildren];
709 + }
710 + var nextChildren = nextProps.children || [];
711 + if (!Array.isArray(nextChildren)) {
712 + nextChildren = [nextChildren];
713 + }
714 + // These are arrays of internal instances:
715 + var prevRenderedChildren = this.renderedChildren;
716 + var nextRenderedChildren = [];
717 +
718 + // As we iterate over children, we will add operations to the array.
719 + var operationQueue = [];
720 +
721 + // Note: the section below is extremely simplified!
722 + // It doesn't handle reorders, children with holes, or keys.
723 + // It only exists to illustrate the overall flow, not the specifics.
724 +
725 + for (var i = 0; i < nextChildren.length; i++) {
726 + // Try to get an existing internal instance for this child
727 + var prevChild = prevRenderedChildren[i];
728 +
729 + // If there is no internal instance under this index,
730 + // a child has been appended to the end. Create a new
731 + // internal instance, mount it, and use its node.
732 + if (!prevChild) {
733 + var nextChild = instantiateComponent(nextChildren[i]);
734 + var node = nextChild.mount();
735 +
736 + // Record that we need to append a node
737 + operationQueue.push({type: 'ADD', node});
738 + nextRenderedChildren.push(nextChild);
739 + continue;
740 + }
741 +
742 + // We can only update the instance if its element's type matches.
743 + // For example, <Button size="small" /> can be updated to
744 + // <Button size="large" /> but not to an <App />.
745 + var canUpdate = prevChildren[i].type === nextChildren[i].type;
746 +
747 + // If we can't update an existing instance, we have to unmount it
748 + // and mount a new one instead of it.
749 + if (!canUpdate) {
750 + var prevNode = prevChild.node;
751 + prevChild.unmount();
752 +
753 + var nextChild = instantiateComponent(nextChildren[i]);
754 + var nextNode = nextChild.mount();
755 +
756 + // Record that we need to swap the nodes
757 + operationQueue.push({type: 'REPLACE', prevNode, nextNode});
758 + nextRenderedChildren.push(nextChild);
759 + continue;
760 + }
761 +
762 + // If we can update an existing internal instance,
763 + // just let it receive the next element and handle its own update.
764 + prevChild.receive(nextChildren[i]);
765 + nextRenderedChildren.push(prevChild);
766 + }
767 +
768 + // Finally, unmount any children that don't exist:
769 + for (var j = nextChildren.length; j < prevChildren.length; j++) {
770 + var prevChild = prevRenderedChildren[j];
771 + var node = prevChild.node;
772 + prevChild.unmount();
773 +
774 + // Record that we need to remove the node
775 + operationQueue.push({type: 'REMOVE', node});
776 + }
777 +
778 + // Point the list of rendered children to the updated version.
779 + this.renderedChildren = nextRenderedChildren;
780 +
781 + // ...
782 +```
783 +
784 +As the last step, we execute the DOM operations. Again, the real reconciler code is more complex because it also handles moves:
785 +
786 +```js
787 + // ...
788 +
789 + // Process the operation queue.
790 + while (operationQueue.length > 0) {
791 + var operation = operationQueue.shift();
792 + switch (operation.type) {
793 + case 'ADD':
794 + this.node.appendChild(operation.node);
795 + break;
796 + case 'REPLACE':
797 + this.node.replaceChild(operation.nextNode, operation.prevNode);
798 + break;
799 + case 'REMOVE':
800 + this.node.removeChild(operation.node);
801 + break;
802 + }
803 + }
804 + }
805 +}
806 +```
807 +
808 +And that is it for updating host components.
809 +
810 +### Top-Level Updates
811 +
812 +Now that both `CompositeComponent` and `DOMComponent` implement the `receive(nextElement)` method, we can change the top-level `mountTree()` function to use it when the element `type` is the same as it was the last time:
813 +
814 +```js
815 +function mountTree(element, containerNode) {
816 + // Check for an existing tree
817 + if (containerNode.firstChild) {
818 + var prevNode = containerNode.firstChild;
819 + var prevRootComponent = prevNode._internalInstance;
820 + var prevElement = prevRootComponent.currentElement;
821 +
822 + // If we can, reuse the existing root component
823 + if (prevElement.type === element.type) {
824 + prevRootComponent.receive(element);
825 + return;
826 + }
827 +
828 + // Otherwise, unmount the existing tree
829 + unmountTree(containerNode);
830 + }
831 +
832 + // ...
833 +
834 +}
835 +```
836 +
837 +Now calling `mountTree()` two times with the same type isn't destructive:
838 +
839 +```js
840 +var rootEl = document.getElementById('root');
841 +
842 +mountTree(<App />, rootEl);
843 +// Reuses the existing DOM:
844 +mountTree(<App />, rootEl);
845 +```
846 +
847 +These are the basics of how React works internally.
848 +
849 +### What We Left Out
850 +
851 +This document is simplified compared to the real codebase. There are a few important aspects we didn't address:
852 +
853 +* Components can render `null`, and the reconciler can handle "empty slots" in arrays and rendered output.
854 +
855 +* The reconciler also reads `key` from the elements, and uses it to establish which internal instance corresponds to which element in an array. A bulk of complexity in the actual React implementation is related to that.
856 +
857 +* In addition to composite and host internal instance classes, there are also classes for "text" and "empty" components. They represent text nodes and the "empty slots" you get by rendering `null`.
858 +
859 +* Renderers use [injection](/react/contributing/codebase-overview.html#dynamic-injection) to pass the host internal class to the reconciler. For example, React DOM tells the reconciler to use `ReactDOMComponent` as the host internal instance implementation.
860 +
861 +* The logic for updating the list of children is extracted into a mixin called `ReactMultiChild` which is used by the host internal instance class implementations both in React DOM and React Native.
862 +
863 +* The reconciler also implements support for `setState()` in composite components. Multiple updates inside event handlers get batched into a single update.
864 +
865 +* The reconciler also takes care of attaching and detaching refs to composite components and host nodes.
866 +
867 +* Lifecycle hooks that are called after the DOM is ready, such as `componentDidMount()` and `componentDidUpdate()`, get collected into "callback queues" and are executed in a single batch.
868 +
869 +* React puts information about the current update into an internal object called "transaction". Transactions are useful for keeping track of the queue of pending lifecycle hooks, the current DOM nesting for the warnings, and anything else that is "global" to a specific update. Transactions also ensure React "cleans everything up" after updates. For example, the transaction class provided by React DOM restores the input selection after any update.
870 +
871 +### Jumping into the Code
872 +
873 +* [`ReactMount`](https://github.com/facebook/react/blob/83381c1673d14cd16cf747e34c945291e5518a86/src/renderers/dom/client/ReactMount.js) is where the code like `mountTree()` and `unmountTree()` from this tutorial lives. It takes care of mounting and unmounting top-level components. [`ReactNativeMount`](https://github.com/facebook/react/blob/83381c1673d14cd16cf747e34c945291e5518a86/src/renderers/native/ReactNativeMount.js) is its React Native analog.
874 +* [`ReactDOMComponent`](https://github.com/facebook/react/blob/83381c1673d14cd16cf747e34c945291e5518a86/src/renderers/dom/shared/ReactDOMComponent.js) is the equivalent of `DOMComponent` in this tutorial. It implements the host component class for React DOM renderer. [`ReactNativeBaseComponent`](https://github.com/facebook/react/blob/83381c1673d14cd16cf747e34c945291e5518a86/src/renderers/native/ReactNativeBaseComponent.js) is its React Native analog.
875 +* [`ReactCompositeComponent`](https://github.com/facebook/react/blob/83381c1673d14cd16cf747e34c945291e5518a86/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js) is the equivalent of `CompositeComponent` in this tutorial. It handles calling user-defined components and maintaining their state.
876 +* [`instantiateReactComponent`](https://github.com/facebook/react/blob/83381c1673d14cd16cf747e34c945291e5518a86/src/renderers/shared/stack/reconciler/instantiateReactComponent.js) contains the switch that picks the right internal instance class to construct for an element. It is equivalent to `instantiateComponent()` in this tutorial.
877 +
878 +* [`ReactReconciler`](https://github.com/facebook/react/blob/83381c1673d14cd16cf747e34c945291e5518a86/src/renderers/shared/stack/reconciler/ReactReconciler.js) is a wrapper with `mountComponent`, `receiveComponent()`, and `unmountComponent()` methods. It calls the underlying implementations on the internal instances, but also includes some code around them that is shared by all internal instance implementations.
879 +
880 +* [`ReactChildReconciler`](https://github.com/facebook/react/blob/83381c1673d14cd16cf747e34c945291e5518a86/src/renderers/shared/stack/reconciler/ReactChildReconciler.js) implements the logic for mounting, updating, and unmounting children according to the `key` of their elements.
881 +
882 +* [`ReactMultiChild`](https://github.com/facebook/react/blob/83381c1673d14cd16cf747e34c945291e5518a86/src/renderers/shared/stack/reconciler/ReactMultiChild.js) implements processing the operation queue for child insertions, deletions, and moves independently of the renderer.
883 +
884 +* `mount()`, `receive()`, and `unmount()` are really called `mountComponent()`, `receiveComponent()`, and `unmountComponent()` in React codebase for legacy reasons, but they receive elements.
885 +
886 +* Properties on the internal instances start with an underscore, e.g. `_currentElement`. They are considered to be read-only public fields throughout the codebase.
887 +
888 +### Future Directions
889 +
890 +Stack reconciler has inherent limitations such as being synchronous and unable to interrupt the work or split it in chunks. There is a work in progress on the [new Fiber reconciler](/react/contributing/codebase-overview.html#fiber-reconciler) with a [completely different architecture](https://github.com/acdlite/react-fiber-architecture). In the future, we intend to replace stack reconciler with it, but at the moment it is far from feature parity.
891 +
892 +### Next Steps
893 +
894 +Read the [next section](/react/contributing/design-principles.html) to learn about the guiding principles we use for React development.
docs/img/docs/implementation-notes-tree.png
Binary files /dev/null and b/docs/img/docs/implementation-notes-tree.png differ