Formatting updates.

master
Thomas Hintz 4 years ago
parent b470727093
commit ba4f38c5a8

@ -12,6 +12,7 @@
#+TITLE: Foundations of High-Performance React Applications #+TITLE: Foundations of High-Performance React Applications
#+AUTHOR: Thomas Hintz #+AUTHOR: Thomas Hintz
#+EXCLUDE_TAGS: noexport
#+startup: indent #+startup: indent
#+tags: noexport sample frontmatter mainmatter backmatter #+tags: noexport sample frontmatter mainmatter backmatter
@ -196,7 +197,8 @@ This is what I'm thinking:
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
['div', { 'className': 'header' }, ['div', { 'className': 'header' },
[['h1', {}, ['Hello']], [['h1', {}, ['Hello']],
['input', { 'type': 'submit', 'disabled': 'disabled' }, []] ['input', { 'type': 'submit', 'disabled': 'disabled' },
[]]
] ]
] ]
#+END_SRC #+END_SRC
@ -345,10 +347,10 @@ focusing on the initial render. In pseudocode it looks like this:
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
function render(element, container) { function render(element, container) {
const domElement = createDOMElement(element); const domElement = createDOMElement(element);
setProps(element, domElement); setProps(element, domElement);
renderChildren(element, domElement); renderChildren(element, domElement);
container.appendChild(domElement); container.appendChild(domElement);
#+END_SRC #+END_SRC
Our DOM element is created first. Then we set the properties, render Our DOM element is created first. Then we set the properties, render
@ -367,23 +369,24 @@ exploring it after we work out the initial render.
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
function render(element, container) { function render(element, container) {
const { type, props } = element; const { type, props } = element;
// create the DOM element // create the DOM element
const domElement = type === 'TEXT' ? const domElement = type === 'TEXT' ?
document.createTextNode(props.nodeValue) : document.createTextNode(props.nodeValue) :
document.createElement(type); document.createElement(type);
// set its properties // set its properties
Object.keys(props) Object.keys(props)
.filter((key) => key !== 'children') .filter((key) => key !== 'children')
.forEach((key) => domElement[key] = props[key]); .forEach((key) => domElement[key] = props[key]);
// render its children // render its children
props.children.forEach((child) => render(child, domElement)); props.children.forEach((child) =>
render(child, domElement));
// add our tree to the DOM! // add our tree to the DOM!
container.appendChild(domElement); container.appendChild(domElement);
} }
#+END_SRC #+END_SRC
@ -464,14 +467,14 @@ from the current tree and ~prevElement~ is the corresponding element
in the tree from the previous render. in the tree from the previous render.
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
if (!element && prevElement) if (!element && prevElement)
// delete dom element // delete dom element
else if (element && !prevElement) else if (element && !prevElement)
// add new dom element, render children // add new dom element, render children
else if (element.type === prevElement.type) else if (element.type === prevElement.type)
// update dom element, render children // update dom element, render children
else if (element.type !== prevElement.type) else if (element.type !== prevElement.type)
// replace dom element, render children // replace dom element, render children
#+END_SRC #+END_SRC
Notice that in every case, except deletion, we still call ~render~ on Notice that in every case, except deletion, we still call ~render~ on
@ -497,10 +500,10 @@ tree, keyed by the ~container~.
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
const renderTrees = {}; const renderTrees = {};
function render(element, container) { function render(element, container) {
const tree = const tree =
render_internal(element, container, renderTrees[container]); render_internal(element, container, renderTrees[container]);
// render complete, store the updated tree // render complete, store the updated tree
renderTrees[container] = tree; renderTrees[container] = tree;
} }
#+END_SRC #+END_SRC
@ -522,34 +525,34 @@ delete a node that previously existed.
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
function render_internal(element, container, prevElement) { function render_internal(element, container, prevElement) {
let domElement, children; let domElement, children;
if (!element && prevElement) { if (!element && prevElement) {
removeDOMElement(prevElement); removeDOMElement(prevElement);
return; return;
} else if (element && !prevElement) { } else if (element && !prevElement) {
domElement = createDOMElement(element); domElement = createDOMElement(element);
} else if (element.type === prevElement.type) { } else if (element.type === prevElement.type) {
domElement = prevElement.domElement; domElement = prevElement.domElement;
} else { // types don't match } else { // types don't match
removeDOMElement(prevElement); removeDOMElement(prevElement);
domElement = createDOMElement(element); domElement = createDOMElement(element);
} }
setDOMProps(element, domElement, prevElement); setDOMProps(element, domElement, prevElement);
children = renderChildren(element, domElement, prevElement); children = renderChildren(element, domElement, prevElement);
if (!prevElement || domElement !== prevElement.domElement) { if (!prevElement || domElement !== prevElement.domElement) {
container.appendChild(domElement); container.appendChild(domElement);
} }
return { return {
domElement: domElement, domElement: domElement,
parent: container, parent: container,
type: element.type, type: element.type,
props: { props: {
...element.props, ...element.props,
children: children children: children
} }
}; };
} }
#+END_SRC #+END_SRC
@ -577,7 +580,7 @@ the parent element. Before removing the element, React would invoke
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
function removeDOMElement(prevElement) { function removeDOMElement(prevElement) {
prevElement.parent.removeChild(prevElement.domElement); prevElement.parent.removeChild(prevElement.domElement);
} }
#+END_SRC #+END_SRC
@ -590,9 +593,9 @@ will set it again. This is where React would invoke
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
function createDOMElement(element) { function createDOMElement(element) {
return element.type === 'TEXT' ? return element.type === 'TEXT' ?
document.createTextNode(element.props.nodeValue) : document.createTextNode(element.props.nodeValue) :
document.createElement(element.type); document.createElement(element.type);
} }
#+END_SRC #+END_SRC
@ -603,18 +606,18 @@ and it isn't intended to be set directly.
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
function setDOMProps(element, domElement, prevElement) { function setDOMProps(element, domElement, prevElement) {
if (prevElement) { if (prevElement) {
Object.keys(prevElement.props) Object.keys(prevElement.props)
.filter((key) => key !== 'children') .filter((key) => key !== 'children')
.forEach((key) => { .forEach((key) => {
domElement[key] = ''; // clear prop domElement[key] = ''; // clear prop
}); });
} }
Object.keys(element.props) Object.keys(element.props)
.filter((key) => key !== 'children') .filter((key) => key !== 'children')
.forEach((key) => { .forEach((key) => {
domElement[key] = element.props[key]; domElement[key] = element.props[key];
}); });
} }
#+END_SRC #+END_SRC
@ -640,17 +643,17 @@ corresponding element, like when the list of children has grown.
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
function renderChildren(element, domElement, prevElement = { props: { children: [] }}) { function renderChildren(element, domElement, prevElement = { props: { children: [] }}) {
const elementLen = element.props.children.length; const elementLen = element.props.children.length;
const prevElementLen = prevElement.props.children.length; const prevElementLen = prevElement.props.children.length;
// remove now unused elements // remove now unused elements
for (let i = elementLen; i < prevElementLen - elementLen; i++) { for (let i = elementLen; i < prevElementLen - elementLen; i++) {
removeDOMElement(element.props.children[i]); removeDOMElement(element.props.children[i]);
} }
// render existing and new elements // render existing and new elements
return element.props.children.map((child, i) => { return element.props.children.map((child, i) => {
const prevChild = i < prevElementLen ? prevElement.props.children[i] : undefined; const prevChild = i < prevElementLen ? prevElement.props.children[i] : undefined;
return render_internal(child, domElement, prevChild); return render_internal(child, domElement, prevChild);
}); });
} }
#+END_SRC #+END_SRC
@ -719,19 +722,19 @@ At this point the only thing left to do is to create some components
and use them! and use them!
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
const SayNow = ({ dateTime }) => { const SayNow = ({ dateTime }) => {
return ['h1', {}, [`It is: ${dateTime}`]]; return ['h1', {}, [`It is: ${dateTime}`]];
}; };
const App = () => { const App = () => {
return ['div', { 'className': 'header' }, return ['div', { 'className': 'header' },
[SayNow({ dateTime: new Date() }), [SayNow({ dateTime: new Date() }),
['input', { 'type': 'submit', 'disabled': 'disabled' }, []] ['input', { 'type': 'submit', 'disabled': 'disabled' }, []]
] ]
]; ];
} }
render(createElement(App()), document.getElementById('root')); render(createElement(App()), document.getElementById('root'));
#+END_SRC #+END_SRC
We are creating two components, that output JSM, as we defined it We are creating two components, that output JSM, as we defined it
@ -745,8 +748,8 @@ The next step is to call render multiple times.
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
setInterval(() => setInterval(() =>
render(createElement(App()), document.getElementById('root')), render(createElement(App()), document.getElementById('root')),
1000); 1000);
#+END_SRC #+END_SRC
If you run the code above you will see the DateTime display being If you run the code above you will see the DateTime display being
@ -787,7 +790,7 @@ cause of a performance bottleneck? Or how do you use the React APIs in
a performant way? These types of questions should be easier to track a performant way? These types of questions should be easier to track
down and understand with the foundations covered and I hope this is down and understand with the foundations covered and I hope this is
only the start of your High-Performance React journey. only the start of your High-Performance React journey.
* Image Test * Image Test :noexport:
:PROPERTIES: :PROPERTIES:
:EXPORT_FILE_NAME: manuscript/image-test.markua :EXPORT_FILE_NAME: manuscript/image-test.markua
:END: :END:

@ -10,4 +10,3 @@ reconciliation.markua
fibers.markua fibers.markua
putting-it-all-together.markua putting-it-all-together.markua
conclusion.markua conclusion.markua
image-test.markua

@ -34,7 +34,8 @@ This is what I'm thinking:
``` ```
['div', { 'className': 'header' }, ['div', { 'className': 'header' },
[['h1', {}, ['Hello']], [['h1', {}, ['Hello']],
['input', { 'type': 'submit', 'disabled': 'disabled' }, []] ['input', { 'type': 'submit', 'disabled': 'disabled' },
[]]
] ]
] ]
``` ```

@ -5,15 +5,15 @@ At this point the only thing left to do is to create some components and use the
{format: "javascript"} {format: "javascript"}
``` ```
const SayNow = ({ dateTime }) => { const SayNow = ({ dateTime }) => {
return ['h1', {}, [`It is: ${dateTime}`]]; return ['h1', {}, [`It is: ${dateTime}`]];
}; };
const App = () => { const App = () => {
return ['div', { 'className': 'header' }, return ['div', { 'className': 'header' },
[SayNow({ dateTime: new Date() }), [SayNow({ dateTime: new Date() }),
['input', { 'type': 'submit', 'disabled': 'disabled' }, []] ['input', { 'type': 'submit', 'disabled': 'disabled' }, []]
] ]
]; ];
} }
render(createElement(App()), document.getElementById('root')); render(createElement(App()), document.getElementById('root'));
@ -26,8 +26,8 @@ The next step is to call render multiple times.
{format: "javascript"} {format: "javascript"}
``` ```
setInterval(() => setInterval(() =>
render(createElement(App()), document.getElementById('root')), render(createElement(App()), document.getElementById('root')),
1000); 1000);
``` ```
If you run the code above you will see the DateTime display being updated every second. And if you watch in your dev tools or if you profile the run you will see that the only part of the DOM that gets updated or replaced is the part that changes (aside from the DOM props). We now have a working version of our own React. If you run the code above you will see the DateTime display being updated every second. And if you watch in your dev tools or if you profile the run you will see that the only part of the DOM that gets updated or replaced is the part that changes (aside from the DOM props). We now have a working version of our own React.

@ -26,13 +26,13 @@ While keeping that in mind, here is an overview of the algorithm we will be impl
{format: "javascript"} {format: "javascript"}
``` ```
if (!element && prevElement) if (!element && prevElement)
// delete dom element // delete dom element
else if (element && !prevElement) else if (element && !prevElement)
// add new dom element, render children // add new dom element, render children
else if (element.type === prevElement.type) else if (element.type === prevElement.type)
// update dom element, render children // update dom element, render children
else if (element.type !== prevElement.type) else if (element.type !== prevElement.type)
// replace dom element, render children // replace dom element, render children
``` ```
Notice that in every case, except deletion, we still call `render` on the element's children. And while it's possible that the children will have their associated DOM elements reused, their `render` methods will still be invoked. Notice that in every case, except deletion, we still call `render` on the element's children. And while it's possible that the children will have their associated DOM elements reused, their `render` methods will still be invoked.
@ -45,10 +45,10 @@ Here we begin by adding a global object that will store our last render tree, ke
``` ```
const renderTrees = {}; const renderTrees = {};
function render(element, container) { function render(element, container) {
const tree = const tree =
render_internal(element, container, renderTrees[container]); render_internal(element, container, renderTrees[container]);
// render complete, store the updated tree // render complete, store the updated tree
renderTrees[container] = tree; renderTrees[container] = tree;
} }
``` ```
@ -59,34 +59,34 @@ Now that we have stored our last render tree we can go ahead and update our rend
{format: "javascript"} {format: "javascript"}
``` ```
function render_internal(element, container, prevElement) { function render_internal(element, container, prevElement) {
let domElement, children; let domElement, children;
if (!element && prevElement) { if (!element && prevElement) {
removeDOMElement(prevElement); removeDOMElement(prevElement);
return; return;
} else if (element && !prevElement) { } else if (element && !prevElement) {
domElement = createDOMElement(element); domElement = createDOMElement(element);
} else if (element.type === prevElement.type) { } else if (element.type === prevElement.type) {
domElement = prevElement.domElement; domElement = prevElement.domElement;
} else { // types don't match } else { // types don't match
removeDOMElement(prevElement); removeDOMElement(prevElement);
domElement = createDOMElement(element); domElement = createDOMElement(element);
} }
setDOMProps(element, domElement, prevElement); setDOMProps(element, domElement, prevElement);
children = renderChildren(element, domElement, prevElement); children = renderChildren(element, domElement, prevElement);
if (!prevElement || domElement !== prevElement.domElement) { if (!prevElement || domElement !== prevElement.domElement) {
container.appendChild(domElement); container.appendChild(domElement);
} }
return { return {
domElement: domElement, domElement: domElement,
parent: container, parent: container,
type: element.type, type: element.type,
props: { props: {
...element.props, ...element.props,
children: children children: children
} }
}; };
} }
``` ```
@ -101,7 +101,7 @@ Removing a DOM element is straightforward; we just `removeChild` on the parent e
{format: "javascript"} {format: "javascript"}
``` ```
function removeDOMElement(prevElement) { function removeDOMElement(prevElement) {
prevElement.parent.removeChild(prevElement.domElement); prevElement.parent.removeChild(prevElement.domElement);
} }
``` ```
@ -110,9 +110,9 @@ In creating a new DOM element we just need to branch if we are creating a text e
{format: "javascript"} {format: "javascript"}
``` ```
function createDOMElement(element) { function createDOMElement(element) {
return element.type === 'TEXT' ? return element.type === 'TEXT' ?
document.createTextNode(element.props.nodeValue) : document.createTextNode(element.props.nodeValue) :
document.createElement(element.type); document.createElement(element.type);
} }
``` ```
@ -121,18 +121,18 @@ To set the props on an element, we first clear all the existing props and then l
{format: "javascript"} {format: "javascript"}
``` ```
function setDOMProps(element, domElement, prevElement) { function setDOMProps(element, domElement, prevElement) {
if (prevElement) { if (prevElement) {
Object.keys(prevElement.props) Object.keys(prevElement.props)
.filter((key) => key !== 'children') .filter((key) => key !== 'children')
.forEach((key) => { .forEach((key) => {
domElement[key] = ''; // clear prop domElement[key] = ''; // clear prop
}); });
} }
Object.keys(element.props) Object.keys(element.props)
.filter((key) => key !== 'children') .filter((key) => key !== 'children')
.forEach((key) => { .forEach((key) => {
domElement[key] = element.props[key]; domElement[key] = element.props[key];
}); });
} }
``` ```
@ -145,17 +145,17 @@ For rendering children we use two loops. The first loop removes any elements tha
{format: "javascript"} {format: "javascript"}
``` ```
function renderChildren(element, domElement, prevElement = { props: { children: [] }}) { function renderChildren(element, domElement, prevElement = { props: { children: [] }}) {
const elementLen = element.props.children.length; const elementLen = element.props.children.length;
const prevElementLen = prevElement.props.children.length; const prevElementLen = prevElement.props.children.length;
// remove now unused elements // remove now unused elements
for (let i = elementLen; i < prevElementLen - elementLen; i++) { for (let i = elementLen; i < prevElementLen - elementLen; i++) {
removeDOMElement(element.props.children[i]); removeDOMElement(element.props.children[i]);
} }
// render existing and new elements // render existing and new elements
return element.props.children.map((child, i) => { return element.props.children.map((child, i) => {
const prevChild = i < prevElementLen ? prevElement.props.children[i] : undefined; const prevChild = i < prevElementLen ? prevElement.props.children[i] : undefined;
return render_internal(child, domElement, prevChild); return render_internal(child, domElement, prevChild);
}); });
} }
``` ```

@ -14,10 +14,10 @@ This is the same signature as that of React itself. We begin by just focusing on
{format: "javascript"} {format: "javascript"}
``` ```
function render(element, container) { function render(element, container) {
const domElement = createDOMElement(element); const domElement = createDOMElement(element);
setProps(element, domElement); setProps(element, domElement);
renderChildren(element, domElement); renderChildren(element, domElement);
container.appendChild(domElement); container.appendChild(domElement);
``` ```
Our DOM element is created first. Then we set the properties, render children elements, and finally append the whole tree to the container. Our DOM element is created first. Then we set the properties, render children elements, and finally append the whole tree to the container.
@ -29,23 +29,24 @@ Now that we have an idea of what to build we will work on expanding the pseudoco
{format: "javascript"} {format: "javascript"}
``` ```
function render(element, container) { function render(element, container) {
const { type, props } = element; const { type, props } = element;
// create the DOM element // create the DOM element
const domElement = type === 'TEXT' ? const domElement = type === 'TEXT' ?
document.createTextNode(props.nodeValue) : document.createTextNode(props.nodeValue) :
document.createElement(type); document.createElement(type);
// set its properties // set its properties
Object.keys(props) Object.keys(props)
.filter((key) => key !== 'children') .filter((key) => key !== 'children')
.forEach((key) => domElement[key] = props[key]); .forEach((key) => domElement[key] = props[key]);
// render its children // render its children
props.children.forEach((child) => render(child, domElement)); props.children.forEach((child) =>
render(child, domElement));
// add our tree to the DOM! // add our tree to the DOM!
container.appendChild(domElement); container.appendChild(domElement);
} }
``` ```

Loading…
Cancel
Save