From 0745ff62818e587425181ab1ea44cab234f7d802 Mon Sep 17 00:00:00 2001 From: Thomas Hintz Date: Thu, 23 Jul 2020 20:09:02 -0700 Subject: [PATCH] Initial commit. --- high-performance-react.org | 273 +++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 high-performance-react.org diff --git a/high-performance-react.org b/high-performance-react.org new file mode 100644 index 0000000..d5bf213 --- /dev/null +++ b/high-performance-react.org @@ -0,0 +1,273 @@ +* Preface +* Introduction +It was the late 90's and I was just a kid visiting my Aunt and Uncle +and their family in Denver. The days were packed with endless playing +and goofing around. I didn't get to see my cousins much and we were +having a good time. But it was the late 90's and the Internet was +booming. And my cousin was in on it. + +A "startup", that's what he called it. I didn't understand any of what +he was saying about it. Grown-up stuff. Then he showed us the webpage +for the startup and I thought that was impressive. + +"How did you make that"? I asked him. I think he was a little confused +at first about what I was even talking about but he quickly brought me +over to the computer and showed me a screen full of text. + +"You just type HTML, that's how you make the webpage." I thought this +was the coolest. + +"What do you type that into? What program is it? Can I do that?" He +told me it was easy: just use Notepad. I wasn't going to let him go +without some hook I could grab into this alien world. He told me it's +really easy to learn: do an AOL search for "HTML tutorial". + +So began my journey with web development. I AOL searched my way +through as many blinking text tutorials as I could find. It wasn't +long until I was building AJAX. We had IE 5.5 and 6 and Mozilla +Pheonix. And GMail came out. That changed things, now web apps were +"legitimate." + +A lot of the technologies and libraries came and went over the years +but one thing remained constant in large web apps: poor +performance. From the very early days I was timing things with my stop +watch. Sometimes things were slow and I had to understand why and how +to fix them. Over the years I learned all about the browser's DOM and +its APIs and how they work. I learned how jQuery worked and +backbone.js and all the rest. I made apps that didn't lag or have +jank. + +I was able to do this because I understood the performance +implications of the tools and libraries I was using and I learned how +to measure performance. I had discovered the recipe for +high-performance code. + +And that is what this book is: a recipe for producing high-performance +React applications. First, we learn how React works. Then we learn how +to measure performance. And last we learn how to address the +bottlenecks we find. Parts of any technical book will go stale as +technology changes and that is no less true for this book. But what I +hope you learn is not just the technical details but more importantly +the method for writing high-performance code. The API might change but +the method will remain the same. +* Mini 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 +temperature and a period of wait. It gave me mediocre bread of wildly +varying quality. I tried different recipes but the result was always +the same. + +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 +and general recommendations for rests and waits. So why does the bread +turn out better? + +Before baking is finished bread is a living organism. The way it grows +and develops and flavors depend on what you feed it and how you feed +it and massage it, care for it. If you have it grow and ferment at a +higher temperature and more yeast it overdevelops producing too much +alcohol. If you give it too much time acidity will take over the +flavor. The recipes I used initially were missing a critical +ingredient: the rising temperature. + +But unlike a lot of ingredients: temperature is hard to control for +the home cook. So the recipe can't just tell you exactly what +temperature to grow the bread at. My initial recipes just silently +made assumptions for the temperature, which rarely turn out to be +true. This means that the only way to consistently make good bread is +to have an understanding of how bread develops so that you can adjust +the other ingredients to complement the temperature. Now the bread can +tell me what to do. + +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 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 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 + +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. + +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 +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 +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 +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. + +Before JSX the normal way of injecting HTML into the DOM was via +directly utilizing the browser's DOM APIs. This was very cumbersome. +The code's structure did not match the structure of the HTML that it +output which made it hard to quickly understand what the output of +a piece of code would be. So naturally programmers have been endlessly +searching for better ways to mix HTML with Javascript. + +And this brings us to JSX. It is nothing new; nothing +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. + +To start with we need to create a data structure that both represents +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

+ +
+#+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? + +In Javascript we store lists of things in arrays and key/value +properties in objects. Luckily for us Javascript even gives us literal +syntax for both so we can easily make a compact DOM tree with our own +notation. + +This is what I'm thinking: + +#+BEGIN_SRC javascript +['div', { 'class': 'header' }, + [['h1', {}, ['Hello']], + ['input', { 'type': 'submit', 'disabled': 'disabled' }, []] + ] +] +#+END_SRC + +As you can see we have a clear mapping from our notation to the +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 +tag's properties, and the third is an array of its children; which are +all made up of the same three element arrays. + +The truth is though, 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 +consistent basis? I can assure you, it is rather not fun. But it has +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 +and updates the DOM accordingly. We will get back to this. + +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 +are being honest with ourselves, while functional, it isn't a pretty +notation nor easy to work with. + +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 +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: + +#+BEGIN_SRC javascript +React.createElement( + 'div', + { className: 'header' }, + React.createElement('h1', {}, 'Hello'), + React.createElement( + 'input', + { type: 'submit', 'disabled': 'disabled' }) +); +#+END_SRC + +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. + +So now that we've worked through JSX we're ready to tackle +~createElement~, the item on our way to building our own React. + +TODO JSX also does validation and escapes input to prevent XXS + +*** ~createElement~ + +A tale of two trees. + +#+BEGIN_SRC javascript +function createElement(node) { + if (typeof node === 'string') { + return { + type: 'TEXT_ELEMENT' + } + const [ tag, props, children ] = node; + const element = document.createDOMElement(tag); + for ([key, val] in props) { + element[key] = val; + } + children.forEach(createElement); +} +#+END_SRC + +* Rendering Model + React calls shouldComponentUpdate to know if it should re-render the + component. by default it returns true. + + generally use PureComponent/React.memo +* Diagnosing Bottlenecks +* Reducing Renders +* Improving DOM Merge Performance +* Reducing Number of Components + higher-order components +* Windowing +* Performance Tools + trace from scheduler/tracing/profiler component +* JS Performance Tools +* Code Splitting + React.lazy, suspense + + use on routes +* Server Side Rendering +* Concurrent Rendering +* UX +* JS Service Workers +* Reconciliation + - diffing algorithm based on heuristics. generic algorithm is O(n^3) + - "Fiber" algorithm notes + - lists reordering without key means full list output/update + - type changes cause full re-render + - keys should be stable, predictable, unique +