@ -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: