|
| 1 | +--- |
| 2 | +title: Notes on React's Rendering Model |
| 3 | +tags: |
| 4 | + - notes |
| 5 | +--- |
| 6 | + |
| 7 | +- **React Core:** Creates "stuff" on the page with Javascript. How? VDOM! |
| 8 | +- **VDOM (Virtual DOM):** |
| 9 | + - In-memory representation of the actual DOM. Like a lightweight, optimized database for the UI. |
| 10 | + - Organized as a tree structure (components/DOM elements as nodes). |
| 11 | + - **Reconciliation:** |
| 12 | + - React compares the _new_ VDOM with the _previous_ VDOM when data changes (state, props, etc.). |
| 13 | + - Creates "patches" representing _minimum_ changes needed for the real DOM. |
| 14 | + - React uses smart heuristics (batching updates in React 18+, root-based comparisons) to minimize re-rendering time. |
| 15 | + - **VDOM Advantages:** |
| 16 | + - Sets a reasonable ceiling on rendering performance. |
| 17 | + - Can batch operations to avoid costly re-renders. |
| 18 | + - **VDOM Disadvantages:** |
| 19 | + - Inherently adds latency compared to direct DOM manipulation (due to comparison and patching). |
| 20 | + - _Never_ faster than directly manipulating the DOM. (Tradeoff: dev experience vs. raw speed) |
| 21 | + |
| 22 | +## **Props, Re-renders, and the Prop-Drilling Problem** |
| 23 | + |
| 24 | +- Props: How you pass data from parent to child components. |
| 25 | +- Passed via _reference_ (pointers), _not_ value. |
| 26 | + - Good: Avoids data duplication, mutations propagate. |
| 27 | + - Bad: Hard to tell what _actually_ needs to re-render, causing performance issues. |
| 28 | +- **Prop Drilling Scenario (A -> B -> C -> D):** |
| 29 | + |
| 30 | + - A holds data `x`, needs to pass it to D. |
| 31 | + - A passes `x` to B, B to C, C to D. |
| 32 | + - Only D _uses_ `x`, but A, B, C, and D _all_ re-render when `x` changes. _Why?!_ |
| 33 | + - **Code Example:** |
| 34 | + |
| 35 | + ```javascript |
| 36 | + import React, { useState } from "react" |
| 37 | + |
| 38 | + const ComponentD = ({ x }) => { |
| 39 | + console.log("Component D rendered") |
| 40 | + return <div>Value of x: {x}</div> |
| 41 | + } |
| 42 | + |
| 43 | + const ComponentC = ({ x }) => { |
| 44 | + console.log("Component C rendered") |
| 45 | + return <ComponentD x={x} /> |
| 46 | + } |
| 47 | + |
| 48 | + const ComponentB = ({ x }) => { |
| 49 | + console.log("Component B rendered") |
| 50 | + return <ComponentC x={x} /> |
| 51 | + } |
| 52 | + |
| 53 | + const ComponentA = () => { |
| 54 | + const [x, setX] = useState(0) |
| 55 | + console.log("Component A rendered") |
| 56 | + |
| 57 | + return ( |
| 58 | + <div> |
| 59 | + <button onClick={() => setX(x + 1)}>Increment x</button> |
| 60 | + <ComponentB x={x} /> |
| 61 | + </div> |
| 62 | + ) |
| 63 | + } |
| 64 | + ``` |
| 65 | + |
| 66 | + - Clicking "Increment x" re-renders A, B, C, and D. |
| 67 | + - B and C don't even _use_ `x` directly! |
| 68 | + - **Reason:** |
| 69 | + - VDOM + pass-by-reference. |
| 70 | + - A's state update creates a _new reference_ for the `x` prop. |
| 71 | + - VDOM sees the `x` prop has changed for B and C (different reference). |
| 72 | + - Triggers re-render _even if the underlying value of_ `x` _is the same_. |
| 73 | + - This is inherent to JavaScript and React. React follows a top-down approach and will re-render the component which modified the state, as well as all it's children. |
| 74 | +
|
| 75 | +- **Implications of Prop Drilling:** |
| 76 | + - In larger apps, complex states passed down the tree can cause a cascade of re-renders. |
| 77 | + - Even _unaffected_ children re-render, creating a performance bottleneck. |
| 78 | + - Hard to debug due to unnecessary re-renders. |
| 79 | + - **Trade-off:** React prioritizes keeping the UI in sync with the state (even if it means unnecessary re-renders). |
| 80 | + - React's reconciliation is smart, but not _perfect_. It errs on the side of caution by re-rendering. |
| 81 | + |
| 82 | +## **Why Should You Care?** |
| 83 | + |
| 84 | +- Performance _does_ matter, even if people don't notice small FPS differences. |
| 85 | +- It's hard to work your way out of performance issues _later_. Design for performance from the start. |
| 86 | +- Prop drilling makes components _too coupled_. |
| 87 | +- React should be about composition, pure functions (inputs -> outputs, no side effects). |
| 88 | +- Prop drilling means a component is concerned with its parent's and children's responsibilities. |
| 89 | +- Makes components less composable, less performant, harder to test. |
| 90 | + |
| 91 | +## **What to Do?** |
| 92 | + |
| 93 | +- _Lean on the work of others:_ Use libraries and established patterns! (Billions of dollars of dev-hours invested in this). |
| 94 | +- Client-side State Management |
| 95 | +- Server-side State Management |
| 96 | +- Memoization |
| 97 | + |
| 98 | +- **Sources:** |
| 99 | + - [https://www.joshwcomeau.com/react/why-react-re-renders/](https://www.joshwcomeau.com/react/why-react-re-renders/) |
| 100 | + - [https://react.dev/learn/render-and-commit](https://react.dev/learn/render-and-commit) |
| 101 | + - [https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/](https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/) |
0 commit comments