Incorporating Brian's proofing edits.
This commit is contained in:
BIN
foundations-high-performance-react - Brian C proofed.docx
Normal file
BIN
foundations-high-performance-react - Brian C proofed.docx
Normal file
Binary file not shown.
@@ -25,56 +25,56 @@
|
|||||||
:END:
|
:END:
|
||||||
|
|
||||||
Welcome to /Foundations of High-Performance React Applications/ where
|
Welcome to /Foundations of High-Performance React Applications/ where
|
||||||
we build our own simplified version of React. We will use our React to
|
we build our own simplified version of React. We’ll use our React to
|
||||||
gain an understanding of the real React and how to build
|
gain an understanding of the real React and how to build
|
||||||
high-performance applications with it.
|
high-performance applications with it.
|
||||||
|
|
||||||
This book is based on the first chapter of the book /High-Performance
|
This book is based on the first chapter of the book /High-Performance
|
||||||
React/. If you enjoy this book and you want to learn more practical
|
React/. If you enjoy this book and you want to learn more practical
|
||||||
ways to utilize the foundations we will learn here and get a more
|
ways to utilize the foundations we’ll learn here and get a more
|
||||||
detailed blue-print for creating high performance React applications,
|
detailed blueprint for creating high performance React applications,
|
||||||
then be sure to check out /High-Performance React/.
|
then be sure to check out /High-Performance React/.
|
||||||
|
|
||||||
This book is not intended to be an introduction to React or
|
This book is not intended to be an introduction to React or
|
||||||
JavaScript. While it might be useful to beginners, this book assumes
|
JavaScript. While it might be useful to beginners, this book assumes
|
||||||
familiarity with both JavaScript and React.
|
familiarity with both JavaScript and React.
|
||||||
|
|
||||||
And while this book only specifically addresses React-DOM the
|
And while this book only specifically addresses React-DOM, the
|
||||||
foundations apply equally to React-Native and other React
|
foundations apply equally to React-Native and other React
|
||||||
implementations because they are all based on the same core React
|
implementations because they are all based on the same core React
|
||||||
library and algorithms.
|
library and algorithms.
|
||||||
|
|
||||||
For the code in this book, the goal is to be clear and simple; to best
|
The code in this book is clear and simple so as to best
|
||||||
communicate the algorithms we will be exploring. It is not intended to
|
communicate the algorithms we’ll be exploring. It is not intended to
|
||||||
be used in production but it is functional and you will likely find it
|
be used in production, but it is functional. I think you’ll likely find it
|
||||||
useful to follow along by also writing the code yourself. It will help
|
useful to follow along by writing the code yourself. It will help
|
||||||
you better understand how it works, and even more critically, it will
|
you better understand how it works, and even more critically, it will
|
||||||
allow you to play with it and test how the algorithms work with your
|
allow you to play with it and test how the algorithms work with your
|
||||||
own examples.
|
own examples.
|
||||||
|
|
||||||
Even if you don't write out the code yourself and instead treat this
|
Even if you don't write out the code yourself and,instead, read through this
|
||||||
book more as a novel you read through and then move on, I believe the
|
book more like a novel, I believe the
|
||||||
fundamentals should still stick with you and provide value in your
|
fundamentals will still stick with you and provide value in your
|
||||||
React programs-to-come.
|
React programs-to-come.
|
||||||
|
|
||||||
I'm very excited to take you on this journey with me and now it's time
|
I'm very excited to take you on this journey with me and, so, now it's time
|
||||||
to learn what lays at the very foundation of React.
|
to learn what lies at the very foundation of React.
|
||||||
|
|
||||||
* Acknowledgments
|
* Acknowledgments
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:EXPORT_FILE_NAME: manuscript/acknowledgments.markua
|
:EXPORT_FILE_NAME: manuscript/acknowledgments.markua
|
||||||
:END:
|
:END:
|
||||||
First, I'd like to thank my partner Laura, for always supporting me in
|
First, I'd like to thank my partner Laura for always supporting me in
|
||||||
whatever endeavors I embark upon even if they're new, challenging, or
|
whatever endeavors I embark upon, whether they're new, challenging, or
|
||||||
scary. This book, nor my work with React, would have taken place if it
|
scary. This book and my work with React wouldn’t have taken place if it
|
||||||
weren't for her support and strength.
|
weren't for her support and strength.
|
||||||
|
|
||||||
Second, I would like to thank my friend, Timothy Licata, for providing
|
Second, I would like to thank my friend Timothy Licata for providing
|
||||||
invaluable feedback on earlier versions and always pushing me to
|
invaluable feedback on earlier versions and always pushing me to
|
||||||
explore new ways of using Emacs, such as writing this book.
|
explore new ways of using Emacs, such as writing this book.
|
||||||
|
|
||||||
And last, I would like to thank the wider JavaScript and React
|
And last, I would like to thank the wider JavaScript and React
|
||||||
community for providing many years of work to build upon and
|
community for providing many years of work to build upon,
|
||||||
specifically Rodrigo Pombo's "Build Your Own React" for being the
|
specifically Rodrigo Pombo's "Build Your Own React" for being the
|
||||||
inspiration for a lot of this book's content.
|
inspiration for a lot of this book's content.
|
||||||
* Introduction :mainmatter:
|
* Introduction :mainmatter:
|
||||||
@@ -82,40 +82,39 @@ inspiration for a lot of this book's content.
|
|||||||
:EXPORT_FILE_NAME: manuscript/introduction.markua
|
:EXPORT_FILE_NAME: manuscript/introduction.markua
|
||||||
:END:
|
:END:
|
||||||
|
|
||||||
Baking bread. When I first began to learn how to bake bread the recipe
|
When I first began to learn how to bake bread, a recipe
|
||||||
told me what to do. It listed some ingredients and told me how to
|
told me what to do. It listed some ingredients, told me how to
|
||||||
combine them and prescribed times of rest. It gave me an oven
|
combine them, and prescribed times of rest. It gave me an oven
|
||||||
temperature and a period of wait. It gave me mediocre bread of wildly
|
temperature and a period of wait. It gave me mediocre bread of wildly
|
||||||
varying quality. I tried different recipes but the result was always
|
varying quality. I tried different recipes, but the result was always
|
||||||
the same.
|
the same.
|
||||||
|
|
||||||
Understanding: that's what I was missing. The bread I make is now
|
Understanding: that's what I was missing. The bread I make is now
|
||||||
consistently good. The recipes I use are simpler and only give ratios
|
consistently good. The recipes I use are simpler and only give ratios
|
||||||
and general recommendations for rests and waits. So why does the bread
|
and general recommendations for rests and waits. So, why does the bread
|
||||||
turn out better?
|
now turn out better?
|
||||||
|
|
||||||
Before baking is finished bread is a living organism. The way it grows
|
Before it is baked, bread is a living organism. So, the way it grows,
|
||||||
and develops and flavors depend on what you feed it and how you feed
|
develops, and flavors depends on what you feed it, how you feed it,
|
||||||
it and massage it, care for it. If you have it grow and ferment at a
|
how you massage it, and how you care for it. If you have it grow and
|
||||||
higher temperature and more yeast it overdevelops producing too much
|
ferment with more yeast at a higher temperature, it
|
||||||
alcohol. If you give it too much time, acidity will take over the
|
overdevelops, producing too much alcohol. If you give it too much time,
|
||||||
flavor. The recipes I used initially were missing a critical
|
acidity will take over the flavor. The recipes I used initially were
|
||||||
ingredient: the rising temperature.
|
missing a critical ingredient: the rising temperature.
|
||||||
|
|
||||||
But unlike a lot of ingredients: temperature is hard to control for
|
But unlike other ingredients, temperature is hard for the home cook to
|
||||||
the home cook. So the recipe can't just tell you exactly what
|
control. And recipes don’t say exactly at which temperature to grow
|
||||||
temperature to grow the bread at. My initial recipes just silently
|
the bread. My initial recipes just silently made assumptions about the
|
||||||
made assumptions for the temperature, which rarely turn out to be
|
temperature, which rarely worked. This means the only way to
|
||||||
true. This means that the only way to consistently make good bread is
|
consistently make good bread is to have an understanding of how bread
|
||||||
to have an understanding of how bread develops so that you can adjust
|
develops so that you can adjust the other ingredients to complement
|
||||||
the other ingredients to complement the temperature. Now the bread can
|
the temperature. Now the bread can tell me what to do.
|
||||||
tell me what to do.
|
|
||||||
|
|
||||||
While React isn't technically a living organism that can tell us what
|
While React isn't technically a living organism that can tell us what
|
||||||
to do, it is, in its whole, a complex, abstract entity. We could learn
|
to do, it is, in its whole, a complex, abstract entity. We could learn
|
||||||
basic recipes for how to write high-performance React code but they
|
basic recipes for how to write high-performance React code, but they
|
||||||
wouldn't apply in all cases, and as React and things under it change
|
wouldn't apply in all cases. And as React and things under it change,
|
||||||
our recipes would fall out-of-date. So like the bread, to produce
|
our recipes would fall out-of-date. So, like bread, to produce
|
||||||
consistently good results we need to understand how React does what it
|
consistently good results we need to understand how React does what it
|
||||||
does.
|
does.
|
||||||
|
|
||||||
@@ -139,19 +138,19 @@ differences it updates the browser's DOM to match its internal tree.
|
|||||||
But what does that actually look like? If your app is janky does that
|
But what does that actually look like? If your app is janky does that
|
||||||
explanation point you towards what is wrong? No. It might make you
|
explanation point you towards what is wrong? No. It might make you
|
||||||
wonder if maybe it is too expensive to re-render the tree or if maybe
|
wonder if maybe it is too expensive to re-render the tree or if maybe
|
||||||
the diffing React does is slow but you won't really know. When I was
|
the diffing React does is slow, but you won't really know. When I was
|
||||||
initially testing out different bread recipes I had guesses at why it
|
initially testing out different bread recipes I had guesses at why it
|
||||||
wasn't working but I didn't really figure it out until I had a deeper
|
wasn't working, but I didn't really figure it out until I had a deeper
|
||||||
understanding of how making bread worked. It's time we build up our
|
understanding of how making bread worked. It's time we build up our
|
||||||
understanding of how React works so that we can start to answer our
|
understanding of how React works so that we can start to answer our
|
||||||
questions with solid answers.
|
questions with solid answers.
|
||||||
|
|
||||||
React is centered around the ~render~ method. The ~render~ method is
|
React is centered on the ~render~ method. The ~render~ method is
|
||||||
what walks our trees, diffs them with the browser's DOM tree, and
|
what walks our trees, diffs them with the browser's DOM tree, and
|
||||||
updates the DOM as needed. But before we can look at the ~render~
|
updates the DOM as needed. But before we can look at the ~render~
|
||||||
method we have to understand its input. The input comes from
|
method we have to understand its input. The input comes from
|
||||||
~createElement~. While ~createElement~ itself is unlikely to be a
|
~createElement~. While ~createElement~ itself is unlikely to be a
|
||||||
bottleneck it's good to understand how it works so that we can have a
|
bottleneck, it's good to understand how it works so that we can have a
|
||||||
complete picture of the entire process. The more black-boxes we have
|
complete picture of the entire process. The more black-boxes we have
|
||||||
in our mental model the harder it will be for us to diagnose
|
in our mental model the harder it will be for us to diagnose
|
||||||
performance problems.
|
performance problems.
|
||||||
@@ -165,14 +164,14 @@ performance problems.
|
|||||||
not familiar to us since we usually work in JSX, which is the last
|
not familiar to us since we usually work in JSX, which is the last
|
||||||
element of the chain in this puzzle and the first step in solving
|
element of the chain in this puzzle and the first step in solving
|
||||||
it. While not strictly a part of React, it is almost universally used
|
it. While not strictly a part of React, it is almost universally used
|
||||||
with it. And if we understand it then ~createElement~ will be less of
|
with it. And if we understand it, ~createElement~ will then be less of
|
||||||
a mystery since we will be able to connect all the dots.
|
a mystery since we’ll be able to connect all the dots.
|
||||||
|
|
||||||
JSX is not valid HTML or JavaScript but its own language compiled by a
|
JSX is not valid HTML or JavaScript but its own language compiled by a
|
||||||
compiler, like Babel. The output of that compilation is valid
|
compiler, like Babel. The output of that compilation is valid
|
||||||
JavaScript that represents the original markup.
|
JavaScript that represents the original markup.
|
||||||
|
|
||||||
Before JSX or similar compilers, the normal way of injecting HTML into
|
Before JSX or similar compilers, the typical way of injecting HTML into
|
||||||
the DOM was via directly utilizing the browser's DOM APIs or by
|
the DOM was via directly utilizing the browser's DOM APIs or by
|
||||||
setting ~innerHTML~. This was very cumbersome. The code's structure
|
setting ~innerHTML~. This was very cumbersome. The code's structure
|
||||||
did not match the structure of the HTML that it output which made it
|
did not match the structure of the HTML that it output which made it
|
||||||
@@ -180,12 +179,12 @@ hard to quickly understand what the output of a piece of code would
|
|||||||
be. So naturally programmers have been endlessly searching for better
|
be. So naturally programmers have been endlessly searching for better
|
||||||
ways to mix HTML with JavaScript.
|
ways to mix HTML with JavaScript.
|
||||||
|
|
||||||
And this brings us to JSX. It is nothing new; nothing
|
And this brings us to JSX. It is nothing new, nothing
|
||||||
complicated. Forms of it have been made and used long before React
|
complicated. Forms of it have been made and used long before React
|
||||||
adopted it. Now let's see if we can discover JSX for ourselves.
|
adopted it. Now let's see if we can discover JSX for ourselves.
|
||||||
|
|
||||||
To start with, we need to create a data-structure -- let's call it
|
To start with, we need to create a data-structure – let's call it
|
||||||
JavaScript Markup (JSM) -- that both represents a DOM tree and can
|
JavaScript Markup (JSM) – that both represents a DOM tree and can
|
||||||
also be used to insert one into the browser's DOM. And to do that we
|
also be used to insert one into the browser's DOM. And to do that we
|
||||||
need to understand what a tree of DOM nodes is constructed of. What
|
need to understand what a tree of DOM nodes is constructed of. What
|
||||||
parts do you see here?
|
parts do you see here?
|
||||||
@@ -227,15 +226,15 @@ This is what I'm thinking:
|
|||||||
As you can see, we have a clear mapping from our notation, JSM, to the
|
As you can see, we have a clear mapping from our notation, JSM, to the
|
||||||
original HTML. Our tree is made up of three element arrays. The first
|
original HTML. Our tree is made up of three element arrays. The first
|
||||||
item in the array is the tag, the second is an object containing the
|
item in the array is the tag, the second is an object containing the
|
||||||
tag's properties, and the third is an array of its children; which are
|
tag's properties, and the third is an array of its children which are
|
||||||
all made up of the same three element arrays.
|
all made up of the same three element arrays.
|
||||||
|
|
||||||
The truth is though, if you stare at it long enough, although the
|
The truth is, if you stare at it long enough, although the
|
||||||
mapping is clear, how much fun would it be to read and write that on a
|
mapping is clear, how much fun would it be to read and write that on a
|
||||||
consistent basis? I can assure you, it is rather not fun. But it has
|
consistent basis? I can assure you, it is not fun. But it has
|
||||||
the advantage of being easy to insert into the DOM. All you need to do
|
the advantage of being easy to insert into the DOM. All you need to do
|
||||||
is write a simple recursive function that ingests our data structure
|
is write a simple recursive function that ingests our data structure
|
||||||
and updates the DOM accordingly. We will get back to that.
|
and updates the DOM accordingly. We’ll get back to that.
|
||||||
|
|
||||||
So now we have a way to represent a tree of nodes and we
|
So now we have a way to represent a tree of nodes and we
|
||||||
(theoretically) have a way to get those nodes into the DOM. But if we
|
(theoretically) have a way to get those nodes into the DOM. But if we
|
||||||
@@ -246,7 +245,7 @@ And this is where our object of study enters the scene. JSX is just a
|
|||||||
notation that a compiler takes as input and outputs in its place a
|
notation that a compiler takes as input and outputs in its place a
|
||||||
tree of nodes nearly identical to the notation we came up with! And if
|
tree of nodes nearly identical to the notation we came up with! And if
|
||||||
you look back to our notation you can see that you can easily embed
|
you look back to our notation you can see that you can easily embed
|
||||||
arbitrary JavaScript expressions wherever you want in a node. As you
|
in a node arbitrary JavaScript expressions wherever you want. As you
|
||||||
may have realized, that's exactly what the JSX compiler does when it
|
may have realized, that's exactly what the JSX compiler does when it
|
||||||
sees curly braces!
|
sees curly braces!
|
||||||
|
|
||||||
@@ -270,12 +269,12 @@ React.createElement(
|
|||||||
);
|
);
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
As you can see, it is very similar to our JSM data-structure and for
|
As you can see, it is very similar to our JSM data-structure and, for
|
||||||
the purposes of this book we will use JSM, as it's a bit easier to
|
the purposes of this book, we’ll use JSM, as it's a bit easier to
|
||||||
work with. A JSX compiler also does some validation and escapes input
|
work with. A JSX compiler also does some validation and escapes input
|
||||||
to prevent cross-site scripting attacks. In practice though, it would
|
to prevent cross-site scripting attacks. In practice though, it would
|
||||||
behave the same in our areas of study and we will keep things simple
|
behave the same in our areas of study and we’ll keep things simple
|
||||||
by leaving those aspects of the JSX compiler out.
|
by leaving out those aspects of the JSX compiler.
|
||||||
|
|
||||||
So now that we've worked through JSX we're ready to tackle
|
So now that we've worked through JSX we're ready to tackle
|
||||||
~createElement~, the next item on our way to building our own React.
|
~createElement~, the next item on our way to building our own React.
|
||||||
@@ -304,10 +303,10 @@ React expects nodes defined as JavaScript objects that look like this:
|
|||||||
}
|
}
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
That is: an object with two properties: ~type~ and ~props~. The
|
That is, an object with two properties: ~type~ and ~props~. The
|
||||||
~props~ property contains all the properties of the node. The node's
|
~props~ property contains all the properties of the node. The node's
|
||||||
~children~ are also considered part of its properties. The full
|
~children~ are also considered part of its properties. The full
|
||||||
version of React's ~createElement~ includes more properties but they
|
version of React's ~createElement~ includes more properties, but they
|
||||||
are not relevant to our study here.
|
are not relevant to our study here.
|
||||||
|
|
||||||
#+BEGIN_SRC javascript
|
#+BEGIN_SRC javascript
|
||||||
@@ -340,7 +339,7 @@ elements. The first part tests whether ~node~ is a complex node
|
|||||||
(specified by an array) and then generates an ~element~ object based
|
(specified by an array) and then generates an ~element~ object based
|
||||||
on the input node. It recursively calls ~createElement~ to generate an
|
on the input node. It recursively calls ~createElement~ to generate an
|
||||||
array of children elements. If the node is not complex then we
|
array of children elements. If the node is not complex then we
|
||||||
generate an element of type 'TEXT' which we use for all primitives
|
generate an element of type 'TEXT' which we use for all primitives,
|
||||||
like strings and numbers. We call the output of ~createElement~ a tree
|
like strings and numbers. We call the output of ~createElement~ a tree
|
||||||
of ~elements~ (surprise).
|
of ~elements~ (surprise).
|
||||||
|
|
||||||
@@ -353,9 +352,8 @@ process of rendering our tree to the DOM!
|
|||||||
:END:
|
:END:
|
||||||
|
|
||||||
There are now only two major puzzles remaining in our quest for our
|
There are now only two major puzzles remaining in our quest for our
|
||||||
own React. The next piece is: ~render~. How do we go from our JSM tree
|
own React. The next piece is ~render~. How do we go from our JSM tree
|
||||||
of nodes, to actually displaying something on screen? To do that we
|
of nodes to actually displaying something on screen? We do this by exploring the ~render~ method.
|
||||||
will explore the ~render~ method.
|
|
||||||
|
|
||||||
The signature for our ~render~ method should be familiar to you:
|
The signature for our ~render~ method should be familiar to you:
|
||||||
|
|
||||||
@@ -375,16 +373,16 @@ function render(element, container) {
|
|||||||
#+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
|
||||||
children elements, and finally append the whole tree to the
|
the children elements, and finally append the whole tree to the
|
||||||
container.
|
container.
|
||||||
|
|
||||||
Now that we have an idea of what to build we will work on expanding
|
Now that we have an idea of what to build we’ll work on expanding
|
||||||
the pseudocode until we have our own fully functional ~render~ method
|
the pseudocode until we have our own fully functional ~render~ method
|
||||||
using the same general algorithm React uses. In our first pass we will
|
by using the same general algorithm that React uses. In our first pass we’ll
|
||||||
focus on the initial render and ignore reconciliation.
|
focus on the initial render and ignore reconciliation.
|
||||||
|
|
||||||
#+begin_note
|
#+begin_note
|
||||||
Reconciliation is basically React's "diffing" algorithm. We will be
|
Reconciliation is basically React's "diffing" algorithm. We’ll be
|
||||||
exploring it after we work out the initial render.
|
exploring it after we work out the initial render.
|
||||||
#+end_note
|
#+end_note
|
||||||
|
|
||||||
@@ -428,7 +426,7 @@ reconciliation.
|
|||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:EXPORT_FILE_NAME: manuscript/reconciliation.markua
|
:EXPORT_FILE_NAME: manuscript/reconciliation.markua
|
||||||
:END:
|
:END:
|
||||||
A tale of two trees. These are the two trees that people most often
|
This is a tale of two trees, the two trees that people most often
|
||||||
talk about when talking about React's "secret sauce": the virtual DOM
|
talk about when talking about React's "secret sauce": the virtual DOM
|
||||||
and the browser's DOM tree. This idea is what originally set React
|
and the browser's DOM tree. This idea is what originally set React
|
||||||
apart. React's reconciliation is what allows you to program
|
apart. React's reconciliation is what allows you to program
|
||||||
@@ -444,48 +442,48 @@ diffing" algorithm.
|
|||||||
|
|
||||||
Unfortunately, those researching tree diffing in Computer Science have
|
Unfortunately, those researching tree diffing in Computer Science have
|
||||||
not yet produced a generic algorithm with sufficient performance for
|
not yet produced a generic algorithm with sufficient performance for
|
||||||
use in something like React; as the current best algorithm still [[https://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf][runs
|
use in something like React, as the current best algorithm still [[https://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf][runs
|
||||||
in O(n^3)]].
|
in O(n^3)]].
|
||||||
|
|
||||||
Since an O(n^3) algorithm isn't going to cut it in the real-world, the
|
Since an O(n^3) algorithm isn't going to cut it in the real-world, the
|
||||||
creators of React instead use a set of heuristics to determine what
|
creators of React instead use a set of heuristics to determine what
|
||||||
parts of the tree have changed. Understanding how the React tree
|
parts of the tree have changed. Understanding the heuristics currently
|
||||||
diffing algorithm works in general and the heuristics currently in use
|
in use and how the React tree diffing algorithm works in general can
|
||||||
can help immensely in detecting and fixing React performance
|
help immensely in detecting and fixing React performance
|
||||||
bottlenecks. And beyond that it can help one's understanding of some
|
bottlenecks. And beyond that it can help one's understanding of some
|
||||||
of React's quirks and usage. Even though this algorithm is internal to
|
of React's quirks and usage. Even though this algorithm is internal to
|
||||||
React and can be changed anytime its details have leaked out in some
|
React and can be changed anytime, its details have leaked out in some
|
||||||
ways and are overall unlikely to change in major ways without larger
|
ways and, overall, are unlikely to change in major ways without larger
|
||||||
changes to React itself.
|
changes to React itself.
|
||||||
|
|
||||||
According to the [[https://reactjs.org/docs/reconciliation.html][React documentation]] their diffing algorithm is O(n)
|
According to the [[https://reactjs.org/docs/reconciliation.html][React documentation]] the diffing algorithm is O(n)
|
||||||
and based on two major components:
|
and is based on two major components:
|
||||||
|
|
||||||
- Elements of differing types will yield different trees
|
- Elements of differing types will yield different trees
|
||||||
- You can hint at tree changes with the ~key~ prop.
|
- You can hint at tree changes with the ~key~ prop.
|
||||||
|
|
||||||
In this section we will focus on the first part: differing types.
|
In this section we’ll focus on the first part: differing types.
|
||||||
|
|
||||||
#+begin_note
|
#+begin_note
|
||||||
In this book we won't be covering keys in depth but you will see why
|
In this book we won't be covering keys in depth, but you’ll see why
|
||||||
it's very important to follow the guidance from React's documentation
|
it's very important to follow the guidance from React's documentation
|
||||||
that keys be: stable, predictable, and unique.
|
that keys are stable, predictable, and unique.
|
||||||
#+end_note
|
#+end_note
|
||||||
|
|
||||||
The approach we will take here is to integrate the heuristics that
|
The approach we’ll take here is to integrate the heuristics that
|
||||||
React uses into our ~render~ method. Our implementation will be very
|
React uses into our ~render~ method. Our implementation will be very
|
||||||
similar to how React itself does it and we will discuss React's actual
|
similar to how React itself does it and we’ll discuss React's actual
|
||||||
implementation later when we talk about Fibers.
|
implementation later when we talk about Fibers.
|
||||||
|
|
||||||
Before we get into the code changes that implement the heuristics it
|
Before we get into the code changes that implement the heuristics, it
|
||||||
is important to remember that React /only/ looks at an element's type,
|
is important to remember that React /only/ looks at an element's type,
|
||||||
existence, and key. It does not do any other diffing. It does not diff
|
existence, and key. It does not do any other diffing. It does not diff
|
||||||
props. It does not diff sub-trees of modified parents.
|
props. It does not diff sub-trees of modified parents.
|
||||||
|
|
||||||
While keeping that in mind, here is an overview of the algorithm we
|
While keeping that in mind, here is an overview of the algorithm we’ll
|
||||||
will be implementing in the ~render~ method. ~element~ is the element
|
be implementing in the ~render~ method. ~element~ is the element from
|
||||||
from the current tree and ~prevElement~ is the corresponding element
|
the current tree and ~prevElement~ is the corresponding element in the
|
||||||
in the tree from the previous render.
|
tree from the previous render.
|
||||||
|
|
||||||
#+BEGIN_SRC javascript
|
#+BEGIN_SRC javascript
|
||||||
if (!element && prevElement)
|
if (!element && prevElement)
|
||||||
@@ -505,21 +503,21 @@ still be invoked.
|
|||||||
|
|
||||||
Now, to get started with our render method we must make some
|
Now, to get started with our render method we must make some
|
||||||
modifications to our previous render method. First, we need to be able
|
modifications to our previous render method. First, we need to be able
|
||||||
to store and retrieve the previous render tree. Then we need to add
|
to store and retrieve the previous render tree. Then, we need to add
|
||||||
code to compare parts of the tree to decide if we can re-use DOM
|
code to compare parts of the tree to decide if we can reuse DOM
|
||||||
elements from the previous render tree. And last, we need to return a
|
elements from the previous render tree. And last, we need to return a
|
||||||
tree of elements that can be used in the next render as a comparison
|
tree of elements that can be used in the next render as a comparison
|
||||||
and to reference the DOM elements that we create. These new element
|
and to reference the DOM elements that we create. These new element
|
||||||
objects will have the same structure as our current elements but we
|
objects will have the same structure as our current elements but we’ll
|
||||||
will add two new properties: ~domElement~ and ~parent~. ~domElement~
|
add two new properties: ~domElement~ and ~parent~. ~domElement~ is the
|
||||||
is the DOM element associated with our synthetic element and ~parent~
|
DOM element associated with our synthetic element and ~parent~ is a
|
||||||
is a reference to the parent DOM element.
|
reference to the parent DOM element.
|
||||||
|
|
||||||
Here we begin by adding a global object that will store our last render
|
Here we begin by adding a global object that will store our last render
|
||||||
tree, keyed by the ~container~. ~container~ refers to the browser's
|
tree, keyed by the ~container~. ~container~ refers to the browser's
|
||||||
DOM element that will be the parent for all of the React derived DOM
|
DOM element that will be the parent for all of the React derived DOM
|
||||||
elements. This parent DOM element can only be used to render one tree
|
elements. This parent DOM element can only be used to render one tree
|
||||||
of elements at a time so it works well to use as a key for
|
of elements at a time, so it works well to use it as a key for
|
||||||
~renderTrees~.
|
~renderTrees~.
|
||||||
|
|
||||||
#+BEGIN_SRC javascript
|
#+BEGIN_SRC javascript
|
||||||
@@ -537,16 +535,16 @@ As you can see, the change we made is to move the core of our
|
|||||||
algorithm into a new function called ~render_internal~ and pass in the
|
algorithm into a new function called ~render_internal~ and pass in the
|
||||||
result of our last render to ~render_internal~.
|
result of our last render to ~render_internal~.
|
||||||
|
|
||||||
Now that we have stored our last render tree we can go ahead and
|
Now that we have stored our last render tree, we can go ahead and
|
||||||
update our render method with the heuristics for reusing the DOM
|
update our render method with the heuristics for reusing the DOM
|
||||||
elements. We name it ~render_internal~ because it is what controls the
|
elements. We name it ~render_internal~ because it is what controls the
|
||||||
rendering but takes an additional argument now: the
|
rendering, but it now takes an additional argument: the
|
||||||
~prevElement~. ~prevElement~ is a reference to the corresponding
|
~prevElement~. ~prevElement~ is a reference to the corresponding
|
||||||
~element~ from the previous render and contains a reference to its
|
~element~ from the previous render and contains a reference to its
|
||||||
associated DOM element and parent DOM element. If it's the first
|
associated DOM element and parent DOM element. If it's the first
|
||||||
render or if we are rendering a new node or branch of the tree then
|
render or if we are rendering a new node or branch of the tree, then
|
||||||
~prevElement~ will be ~undefined~. If, however, ~element~ is
|
~prevElement~ will be ~undefined~. If, however, ~element~ is
|
||||||
~undefined~ and ~prevElement~ is defined then we know we need to
|
~undefined~ and ~prevElement~ is defined, then we know we need to
|
||||||
delete a node that previously existed.
|
delete a node that previously existed.
|
||||||
|
|
||||||
#+BEGIN_SRC javascript
|
#+BEGIN_SRC javascript
|
||||||
@@ -589,11 +587,11 @@ render its children is when we are deleting an existing DOM
|
|||||||
element. We use this observation to group the calls for ~setDOMProps~
|
element. We use this observation to group the calls for ~setDOMProps~
|
||||||
and ~renderChildren~. Choosing when to append a new DOM element to the
|
and ~renderChildren~. Choosing when to append a new DOM element to the
|
||||||
container is also part of the heuristics. If we can reuse an existing
|
container is also part of the heuristics. If we can reuse an existing
|
||||||
DOM element then we do, but if the element type has changed or if
|
DOM element, then we do this, but if the element type has changed or if
|
||||||
there was no corresponding existing DOM element then and only then do
|
there was no corresponding existing DOM element, then, and only then, do
|
||||||
we append a new DOM element. This ensures the actual DOM tree isn't
|
we append a new DOM element. This ensures the actual DOM tree isn't
|
||||||
being replaced every time we render, only the elements that change are
|
being replaced every time we render, only the elements that change are
|
||||||
replaced.
|
being replaced.
|
||||||
|
|
||||||
In the real React, when a new DOM element is appended to the DOM tree,
|
In the real React, when a new DOM element is appended to the DOM tree,
|
||||||
React would invoke ~componentDidMount~ or schedule ~useEffect~.
|
React would invoke ~componentDidMount~ or schedule ~useEffect~.
|
||||||
@@ -612,12 +610,12 @@ function removeDOMElement(prevElement) {
|
|||||||
}
|
}
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
In creating a new DOM element we just need to branch if we are
|
In creating a new DOM element, we just need to branch if we are
|
||||||
creating a text element since the browser API differs slightly. We
|
creating a text element since the browser API differs slightly. We
|
||||||
also populate the text element's value as the API requires the first
|
also populate the text element's value, as the API requires the first
|
||||||
argument to be specified even though later on when we set props we
|
argument to be specified even though later on when we set props we’ll
|
||||||
will set it again. This is where React would invoke
|
set it again. This is where React would invoke ~componentWillMount~ or
|
||||||
~componentWillMount~ or schedule ~useEffect~.
|
schedule ~useEffect~.
|
||||||
|
|
||||||
#+BEGIN_SRC javascript
|
#+BEGIN_SRC javascript
|
||||||
function createDOMElement(element) {
|
function createDOMElement(element) {
|
||||||
@@ -656,7 +654,7 @@ need to be updated or removed.
|
|||||||
|
|
||||||
#+begin_warning
|
#+begin_warning
|
||||||
This algorithm for setting props does not correctly handle events,
|
This algorithm for setting props does not correctly handle events,
|
||||||
which must be treated specially. For this exercise that detail is not
|
which must be treated specially. For this exercise, that detail is not
|
||||||
important and we leave it out for simplicity.
|
important and we leave it out for simplicity.
|
||||||
#+end_warning
|
#+end_warning
|
||||||
|
|
||||||
@@ -665,7 +663,7 @@ elements that are no longer being used. This would happen when the
|
|||||||
number of children is decreased. The second loop starts at the first
|
number of children is decreased. The second loop starts at the first
|
||||||
child and then iterates through all of the children of the parent
|
child and then iterates through all of the children of the parent
|
||||||
element, calling ~render_internal~ on each child. When
|
element, calling ~render_internal~ on each child. When
|
||||||
~render_internal~ is called the corresponding previous element in that
|
~render_internal~ is called, the corresponding previous element in that
|
||||||
position is passed to ~render_internal~, or ~undefined~ if there is no
|
position is passed to ~render_internal~, or ~undefined~ if there is no
|
||||||
corresponding element, like when the list of children has grown.
|
corresponding element, like when the list of children has grown.
|
||||||
|
|
||||||
@@ -692,7 +690,7 @@ It's very important to understand the algorithm used here because this
|
|||||||
is essentially what happens in React when incorrect keys are used,
|
is essentially what happens in React when incorrect keys are used,
|
||||||
like using a list index for a key. And this is why keys are so
|
like using a list index for a key. And this is why keys are so
|
||||||
critical to high performance (and correct) React code. For example, in
|
critical to high performance (and correct) React code. For example, in
|
||||||
our algorithm here, if you removed an item from the front of the list
|
our algorithm here, if you removed an item from the front of the list,
|
||||||
you may cause every element in the list to be created anew in the DOM
|
you may cause every element in the list to be created anew in the DOM
|
||||||
if the types no longer match up. In this book we won't be
|
if the types no longer match up. In this book we won't be
|
||||||
incorporating keys, but it's actually only a minor difference in
|
incorporating keys, but it's actually only a minor difference in
|
||||||
@@ -700,7 +698,7 @@ determining which ~child~ gets paired with which
|
|||||||
~prevChild~. Otherwise this is effectively the same algorithm React
|
~prevChild~. Otherwise this is effectively the same algorithm React
|
||||||
uses when rendering lists of children.
|
uses when rendering lists of children.
|
||||||
|
|
||||||
#+CAPTION: Example of ~renderChildren~ 2nd loop when the 1st element has been removed. In this case the trees for all of the children will be torn down and rebuilt.
|
#+CAPTION: Example of ~renderChildren~ 2nd loop when the 1st element has been removed. In this case, the trees for all of the children will be torn down and rebuilt.
|
||||||
| i | child Type | prevChild Type |
|
| i | child Type | prevChild Type |
|
||||||
|---+------------+----------------|
|
|---+------------+----------------|
|
||||||
| 0 | span | div |
|
| 0 | span | div |
|
||||||
@@ -709,8 +707,8 @@ uses when rendering lists of children.
|
|||||||
|
|
||||||
There are a few things to note here. First, it is important to pay
|
There are a few things to note here. First, it is important to pay
|
||||||
attention to when React will be removing a DOM element from the tree
|
attention to when React will be removing a DOM element from the tree
|
||||||
and adding a new one as this is when the related lifecycle events or
|
and adding a new one, as this is when the related lifecycle events, or
|
||||||
hooks are invoked. And invoking those lifecycle methods or hooks, and
|
hooks, are invoked. And invoking those lifecycle methods, or hooks, and
|
||||||
the whole process of tearing down and building up a component is
|
the whole process of tearing down and building up a component is
|
||||||
expensive. So again, if you use a bad key, like the algorithm here
|
expensive. So again, if you use a bad key, like the algorithm here
|
||||||
simulates, you'll be hitting a major performance bottleneck since
|
simulates, you'll be hitting a major performance bottleneck since
|
||||||
@@ -724,24 +722,24 @@ tearing down and rebuilding the trees of child components.
|
|||||||
|
|
||||||
The actual React implementation used to look very similar to what
|
The actual React implementation used to look very similar to what
|
||||||
we've built so far, but with React 16 this has changed dramatically
|
we've built so far, but with React 16 this has changed dramatically
|
||||||
with the introduction of Fibers. Fibers are a name that React gives to
|
with the introduction of Fibers. Fibers is a name that React gives to
|
||||||
discrete units of work during the render process. And the React
|
discrete units of work during the render process. And the React
|
||||||
reconciliation algorithm was changed to be based on small units of
|
reconciliation algorithm was changed to be based on small units of
|
||||||
work instead of one large, potentially long-running call to
|
work instead of one large, potentially long-running call to
|
||||||
~render~. This means that React is now able to process just part of
|
~render~. This means that React is now able to process just part of
|
||||||
the render phase, pause to let the browser take care of other things,
|
the render phase, pause to let the browser take care of other things,
|
||||||
and resume again. This is the underlying change the enables the
|
and resume again. This is the underlying change that enables the
|
||||||
experimental Concurrent Mode as well as running most hooks without
|
experimental Concurrent Mode as well as runs most hooks without
|
||||||
blocking the render.
|
blocking the render.
|
||||||
|
|
||||||
But even with such a large change, the underlying algorithms for
|
But even with such a large change, the underlying algorithms that
|
||||||
deciding how and when to render components is the same. And when not
|
decide how and when to render components are the same. And, when not
|
||||||
running in Concurrent Mode the effect is still the same as React does
|
running in Concurrent Mode, the effect is still the same, as React
|
||||||
the render phase in one block still. So using a simplified
|
still does the render phase in one block. So, using a simplified
|
||||||
interpretation that doesn't include all the complexities of breaking
|
interpretation that doesn't include all the complexities of breaking
|
||||||
up the process in to chunks enables us to see more clearly how the
|
up the process in to chunks enables us to see more clearly how the
|
||||||
process as a whole works. At this point bottlenecks are much more
|
process works as a whole. At this point, bottlenecks are much more
|
||||||
likely to occur from the underlying algorithms and not from the Fiber
|
likely to occur from the underlying algorithms and not from the Fibers
|
||||||
specific details.
|
specific details.
|
||||||
|
|
||||||
* Putting it all together
|
* Putting it all together
|
||||||
@@ -749,7 +747,7 @@ specific details.
|
|||||||
:EXPORT_FILE_NAME: manuscript/putting-it-all-together.markua
|
:EXPORT_FILE_NAME: manuscript/putting-it-all-together.markua
|
||||||
:END:
|
:END:
|
||||||
|
|
||||||
Now that we've explored how React renders your components it's time to
|
Now that we've explored how React renders your components, it's time to
|
||||||
finally create some components and use them!
|
finally create some components and use them!
|
||||||
|
|
||||||
#+BEGIN_SRC javascript
|
#+BEGIN_SRC javascript
|
||||||
@@ -771,7 +769,7 @@ render(createElement(App()),
|
|||||||
document.getElementById('root'));
|
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
|
||||||
earlier. We create one component prop for the ~SayNow~ component:
|
earlier. We create one component prop for the ~SayNow~ component:
|
||||||
~dateTime~. It gets passed from the ~App~ component. The ~SayNow~
|
~dateTime~. It gets passed from the ~App~ component. The ~SayNow~
|
||||||
component prints out the ~DateTime~ passed in to it. You might notice
|
component prints out the ~DateTime~ passed in to it. You might notice
|
||||||
@@ -787,17 +785,17 @@ setInterval(() =>
|
|||||||
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’ll see the DateTime display being
|
||||||
updated every second. And if you watch in your dev tools or if you
|
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
|
profile the run, you’ll see that the only part of the DOM that gets
|
||||||
updated or replaced is the part that changes (aside from the DOM
|
updated or replaced is the part that changes (aside from the DOM
|
||||||
props). We now have a working version of our own React.
|
props). We now have a working version of our own React.
|
||||||
|
|
||||||
#+begin_note
|
#+begin_note
|
||||||
This implementation is designed for teaching purposes and has some
|
This implementation is designed for teaching purposes and has some
|
||||||
known issues and bugs, like always updating the DOM props, along with
|
known bugs, like always updating the DOM props, along with
|
||||||
other things. Fundamentally, it functions the same as React but if you
|
other issues. Fundamentally, it functions the same as React, but if you
|
||||||
wanted to use it in a more production setting it would take a lot more
|
want to use it in a more production-like setting, it would take a lot more
|
||||||
development.
|
development.
|
||||||
#+end_note
|
#+end_note
|
||||||
|
|
||||||
@@ -808,7 +806,7 @@ development.
|
|||||||
|
|
||||||
Of course our version of React elides over many details that React
|
Of course our version of React elides over many details that React
|
||||||
must contend with, like starting a re-render from where state changes
|
must contend with, like starting a re-render from where state changes
|
||||||
and event handlers. For understanding how to build high-performance
|
and event handlers. To build high-performance
|
||||||
React applications, however, the most important piece to understand is
|
React applications, however, the most important piece to understand is
|
||||||
how and when React renders components, which is what we have learned
|
how and when React renders components, which is what we have learned
|
||||||
in creating our own mini version of React.
|
in creating our own mini version of React.
|
||||||
@@ -820,11 +818,11 @@ components, and how React chooses when to replace a node or re-use
|
|||||||
one. If your React application is performing poorly you can think
|
one. If your React application is performing poorly you can think
|
||||||
about which part of the algorithm or heuristics might be the issue.
|
about which part of the algorithm or heuristics might be the issue.
|
||||||
|
|
||||||
Now, there is a lot more to explore. Like how do you track down the
|
Now, there is a lot more to explore. Like, how do you track down the
|
||||||
cause of a performance bottleneck? Or how do you use the React APIs in
|
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 we covered. So I hope this is
|
||||||
only the start of your High-Performance React journey.
|
just the start of your high-performance React journey.
|
||||||
* Image Test :noexport:
|
* Image Test :noexport:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:EXPORT_FILE_NAME: manuscript/image-test.markua
|
:EXPORT_FILE_NAME: manuscript/image-test.markua
|
||||||
|
|||||||
Reference in New Issue
Block a user