diff --git a/high-performance-react.org b/high-performance-react.org index c375d85..340b577 100644 --- a/high-performance-react.org +++ b/high-performance-react.org @@ -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

Hello

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