|
|
|
@ -56,7 +56,7 @@ the method will remain the same.
|
|
|
|
|
|
|
|
|
|
TODO note that the book references React-DOM but the algorithms should
|
|
|
|
|
generally apply to all React implementations.
|
|
|
|
|
* Mini React
|
|
|
|
|
* Fundamentals: Building our own React
|
|
|
|
|
Baking bread. When I first began to learn how to bake bread the recipe
|
|
|
|
|
told me what to do. It listed some ingredients and told me how to
|
|
|
|
|
combine them and prescribed times of rest. It gave me an oven
|
|
|
|
@ -93,13 +93,13 @@ 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 consistently
|
|
|
|
|
good results we need to understand how React does what it does.
|
|
|
|
|
|
|
|
|
|
** Basic React
|
|
|
|
|
** React, made of
|
|
|
|
|
|
|
|
|
|
Conceptually React is very simple. It starts by walking a tree of
|
|
|
|
|
components and building up a tree of their output. Then it compares
|
|
|
|
|
that tree to the tree currently in the browser's DOM to find any
|
|
|
|
|
differences between them. When it finds differences it updates the
|
|
|
|
|
browser's DOM to match its tree.
|
|
|
|
|
browser's DOM to match its internal tree.
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
@ -108,22 +108,31 @@ 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
|
|
|
|
|
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
|
|
|
|
|
understand 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.
|
|
|
|
|
|
|
|
|
|
React is made up of a few pieces: ~createElement~, ~render~, and
|
|
|
|
|
reconciliation. The first building block is ~createElement~. While
|
|
|
|
|
~createElement~ is itself unlikely to be a bottleneck it's a good to
|
|
|
|
|
understand how it works so that we can have a 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 performance problems.
|
|
|
|
|
|
|
|
|
|
** ~JSX~
|
|
|
|
|
|
|
|
|
|
But before we get to ~createElement~ we should talk about JSX. 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 a
|
|
|
|
|
mystery since we will be able to connect all the dots.
|
|
|
|
|
React is centered around the ~render~ method. The ~render~ method is
|
|
|
|
|
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~
|
|
|
|
|
method me have to understand its input. The input comes from
|
|
|
|
|
~createElement~. While ~createElement~ itself is unlikely to be a
|
|
|
|
|
bottleneck it's a good to understand how it works so that we can have
|
|
|
|
|
a 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
|
|
|
|
|
performance problems.
|
|
|
|
|
|
|
|
|
|
** Markup in Javascript: ~JSX~
|
|
|
|
|
|
|
|
|
|
~createElement~, however, takes as input something that is probably
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
|
a mystery since we will be able to connect all the dots.
|
|
|
|
|
|
|
|
|
|
JSX is not valid HTML or Javascript but its own language compiled by a
|
|
|
|
|
compiler, like Babel. The output of that compilation is valid
|
|
|
|
|
Javascript that represents the original markup.
|
|
|
|
|
|
|
|
|
|
Before JSX the normal way of injecting HTML into the DOM was via
|
|
|
|
|
directly utilizing the browser's DOM APIs. This was very cumbersome.
|
|
|
|
@ -141,16 +150,6 @@ a DOM tree and can 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 parts do you see here?
|
|
|
|
|
|
|
|
|
|
TODO include text element (Hello)
|
|
|
|
|
TODO change to using objects instead of arrays?
|
|
|
|
|
probably do after what we've already done
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
type: 'h1',
|
|
|
|
|
props: { x: y },
|
|
|
|
|
children: []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC html
|
|
|
|
|
<div class="header">
|
|
|
|
|
<h1>Hello</h1>
|
|
|
|
@ -159,7 +158,15 @@ probably do after what we've already done
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
I see three parts: the name of the tag, the tag's properties, and its
|
|
|
|
|
children. Now how could we recreate that in Javascript?
|
|
|
|
|
children.
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC
|
|
|
|
|
tag name: 'div'
|
|
|
|
|
tag prop: 'class'
|
|
|
|
|
children: h1..., 'Hello', input...
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
Now how could we recreate that in Javascript?
|
|
|
|
|
|
|
|
|
|
In Javascript we store lists of things in arrays and key/value
|
|
|
|
|
properties in objects. Luckily for us Javascript even gives us literal
|
|
|
|
@ -198,15 +205,15 @@ 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
|
|
|
|
|
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
|
|
|
|
|
arbitrary Javascript expression wherever you want in a node. As you
|
|
|
|
|
arbitrary Javascript expressions wherever you want in a node. As you
|
|
|
|
|
may have realized, that's exactly what the JSX compiler does when it
|
|
|
|
|
sees curly braces!
|
|
|
|
|
|
|
|
|
|
There are three main differences between our data structure and the
|
|
|
|
|
real one that JSX compiler outputs: it uses objects instead of arrays,
|
|
|
|
|
it inserts calls to React.createElement on children, and spreads the
|
|
|
|
|
children instead of containing them in an array. Here is what "real"
|
|
|
|
|
JSX compiler output looks like:
|
|
|
|
|
real one that the JSX compiler outputs: it uses objects instead of
|
|
|
|
|
arrays, it inserts calls to React.createElement on children, and
|
|
|
|
|
spreads the children instead of containing them in an array. Here is
|
|
|
|
|
what "real" JSX compiler output looks like:
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC javascript
|
|
|
|
|
React.createElement(
|
|
|
|
@ -221,15 +228,21 @@ React.createElement(
|
|
|
|
|
|
|
|
|
|
As you can see it is very similar to our data-structure and for the
|
|
|
|
|
purposes of this book we will use our own simplified data-structure as
|
|
|
|
|
it's a bit easier to work with. In practice they would behave the same
|
|
|
|
|
in the ways that matter to us now.
|
|
|
|
|
it's a bit easier to work with. A JSX compiler also does some
|
|
|
|
|
validation and escapes input to prevent cross-site scripting
|
|
|
|
|
attacks. In practice though they would behave the same in the ways
|
|
|
|
|
that matter to us now.
|
|
|
|
|
|
|
|
|
|
So now that we've worked through JSX we're ready to tackle
|
|
|
|
|
~createElement~, the item on our way to building our own React.
|
|
|
|
|
~createElement~, the next item on our way to building our own React.
|
|
|
|
|
|
|
|
|
|
TODO JSX also does validation and escapes input to prevent XXS
|
|
|
|
|
** Getting Ready to Render with ~createElement~
|
|
|
|
|
|
|
|
|
|
** ~createElement~
|
|
|
|
|
React's ~render~ expects to consume a tree of element objects in a
|
|
|
|
|
specific, uniform format. ~createElement~ is the method by which we
|
|
|
|
|
achieve that objective. ~createElement~ will take as input our
|
|
|
|
|
JSX-like notation and output a tree of objects compatible with
|
|
|
|
|
~render~.
|
|
|
|
|
|
|
|
|
|
React expects nodes defined as Javascript objects that look like this:
|
|
|
|
|
|
|
|
|
@ -245,20 +258,12 @@ React expects nodes defined as Javascript objects that look like this:
|
|
|
|
|
}
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
That is an object with two properties: ~type~ and ~props~. The ~props~
|
|
|
|
|
property contains all the properties of the node. The node's
|
|
|
|
|
That is: an object with two properties: ~type~ and ~props~. The
|
|
|
|
|
~props~ property contains all the properties of the node. The node's
|
|
|
|
|
~children~ are also considered part of its properties. The full
|
|
|
|
|
React's ~createElement~ includes more properties but they are unlikely
|
|
|
|
|
to be relevant to your application's performance or our version of
|
|
|
|
|
React here.
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC javascript
|
|
|
|
|
// React's createElement
|
|
|
|
|
const ReactElement = function(type, key, ref, self, source, owner, props)
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
So all our ~createElement~ needs to do is transform our data structure
|
|
|
|
|
into the objects that our React expects.
|
|
|
|
|
version of React's ~createElement~ includes more properties but they
|
|
|
|
|
are unlikely to be relevant to your application's performance or our
|
|
|
|
|
version of React here.
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC javascript
|
|
|
|
|
function createElement(node) {
|
|
|
|
@ -300,11 +305,10 @@ process of rendering our tree to the DOM!
|
|
|
|
|
** Render
|
|
|
|
|
|
|
|
|
|
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 tree of
|
|
|
|
|
own React. The next piece is: ~render~. How do we go from our tree of
|
|
|
|
|
nodes to actually displaying something on screen?
|
|
|
|
|
|
|
|
|
|
The signature for our ~render~ method is very simple and will be
|
|
|
|
|
familiar to you:
|
|
|
|
|
The signature for our ~render~ method should be familiar to you:
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC javascript
|
|
|
|
|
function render(element, container)
|
|
|
|
@ -321,7 +325,12 @@ function render(element, container) {
|
|
|
|
|
container.appendChild(domElement);
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
TODO and now full code:
|
|
|
|
|
Our DOM element is created first. Then we set the properties, render
|
|
|
|
|
children elements, and finally append the whole tree to the
|
|
|
|
|
container. Now we will work on expanding the psuedocode until we build
|
|
|
|
|
our own fully functional ~render~ method using the same general
|
|
|
|
|
algorithm React uses. Next we will focus on the initial render and
|
|
|
|
|
ignore reconciliation.
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC javascript
|
|
|
|
|
function render(element, container) {
|
|
|
|
@ -347,20 +356,17 @@ function render(element, container) {
|
|
|
|
|
}
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
Next, we look at renderDOMElement which must also set properties on
|
|
|
|
|
the newly created DOM element and render any children.
|
|
|
|
|
|
|
|
|
|
To start with we create the DOM element. Then we need to set its
|
|
|
|
|
We begin by creating the DOM element. Then we need to set its
|
|
|
|
|
properties. To do this we first need to filter out the ~children~
|
|
|
|
|
property and then we simply loop over they keys setting each property
|
|
|
|
|
directly. Then we render each of the children by looping over the
|
|
|
|
|
children recursively calling ~render~ on each with the ~container~ set
|
|
|
|
|
to the current DOM element (which is each child's parent).
|
|
|
|
|
|
|
|
|
|
Now we can go all the way from JSX to a rendered tree in the browser's
|
|
|
|
|
DOM! But so far we can only add things to our tree. To be able to
|
|
|
|
|
remove and modify the tree we need two more parts: reconciliation and
|
|
|
|
|
the commit phase.
|
|
|
|
|
Now we can go all the way from our JSX-like notation to a rendered
|
|
|
|
|
tree in the browser's DOM! But so far we can only add things to our
|
|
|
|
|
tree. To be able to remove and modify the tree we need one more part:
|
|
|
|
|
reconciliation.
|
|
|
|
|
|
|
|
|
|
** Reconciliation
|
|
|
|
|
A tale of two trees. These are the two trees that people most often
|
|
|
|
@ -455,11 +461,11 @@ function render(element, container) {
|
|
|
|
|
}
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
TODO note that we are adding parent and domElement properties.
|
|
|
|
|
|
|
|
|
|
Now that we have a way to see what we rendered last time we can go
|
|
|
|
|
ahead and update our render method with the heuristics.
|
|
|
|
|
|
|
|
|
|
TODO note that we are adding parent and domElement properties.
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC javascript
|
|
|
|
|
function render_internal(element, container, prevElement) {
|
|
|
|
|
let domElement, children;
|
|
|
|
|