Upgrade to Pro — share decks privately, control downloads, hide ads and more …

๐Ÿ‡ฉ๐Ÿ‡ช iJS Munich 2023 - Deep diving on Concurrent React

๐Ÿ‡ฉ๐Ÿ‡ช iJS Munich 2023 - Deep diving on Concurrentย React

Writing fluid user interfaces becomes more and more challenging as the application complexity increases. In this talk, weโ€™ll explore how proper scheduling improves your appโ€™s experience by diving into some concurrent React features, understanding their rationales, and how they work under the hood.

Matheus Albuquerque

October 24, 2023
Tweet

More Decks by Matheus Albuquerque

Other Decks in Programming

Transcript

  1. โ†‘ ALL THE LINKS! ๐Ÿง‘๐Ÿซ TECHLABS ๐Ÿฆ @ythecombinator ๐Ÿ‘จ๐Ÿ’ป MEDALLIA

    โšก PERF GDE SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE /
  2. WE TALKED ABOUTโ€ฆ โ† FIBERS โ† COROUTINES โ† GENERATORS โ†

    ALGEBRAIC EFFECTS I WANTED TO DISCUSS MOREโ€ฆ โ† VIRTUAL DOM โ† CONCURRENCY/PARALLELISM โ† CONCURRENT REACT โ† MEASUREMENT
  3. DATA-DRIVEN UI LIBRARIES DOM RECONCILIATION E.G. ANGULAR, POLYMER, LIT-HTML VIRTUAL

    DOM E.G. REACT, VUE, INFERNO REACTIVE E.G. KNOCKOUT, SVELTE, SOLID
  4. DOM RECONCILIATION E.G. ANGULAR, POLYMER, LIT-HTML VIRTUAL DOM E.G. REACT,

    VUE, INFERNO REACTIVE E.G. KNOCKOUT, SVELTE, SOLID DATA-DRIVEN UI LIBRARIES
  5. #quote ๐Ÿ’ฌ โ€œ[โ€ฆ] With React you can build applications without

    even thinking about performance and the default state is fast.โ€ โ€”โ€‰Rethinking Best Practices by Pete Hunt, 2013
  6. VIRTUAL DOM โ† GENERATE A VIRTUAL TREE AND THEN DIFF

    AGAINST THE PREVIOUS ITERATION โ† PATCH THE DOM UPDATES โ† USE IMMUTABILITY AND REFERENTIAL EQUALITY TO OPTIMIZE THE PROCESS WHICH RESULTS IN SIGNIFICANT CLONING AND MEMORY ALLOCATION
  7. #protip ๐Ÿ’ก React doesnโ€™t have any understanding of the values

    running through your app. Itโ€™s not reactive.
  8. VIRTUAL DOM โ† IT HAS BEEN OPTIMIZED WELL ENOUGH IN

    MOST SCENARIOS โ† WE DONโ€™T COMPLAIN ABOUT PERF IN INFERNO OR BUNDLE SIZE IN PREACT โ† THERE ARE LIBRARIES THAT CAN PRODUCE EQUIVALENT/ SMALLER BUNDLES (E.G. HyperApp)
  9. VIRTUAL DOM โ† IT'S JUST ONE OF THE APPROACHES WE

    HAVE AND IT'S NEVER A TRUE/FALSE QUESTION โ† UNDERSTANDING ITS KEY TAKEAWAYS HELPS US UNDERSTAND WHERE CONCURRENT REACT FITS
  10. PERFOMANCE PERCEIVED LOAD SPEED HOW QUICKLY A PAGE CAN LOAD

    AND RENDER ALL OF ITS VISUAL ELEMENTS TO THE SCREEN SMOOTHNESS DO TRANSITIONS & ANIMATIONS RENDER AT A CONSISTENT FRAME RATE AND FLOW FLUIDLY? LOAD RESPONSIVENESS HOW QUICKLY A PAGE CAN LOAD/RUN ANY REQUIRED JS IN ORDER FOR COMPONENTS TO RESPOND TO USER INTERACTION RUNTIME RESPONSIVENESS AFTER THE PAGE LOAD, HOW QUICKLY CAN THE PAGE RESPOND TO USER INTERACTION?
  11. PERFOMANCE PERCEIVED LOAD SPEED HOW QUICKLY A PAGE CAN LOAD

    AND RENDER ALL OF ITS VISUAL ELEMENTS TO THE SCREEN LOAD RESPONSIVENESS HOW QUICKLY A PAGE CAN LOAD/RUN ANY REQUIRED JS IN ORDER FOR COMPONENTS TO RESPOND TO USER INTERACTION RUNTIME RESPONSIVENESS AFTER THE PAGE LOAD, HOW QUICKLY CAN THE PAGE RESPOND TO USER INTERACTION? SMOOTHNESS DO TRANSITIONS & ANIMATIONS RENDER AT A CONSISTENT FRAME RATE AND FLOW FLUIDLY?
  12. #research ๐Ÿ“š Phone users experience slow First Input Delay on

    7x more websites. โ€”โ€‰Web Almanac By HTTP Archive, 2021
  13. FIRST INPUT DELAY DESKTOP PHONE 0 25 50 75 100

    SLOW ( > = 250MS) AVERAGE FAST (<50 MS)
  14. BUSINESS OUTCOMES โ† LONG TASKS DELAYED TTI โ† AS FIRST-PAGE

    LONG TASK TIME INCREASED, OVERALL CONVERSION RATES DECREASED โ† MOBILE HAD UP TO หœ12X LONGER LONG TASKS โ† OLDER DEVICES COULD BE SPENDING HALF OF THEIR LOAD-TIME ON LONG TASKS โ€”โ€‰AKAMAI AND CHROME RESEARCH, 2017
  15. #question ๐Ÿค” If you were to summarize Concurrent React in

    one word/expression, whatโ€™d be your pick?
  16. #question ๐Ÿค” If you were to summarize Concurrent React in

    one word/expression, whatโ€™d be your pick? e.g. fibers โ† units of work Concurrent React โ† ???
  17. If you were to summarize Concurrent React in one word/expression,

    whatโ€™d be your pick? DEEP DIVING ON CONCURRENT REACT /
  18. TASKS A UNIT OF WORK THAT THE BROWSER DOES TO

    RENDER A FRAME JAVASCRIPT STYLES LAYOUT PAINT COMPOSITE
  19. LONG TASKS โ† IF A TASK TAKES MORE THAN 50

    MS, USER INPUT FEELS DELAYED โ† BASED ON THE USER-CENTRIC PERFORMANCE MODEL CALLED RAIL โ† THEY TAKE TOO LONG AND BLOCK OTHER TASKS
  20. TASK RUNNING STRATEGIES PARALLELISM โ† MULTIPLE THREADS = MULTIPLE TASKS

    AT THE SAME TIME ON SEPARATE CPU CORES CONCURRENCY โ† SINGLE THREAD + QUICKLY SWITCHING BETWEEN TASKS SCHEDULING โ† CONCURRENCY + TASK PRIORITIZATION
  21. WORKERS โ† VERY DIFFERENT FROM THE THREADS IN C++, JAVA,

    ETC. โ† NO ACCESS TO ANY VARIABLES/CODE FROM THE PAGE THAT CREATED THEM OR VICE VERSA โ† DATA EXCHANGE IS THROUGH MESSAGE-PASSING
  22. WORKERS โ† NO ACCESS TO THE DOM, MAKING UI UPDATES

    FROM A WORKER BARELY IMPOSSIBLE โ† APPS THAT INTEND TO USE THEM HAVE TO ADAPT THEIR ARCHITECTURE TO ACCOMMODATE THESE REQUIREMENTS โ† TWO MODELS: ACTORS & SHARED MEMORY ๐Ÿคฏ
  23. WORKERS: ACTORS โ† EACH ACTOR MAY OR MAY NOT RUN

    ON A SEPARATE THREAD โ† EACH ACTOR FULLY OWNS THE DATA IT IS OPERATING ON โ† ACTORS CAN ONLY SEND/REACT TO MESSAGES โ† MAIN THREAD = ACTOR THAT OWNS THE DOM/UI ๐Ÿคฏ
  24. WORKERS: ACTORS โ† EVERY MESSAGE WE SEND NEEDS TO BE

    COPIED โ† BALANCE: MOVING CODE TO A WORKER VS COMMUNICATION OVERHEAD/WORKER BEING BUSY โ† postMessage IS A FIRE-AND-FORGET MESSAGING MECHANISM WITH NO BUILT-IN UNDERSTANDING OF REQUEST AND RESPONSE ๐Ÿคฏ
  25. WORKERS: SHARED MEMORY โ† ONE DEDICATED TYPE: SharedArrayBuffer โ† A

    LINEAR CHUNK OF MEMORY THAT CAN BE MANIPULATED USING TypedArrays OR DataViews โ† IF SENT VIA postMessage, THE OTHER END GETS A HANDLE TO THE EXACT SAME MEMORY CHUNK ๐Ÿคฏ
  26. WORKERS: SHARED MEMORY โ† MOST OF THE APIS ARE BUILT

    NO CONCURRENT ACCESS TO OBJECTS IN MIND โ† YOU BUILD YOUR OWN MUTEXES AND OTHER CONCURRENT DATA STRUCTURES โ† NO DIRECT WAY OF WORKING ON FAMILIAR OBJECTS/ ARRAYS; JUST A SERIES OF BYTES ๐Ÿคฏ
  27. WORKERS: WEB ASSEMBLY โ† WORKERS + SharedArrayBuffers TO SUPPORT THE

    THREADING MODEL OF C++ AND OTHERS โ† BEST EXPERIENCE FOR SHARED-MEMORY MODEL โ† FASTER THAN JS WHEN YOU STAY WITHIN WASM, BUT THE MORE YOU HAVE TO CROSS OVER TO JS APIS THE SLOWER IT IS
  28. WORKERS: WEB ASSEMBLY โ† JAVASCRIPT IS OFTEN FASTER AT DOING

    DOM RENDERING โ† HIGH-LEVEL LIBRARIES CAN BE MORE PERFORMANT THAN LOW-LEVEL WASM IMPLEMENTATIONS โ† DOESNโ€™T OFFER LOT OF THE BENEFITS (AND COMFORT) OF JAVASCRIPT
  29. WORKERS โ† GOOD FOR DATA PROCESSING AND CRUNCHING NUMBERS โ†

    HARD TO USE FOR UI-RELATED STUFF โ† HARDER THAN ADJUSTING IT FOR A SCHEDULER
  30. If you were to summarize Concurrent React in one word/expression,

    whatโ€™d be your pick? DEEP DIVING ON CONCURRENT REACT /
  31. SCHEDULING IN REACT HEURISTICS COOPERATIVE MULTITASKING WITH A SINGLE INTERRUPTIBLE

    RENDERING THREAD PRIORITY LEVELS REGISTER CALLBACKS WITH DIFFERENT PRIORITY LEVELS IN THE BROWSER RENDER LANES ABSTRACTIONS AROUND A BITMASK; BRING GRANULARITY, AVOID OVERHEAD & ALLOW BATCHING
  32. SCHEDULING IN REACT function resourcefulOperation(value: number) { let newValue =

    String(value); for (let i = 0; i < 1000000; i++) { newValue = `${value} + ${i} = ${value + i}`; } return newValue; } function ResourcefulComponent(props: { value: number }) { const { value } = props; const result = resourcefulOperation(value); return <p>{result}</p>; }
  33. SCHEDULING IN REACT function resourcefulOperation(value: number) { let newValue =

    String(value); for (let i = 0; i < 1000000; i++) { newValue = `${value} + ${i} = ${value + i}`; } return newValue; } function ResourcefulComponent(props: { value: number }) { const { value } = props; const result = resourcefulOperation(value); return <p>{result}</p>; }
  34. function* resourcefulOperation(value: number) { let newValue = String(value); while (true)

    { yield; for (let i = 0; i < 1000000; i++) { newValue = `${value} + ${i} = ${value + i}`; } return newValue; } } const initialValue = 0; const scheduler = new Scheduler(resourcefulOperation, initialValue); function ResourcefulComponent(props: { value: number }) { const { value } = props; const result = scheduler.performUnitOfWork(value); return <p>{result}</p>; }
  35. function* resourcefulOperation(value: number) { let newValue = String(value); while (true)

    { yield; for (let i = 0; i < 1000000; i++) { newValue = `${value} + ${i} = ${value + i}`; } return newValue; } } const initialValue = 0; const scheduler = new Scheduler(resourcefulOperation, initialValue); function ResourcefulComponent(props: { value: number }) { const { value } = props; const result = scheduler.performUnitOfWork(value); return <p>{result}</p>; } PROMOTED TO A GENERATOR YIELDING EXECUTION DOING CONCURRENT TASKS
  36. enum SchedulerState { IDLE = "IDLE", PENDING = "PENDING", DONE

    = "DONE", } class Scheduler<T> { state: SchedulerState; result: T; worker: (data: T) = > Generator; iterator: Generator; constructor(worker: (data: T) = > Generator, initialResult: T) { this.state = SchedulerState.IDLE; this.worker = worker; this.result = initialResult; } performUnitOfWork(data: T) { switch (this.state) { case "IDLE": this.state = SchedulerState.PENDING; this.iterator = this.worker(data); throw Promise.resolve(); case "PENDING": const { value, done } = this.iterator.next(); if (done) { this.result = value; this.state = SchedulerState.DONE; return value; } throw Promise.resolve(); case "DONE": this.state = SchedulerState.IDLE; return this.result; } } }
  37. performUnitOfWork(data: T) { switch (this.state) { case "IDLE": this.state =

    SchedulerState.PENDING; this.iterator = this.worker(data); throw Promise.resolve(); case "PENDING": const { value, done } = this.iterator.next(); if (done) { this.result = value; this.state = SchedulerState.DONE; return value; } throw Promise.resolve(); case "DONE": this.state = SchedulerState.IDLE; return this.result; } }
  38. function resourcefulOperation(value: number) { let newValue = String(value); for (let

    i = 0; i < 1000000; i++) { newValue = `${value} + ${i} = ${value + i}`; } return newValue; } function ResourcefulComponent(props: { value: number }) { const { value } = props; const result = resourcefulOperation(value); return <p>{result}</p>; }
  39. function resourcefulOperation(value: number) { let newValue = String(value); for (let

    i = 0; i < 1000000; i++) { newValue = `${value} + ${i} = ${value + i}`; } return newValue; } function ResourcefulComponent(props: { value: number }) { const [_, startTransition] = useTransition(); const [result, setResult] = useState(""); useEffect(() = > { startTransition(() = > { const newResult = resourcefulOperation(props.value); setResult(newResult); }); }, [props.value]); return <p>{result}</p>; }
  40. HEURISTICS โ† A COOPERATIVE MULTITASKING MODEL โ† A SINGLE INTERRUPTIBLE

    RENDERING THREAD โ† RENDERING CAN BE INTERLEAVED WITH OTHER MAIN THREAD TASKS AND OTHER REACT RENDERS โ† AN UPDATE CAN HAPPEN IN THE BACKGROUND WITHOUT BLOCKING THE RESPONSE TO NEW INPUT
  41. HEURISTICS โ†“ ORIGINAL RENDER TASK USER INPUT โ†’ โ†‘ HIGHER

    PRIORITY RENDER TASK โ†“ RESUME ORIGINAL RENDER TASK
  42. HEURISTICS โ† IT YIELDS EXECUTION IS BACK TO THE MAIN

    THREAD EVERY 5MS โ† IT'S SMALLER THAN A SINGLE FRAME EVEN ON 120FPS, SO IT WON'T BLOCK ANIMATIONS โ† IN PRACTICE, RENDERING IS INTERRUPTIBLE
  43. BRANCHING WORKFLOW โ€” EVERYTHING YOU NEED TO KNOW ABOUT CONCURRENT

    REACT (WITH A LITTLE BIT OF SUSPENSE) โ€ข HENRIQUE YUJI
  44. BRANCHING WORKFLOW โ€” EVERYTHING YOU NEED TO KNOW ABOUT CONCURRENT

    REACT (WITH A LITTLE BIT OF SUSPENSE) โ€ข HENRIQUE YUJI
  45. BRANCHING WORKFLOW โ€” EVERYTHING YOU NEED TO KNOW ABOUT CONCURRENT

    REACT (WITH A LITTLE BIT OF SUSPENSE) โ€ข HENRIQUE YUJI
  46. PRIORITY LEVELS PRIORITY TIMEOUT WHEN I m m ediate SYNCHRONOUSLY

    TASKS THAT NEED TO RUN SYNCHRONOUSLY UserBlocking 250MS RESULTS OF A USER INTERACTION (E.G. A BUTTON CLICK) Normal 5S UPDATES THAT DONโ€™T HAVE TO FEEL INSTANTANEOUS Low 10S TASKS THAT CAN BE DEFERRED BUT MUST STILL COMPLETE EVENTUALLY (E.G. AN ANALYTICS NOTIFICATION) Idle NO TIMEOUT TASKS THAT DO NOT HAVE TO RUN AT ALL (E.G. HIDDEN OFFSCREEN CONTENT)
  47. RENDER LANES โ† ONE LANE = ONE BIT IN A

    BITMASK โ† ONE UPDATE IN REACT = ONE LANE โ† ONE RENDER IN REACT = ONE OR MORE LANES โ† UPDATES IN THE SAME LANE RENDER IN THE SAME BATCH. โ† DIFFERENT LANES, SEPARATE BATCHES. ๐Ÿคฏ
  48. RENDER LANES โ† 31 LEVELS OF GRANULARITY (= ONE BITMASK)

    โ† ALLOWS TO CHOOSE WHETHER TO RENDER MULTIPLE TRANSITIONS IN A SINGLE BATCH OR RENDER THEM INDEPENDENTLY โ† REDUCES OVERHEAD OF MULTIPLE LAYOUT PASSES, STYLE RECALCULATIONS, AND MULTIPLE PAINTS ๐Ÿคฏ
  49. SCHEDULING (FOR THE REST OF US) HANDLING LOTS OF DATA

    WITH THE useTransition HOOK TACKLING WASTED RENDERS WITH THE useSyncExternalStore HOOK HYDRATION IMPROVEMENTS WITH SELECTIVE HYDRATION & CONCURRENT REACT PROFILER ENHANCEMENTS INSPECT TRANSITIONS, GET WARNS, AND MUCH MORE!
  50. HANDLING LARGE SETS OF DATA ๐Ÿ˜” NON-PRACTICALโ€ฆ โ† FINDING PRIMES

    โ† CRACKING PASSWORDS โ† SIERPINSKI TRIANGLE ๐Ÿ˜Š PRACTICALโ€ฆ โ† RENDERING MANY DATA-POINTS โ† RENDERING ON A <canvas> โ† PROCESSING DATA
  51. HANDLING LARGE SETS OF DATA ๐Ÿ˜” NON-PRACTICALโ€ฆ โ† FINDING PRIMES

    โ† CRACKING PASSWORDS โ† SIERPINSKI TRIANGLE ๐Ÿ˜Š PRACTICALโ€ฆ โ† RENDERING MANY DATA-POINTS โ† RENDERING ON A <canvas> โ† PROCESSING DATA
  52. DAILY VISITORS: BEFORE const DailyVisitors = () = > {

    const [data, setData] = useState(initialData); useEffect(() = > { setData(initialData); }, []); const onChange = (newData) = > { setData(newData); }; return ( <Dashboard data={data} initialData={initialData} onChange={onChange} /> ); }; export default DailyVisitors;
  53. DAILY VISITORS: BEFORE const DailyVisitors = () = > {

    const [data, setData] = useState(initialData); const [, startTransition] = useTransition(); useEffect(() = > { setData(initialData); }, []); const onChange = (newData) = > { startTransition(() = > { setData(newData); }); }; return ( <Dashboard data={data} initialData={initialData} onChange={onChange} /> ); }; export default DailyVisitors;
  54. โ† หœ100K DATA POINTS PLOTTED โ† SUPPORT FOR SEARCHING AND

    FILTERING โ† USED WORKERS + REDUX-SAGA UTILITIES + DEBOUNCING โ† COULD'VE USED TRANSITIONS LOCATION DATA #1 of 2
  55. โ† THOUSANDS OF REAL-TIME PLAYERS MESSAGING โ† SUPPORT FOR SEARCHING

    AND FILTERING โ† USED VIRTUALIZATION AND MEMOIZATION โ† COULD'VE USED TRANSITIONS GAME ADMIN PANEL #2 of 2
  56. useSyncExternalStore function useSyncExternalStore<Snapshot>( subscribe: (onStoreChange: () = > void) =

    > () = > void, getSnapshot: () = > Snapshot, getServerSnapshot?: () = > Snapshot ): Snapshot;
  57. useLocation function Pathname() { const { pathname } = useLocation();

    return <Badge title={pathname} subtitle="pathname" />; } function Hash() { const { hash } = useLocation(); return <Badge title={hash} subtitle="hash" />; }
  58. useLocation function Pathname() { const { pathname } = useLocation();

    return <Badge title={pathname} subtitle="pathname" />; } function Hash() { const { hash } = useLocation(); return <Badge title={hash} subtitle="hash" />; } OVER-RETURNING HOOK
  59. useHistorySelector function useHistorySelector(selector) { const history = useHistory(); return useSyncExternalStore(history.listen,

    () = > selector(history)); } function Pathname() { const pathname = useHistorySelector((history) = > history.location.pathname); return <Badge title={pathname} subtitle="pathname" />; } function Hash() { const hash = useHistorySelector((history) = > history.location.hash); return <Badge title={hash} subtitle="hash" />; }
  60. useHistorySelector function useHistorySelector(selector) { const history = useHistory(); return useSyncExternalStore(history.listen,

    () = > selector(history)); } function Pathname() { const pathname = useHistorySelector((history) = > history.location.pathname); return <Badge title={pathname} subtitle="pathname" />; } function Hash() { const hash = useHistorySelector((history) = > history.location.hash); return <Badge title={hash} subtitle="hash" />; }
  61. HYDRATION: BEFORE FETCHING DATA (SERVER) RENDERING HTML (SERVER) LOADING CODE

    (CLIENT) HYDRATING TIME TO FIRST BYTE FIRST CONTENTFUL PAINT TIME TO INTERACTIVE 124
  62. โ† HYDRATION COULD ONLY BEGIN AFTER THE ENTIRE DATA WAS

    FETCHED AND RENDERED โ† USERS COULDNโ€™T INTERACT WITH THE PAGE UNTIL HYDRATION WAS COMPLETE FOR THE WHOLE PAGE โ† PARTS OF YOUR APP THAT LOAD FAST WOULD ALWAYS HAVE TO WAIT FOR THE SLOW ONES HYDRATION: BEFORE
  63. HYDRATION: BEFORE FETCHING DATA (SERVER) RENDERING HTML (SERVER) LOADING CODE

    (CLIENT) HYDRATING TIME TO FIRST BYTE FIRST CONTENTFUL PAINT TIME TO INTERACTIVE 127
  64. HYDRATION: AFTER 128 TIME TO FIRST BYTE FIRST CONTENTFUL PAINT

    TIME TO INTERACTIVE [โ€ฆ] FETCHING DATA (SERVER) RENDERING HTML (SERVER) HYDRATING LOADING CODE (CLIENT) [โ€ฆ] [โ€ฆ] [โ€ฆ] [โ€ฆ] [โ€ฆ] [โ€ฆ]
  65. โ† pipeToNodeStream + createRoot + <Suspense> โ† REACT PRIORITIZES HYDRATING

    THE PARTS THAT THE USER INTERACTED WITH BEFORE THE REST โ† COMPONENTS CAN BECOME INTERACTIVE FASTER BY ALLOWING THE BROWSER TO DO OTHER WORK AT THE SAME TIME AS HYDRATION HYDRATION: AFTER
  66. โ† REACT WON'T WAIT FOR HUGE COMPONENTS TO LOAD TO

    CONTINUE STREAMING HTML FOR THE REST OF THE PAGE โ† WHEN THE HTML BECOMES AVAILABLE ON THE SERVER, IT WILL BE ADDED TO THE SAME STREAM ALONG WITH A SCRIPT TAG AND INSERTED IN THE RIGHT PLACE HYDRATION: AFTER
  67. SCHEDULING ON THE WEB WE HAVE A FEW SCHEDULING PRIMITIVES:

    โ† setTimeout โ† requestAnimationFrame โ† requestIdleCallback โ† postMessage
  68. SCHEDULING ON THE WEB โ† WE ALL SHOULD USE THE

    SAME SCHEDULER โ† HAVING MORE THAN ONE SCHEDULER CAUSES RESOURCE FIGHTING โ† INTERLEAVING TASKS WITH BROWSER WORK (RENDERING, GARBAGE COLLECTION, ETC.)
  69. SCHEDULING API โ† A MORE ROBUST SOLUTION FOR SCHEDULING TASKS

    โ† INTEGRATED DIRECTLY INTO THE EVENT LOOP โ† CONTROL AND SCHEDULE PRIORITIZED TASKS IN A UNITED AND FLEXIBLE WAY โ† ALIGNED WITH THE WORK OF THE REACT TEAM AND IN COOPERATION WITH GOOGLE, W3C AND OTHERS
  70. SCHEDULING API scheduler.postTask() SCHEDULE AND CONTROL PRIORITIZING TASKS. scheduler.wait() YIELD

    AND RESUME AFTER SOME AMOUNT OF TIME OR PERHAPS AFTER AN EVENT HAS OCCURRED. scheduler.yield() BREAK UP LONG TASKS BY YIELDING TO THE BROWSER AND CONTINUING AFTER BEING RESCHEDULED. isInputPending() DETERMINE IF THE CURRENT TASK IS BLOCKING INPUT EVENTS.
  71. SCHEDULING API: postTask scheduler.postTask(() = > { console.log('React Brussels'); },

    { delay: 10 }); scheduler.postTask(() = > { console.log('React India'); }); scheduler.postTask(() = > { console.log('React Alicante'); }); / / 'React India' 'React Alicante' 'React Brussels'
  72. SCHEDULING API: postTask const controller = new TaskController({ priority: "user-blocking"

    }); const signal = controller.signal; console.log(signal.priority); / / 'user-blocking' console.log(signal.aborted); / / 'false' scheduler.postTask(doWork, { signal }); controller.setPriority("background"); controller.abort();
  73. SCHEDULING API: isInputPending while (workQueue.length > 0) { if (navigator.scheduling.isInputPending())

    { / / Stop doing work to handle any input event break; } let job = workQueue.shift(); job.execute(); }
  74. SCHEDULING API: yield async function doWork() { while (true) {

    let hasMoreWork = doSomeWork(); if (!hasMoreWork) { return; } if (!navigator.scheduling.isInputPending()) { continue; } await scheduler.yield(); } } ๐Ÿคฏ
  75. SCHEDULING: RECAP โ† SCHEDULING IS AN ALTERNATIVE FOR RESPONSIVE USER

    INTERFACES โ† A WEB STANDARDS PROPOSAL THAT BRINGS A UA SCHEDULER TO THE BROWSER IS BEING COOKED โ† WE CAN SOLVE A LOT AT THE FRAMEWORK LEVEL WITH CONCURRENT REACT AND ITS SCHEDULER
  76. #noSilverBullet ๐Ÿ—ฃ Code that yields too often can cause the

    overhead of scheduling tasks to become a negative influence on your appโ€™s overall performance.
  77. SCHEDULING: BAD PARTS โ† DETECTION AND SCHEDULING HAVE AN OVERHEAD

    โ† NO CORRECT CHUNK SIZE THAT FITS ALL DEVICES โ† PARTIALLY COMPLETE INTERFACES CAN INCREASE THE TOTAL COST OF LAYOUT AND PAINT โ† HARD TO FIND THE RIGHT SPOT BETWEEN PERF AND AMOUNT OF BLOCKING ๐Ÿคฏ
  78. โ† HEAVY COMPONENTS SHOULD BE WRAPPED IN React.memo โ† THEIR

    PROPS MEMOIZED WITH useMemo AND useCallback โ† HEAVY OPERATIONS SHOULD BE MEMOIZED WITH useMemo โ† isPending SHOULD NOT BE PASSED AS A PROP OR DEPENDENCY TO ANYTHING FROM THE ABOVE SCHEDULING: BAD PARTS
  79. #protip ๐Ÿ’ก Start with observability services or libraries like web-vitals.

    Then create your own abstractions on top of the web + React (e.g. custom hooks).
  80. MEASURE: TIMING USER TIMING ALLOWS YOU TO MARK POINTS IN

    TIME AND THEN MEASURE THE DURATION BETWEEN THOSE MARKS. EVENT TIMING EVENT PROCESSING TIME + TIME UNTIL THE NEXT FRAME CAN BE RENDERED. THE BASIS FOR THE FID METRIC. ELEMENT TIMING MEASURE THE RENDER TIME OF SPECIFIC ELEMENTS. THE BASIS FOR THE LCP METRIC.
  81. MEASURE: PROFILING LONG TASKS API REPORTS TASKS THAT TAKES LONGER

    THAN 50 MS AND IT'S THE BASIS FOR TTI AND TBT METRICS JS SELF-PROFILING API PROFILE SPECIFIC COMPLEX OPERATIONS AND IDENTIFY HOT SPOTS USING A SAMPLING PROFILER UserAgentSpecificMemory DETECT MEMORY LEAKS IN APPS THAT HANDLE A HUGE VOLUME OF DATA
  82. MEASURE: PROFILING { name: "same-origin-descendant", entryType: "longtask", startTime: 1023.40999995591, duration:

    187.19000002602115, attribution: [ { name: "unknown", entryType: "taskattribution", startTime: 0, duration: 0, containerType: "iframe", containerSrc: "child.html", containerId: "", containerName: "child1" } ] } { bytes: 1000000, breakdown: [ { bytes: 1000000, attribution: [ { url: "https://example.com", scope: "Window", }, ], types: ["JS", "DOM"], }, { bytes: 0, attribution: [], types: [], }, ], } { "frames": [ { "name": "Profiler" }, { "column": 0, "line": 100, "name": "", "resourceId": 0 }, { "name": "set innerHTML" }, { "column": 10, "line": 10, "name": "A", "resourceId": 1 } { "column": 20, "line": 20, "name": "B", "resourceId": 1 } ], "resources": [ "https://example.com/page", "https://example.com/app.js", ], "samples": [ { "stackId": 0, "timestamp": 161.99500000476837 }, { "stackId": 2, "timestamp": 182.43499994277954 }, { "timestamp": 197.43499994277954 }, { "timestamp": 213.32999992370605 }, { "stackId": 3, "timestamp": 228.59999990463257 }, ], "stacks": [ { "frameId": 0 }, { "frameId": 2 }, { "frameId": 3 }, { "frameId": 4, "parentId": 2 } ] } LONG TASKS SELF-PROFILING USERAGENT MEMORY ๐Ÿคฏ
  83. THE FUTURE โ† I/O LIBRARIES LIKE react-fetch โ† BUILT-IN <Cache>

    FOR DATA FETCHING LIBRARIES TO INTEGRATE WITH <Suspense> โ† REACT SERVER COMPONENTS โ† NATIVE SCHEDULING PRIMITIVES ON THE BROWSER โ† AND MUCH MORE! ๐Ÿคฏ
  84. OFFSCREEN: RECAP โ† RENDERING IN THE BACKGROUND WITH NO ADDITIONAL

    PERFORMANCE OVERHEAD โ† IT DOESN'T ACTUALLY MOUNT UNTIL THE COMPONENT BECOMES VISIBLE AND ITS EFFECTS ARE NOT FIRED โ† TOGGLE THE VISIBILITY WITHOUT LOSING STATE โ† INTEGRATED INTO ROUTERS AND OTHER UI LIBRARIES
  85. OFFSCREEN: USE CASES โ† ROUTERS CAN PRERENDER SCREENS IN THE

    BACKGROUND SO THAT THEYโ€™RE INSTANTLY AVAILABLE. โ† TAB SWITCHING CAN PRESERVE THE STATE OF HIDDEN TABS, SO THE USER CAN SWITCH BETWEEN THEM WITHOUT LOSING THEIR PROGRESS. โ† VIRTUALIZED LIST CAN PRERENDER ADDITIONAL ROWS ABOVE AND BELOW THE VISIBLE WINDOW.
  86. SUSPENSE FOR CPU-BOUND TREES โ† FORCES A FALLBACK ON THE

    INITIAL RENDER REGARDLESS OF WHETHER SOMETHING IS SUSPENDED. โ† DURING THE INITIAL MOUNT, REACT WILL SKIP OVER EXPENSIVE TREES BY RENDERING A PLACEHOLDER. โ† IT HELPS UNBLOCK THE INITIAL SKELETON FOR THE NEW SCREEN.
  87. TRANSITION TRACING โ† USE THE PROFILER API AND DEVTOOLS PROFILER/

    TIMELINE TO WATCH FOR PERFORMANCE REGRESSIONS FOR SPECIFIC TRANSITIONS. โ† DETECT WHEN TRANSITIONS BECOME SLOWER AND INVESTIGATE WHY THEY MAY BE SLOW. โ† A NEW VERSION OF THE INTERACTION TRACING API.
  88. function App() { const user = getViewingUser(); const [pageName, setPageName]

    = useState("homefeed"); const onNavigate = (pageName) = > { startTransition(() = > setPageName(pageName), { name: pageName }); }; return ( <> <NavBar onNavigate={onNavigate} /> <Page name={pageName} id={user.id} /> </> ); } TRANSITION TRACING โ€” TRANSITION TRACING API RFC
  89. const onTransitionStart = (transitionName, startTime) = > . . .

    ; const onTransitionComplete = (transitionName, startTime, endTime) = > . . . ; const onTransitionProgress = (transitionName, startTime, currentTime, pendingSuspenseBoundaries) = > . . . ; const root = React.createRoot(container, { transitionCallbacks: { onTransitionStart, onTransitionComplete, onTransitionProgress, }, }); TRANSITION TRACING โ€” TRANSITION TRACING API RFC
  90. function Profile({ id }) { return ( <TracingMarker name="profile"> <Suspense

    fallback={<LoadingSpinner />}> <ProfileHeader id={id} /> <TracingMarker name="profile:photo-feed"> <div>Photos</div> <Suspense fallback={<LoadingFeed />}> <PhotoFeed /> </Suspense> </TracingMarker> <TracingMarker name="profile:profile-feed"> <div>Profile Feed</div> <Suspense fallback={<LoadingFeed />}> <ProfileFeed /> </Suspense> </TracingMarker> </Suspense> </TracingMarker> ); } TRANSITION TRACING โ€” TRANSITION TRACING API RFC
  91. OPTIMIZING COMPILER โ† IMPROVE THE RENDERING PERFORMANCE BY AUTOMATICALLY GENERATING

    THE EQUIVALENT OF useMemo AND useCallback CALLS. โ† MINIMIZE THE COST OF RE-RENDERING WHILE RETAINING REACTโ€™S PROGRAMMING MODEL. โ† AUTOMATIC REACTIVITY COMPILER.
  92. DEEP DIVING ON CONCURRENT REACT / #1 of 8 REACT

    IS NOT REACTIVE, BUT IT IS CONCURRENT AND THAT MIGHT BE ENOUGH FOR YOU
  93. DEEP DIVING ON CONCURRENT REACT / #2 of 8 REACT

    HAS BEEN PUSHING WEB APIS TO THE FUTURE E.G. THE SCHEDULER API AND DISCUSSIONS AROUND EFFECT HANDLERS
  94. DEEP DIVING ON CONCURRENT REACT / #3 of 8 REACT

    TRIES TO ADDRESS THE LACK OF SOME JS/WEB PLATFORM RESOURCES E.G. EFFECT HANDLERS, CONTINUATIONS & THE SCHEDULER API
  95. DEEP DIVING ON CONCURRENT REACT / #4 of 8 UNDERSTANDING

    THESE INTERNALS AND THEIR RATIONALES HELPS US IMPLEMENT OUR OWN ABSTRACTIONS E.G. THE GENERATOR-BASED SCHEDULER & FIRST CLASS SUPPORT FOR PROMISES
  96. export const Hello = () = > { const value

    = usePromise(() = > delay("Hey there! ๐Ÿ‘‹", 3000)); return <Typography variant="h3">{value}</Typography>; }; function Demo() { return ( <Grid container justifyContent="center"> <Suspense fallback={<Loading message="Loading . . . " />}> <Hello /> </Suspense> </Grid> ); } ๐Ÿคฏ
  97. DEEP DIVING ON CONCURRENT REACT / #5 of 8 SCHEDULING

    DOES NOT NECESSARILY MEAN BETTER PERFORMANCE
  98. DEEP DIVING ON CONCURRENT REACT / #5 of 8 SCHEDULING

    DOES NOT NECESSARILY MEAN BETTER PERFORMANCE โ† NON-URGENT UPDATES TAKE LONGER โ† CANโ€™T HELP SOME EXPENSIVE COMPONENTS โ† EXTRA CPU COST ๐Ÿคฏ
  99. DEEP DIVING ON CONCURRENT REACT / #6 of 8 THERE'S

    NO SILVER BULLET. IDENTIFY YOUR CORE METRICS.
  100. DEEP DIVING ON CONCURRENT REACT / #8 of 8 ALWAYS

    TRY TO CORRELATE BUSINESS METRICS WITH PERFORMANCE