From 7f78c63348498498668baaf413ca10bf4db38c83 Mon Sep 17 00:00:00 2001 From: Thomas Hintz Date: Sun, 26 Jul 2020 18:51:15 -0700 Subject: [PATCH] Beginning of chapter 2. --- high-performance-react.org | 69 +++++++++++++++++++++++++++++-- manuscript/reconciliation.markua | 2 +- manuscript/rendering-model.markua | 28 ++++++++++++- 3 files changed, 93 insertions(+), 6 deletions(-) diff --git a/high-performance-react.org b/high-performance-react.org index c4f5fed..42eb692 100644 --- a/high-performance-react.org +++ b/high-performance-react.org @@ -686,10 +686,73 @@ TODO maybe a graphic summarizing the heuristics? :PROPERTIES: :EXPORT_FILE_NAME: manuscript/rendering-model.markua :END: - React calls shouldComponentUpdate to know if it should re-render the - component. by default it returns true. - generally use PureComponent/React.memo +Now that we have a firm understanding of the underpinnings of React we +can begin to look at potential bottlenecks and their solutions. We'll +start with a little quiz about how React chooses when to render a +component. + +TODO insert img-tree of components + +In figure 1, if state changes in component A but nothing changes in B +will React ask B to re-render? + +Yes. Absolutely. Always, unless ~componentShouldUpdate~ returns false, +which is not even an option with functional components and is +discouraged for class based components. So if we have a large tree of +components and we change state high in the tree React will be +constantly re-rendering large parts of the tree. (This is common +because app state often has to live up high in the tree because props +can only be passed down.) This is clearly very in efficient so why +does React do it? + +If you remember back to when we implemented the render algorithm +you'll recall that React does nothing to see if a component actually +needs to re-render, it only cares tests whether DOM elements need to +be replaced or removed. Instead React always renders all +children. React is effectively off-loading the descision to re-render +to the components themselves because a general solution has poor +performance. + +Originally React had ~componentShouldUpdate~ to solve this issue but +the developers of React found that implementing it correctly was +difficult and error prone. Programmers would add new props to a +component but forget to update ~componentShouldUpdate~ with the new +props causing the component to not update when it should which led to +strange and hard to diagnose bugs. So if we shouldn't use +~componentShouldUpdate~ what tools are we left with? + +And it's a great question because unneeded renders can be a massive +bottleneck. Especially on large lists of components. In fact, there is +no other way to control renders; React will always render. + +But there is still hope. While we can't control if our component will +render what if instead of just always -rerunning all of our render +code on each render we instead kept a copy of the result of the render +and next time React asks us to re-render we just return the result we +saved? Now that, with two modifications, is exactly what we will do. + +TODO Note: this stops full tree from re-rendering + +Obviously we can't just render once and then forever return that +result because our state and props might change. So we also need to +track the state and props and only return our cached result if they +haven't changed. + +As you may have already noticed this is a common solution in Computer +Science for such problems: memoization. What we want is to memoize our +components. + +TODO Note: explain memoization + +This is indeed such a common bottleneck and solution that React +provides an API to facilitate it. + +We will learn about this API by first looking at the signatures of the +React API itself, then we will extend our React implementation from +chapter one to support the same API. Then we will discuss its usage +and analyze when and how to use it. + * Diagnosing Bottlenecks :PROPERTIES: :EXPORT_FILE_NAME: manuscript/diagnosing-bottlenecks.markua diff --git a/manuscript/reconciliation.markua b/manuscript/reconciliation.markua index e97c4c0..623e6fb 100644 --- a/manuscript/reconciliation.markua +++ b/manuscript/reconciliation.markua @@ -1,6 +1,6 @@ # Reconciliation -* diffing algorithm based on heuristics. generic algorithm is O(n3) +* 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 diff --git a/manuscript/rendering-model.markua b/manuscript/rendering-model.markua index d297f04..d6f9c56 100644 --- a/manuscript/rendering-model.markua +++ b/manuscript/rendering-model.markua @@ -1,7 +1,31 @@ # Rendering Model -React calls shouldComponentUpdate to know if it should re-render the component. by default it returns true. +Now that we have a firm understanding of the underpinnings of React we can begin to look at potential bottlenecks and their solutions. We'll start with a little quiz about how React chooses when to render a component. -generally use PureComponent/React.memo +TODO insert img-tree of components + +In figure 1, if state changes in component A but nothing changes in B will React ask B to re-render? + +Yes. Absolutely. Always, unless `componentShouldUpdate` returns false, which is not even an option with functional components and is discouraged for class based components. So if we have a large tree of components and we change state high in the tree React will be constantly re-rendering large parts of the tree. (This is common because app state often has to live up high in the tree because props can only be passed down.) This is clearly very in efficient so why does React do it? + +If you remember back to when we implemented the render algorithm you'll recall that React does nothing to see if a component actually needs to re-render, it only cares tests whether DOM elements need to be replaced or removed. Instead React always renders all children. React is effectively off-loading the descision to re-render to the components themselves because a general solution has poor performance. + +Originally React had `componentShouldUpdate` to solve this issue but the developers of React found that implementing it correctly was difficult and error prone. Programmers would add new props to a component but forget to update `componentShouldUpdate` with the new props causing the component to not update when it should which led to strange and hard to diagnose bugs. So if we shouldn't use `componentShouldUpdate` what tools are we left with? + +And it's a great question because unneeded renders can be a massive bottleneck. Especially on large lists of components. In fact, there is no other way to control renders; React will always render. + +But there is still hope. While we can't control if our component will render what if instead of just always -rerunning all of our render code on each render we instead kept a copy of the result of the render and next time React asks us to re-render we just return the result we saved? Now that, with two modifications, is exactly what we will do. + +TODO Note: this stops full tree from re-rendering + +Obviously we can't just render once and then forever return that result because our state and props might change. So we also need to track the state and props and only return our cached result if they haven't changed. + +As you may have already noticed this is a common solution in Computer Science for such problems: memoization. What we want is to memoize our components. + +TODO Note: explain memoization + +This is indeed such a common bottleneck and solution that React provides an API to facilitate it. + +We will learn about this API by first looking at the signatures of the React API itself, then we will extend our React implementation from chapter one to support the same API. Then we will discuss its usage and analyze when and how to use it.