React Internals
Combining fibers and hooks into a reconciling rendering system, the React core tracks and renders changes in a way that’s highly performant and robust. React’s internal implementation (old or new) works in tandem with its interface and expressive JSX syntax to redefine the relationship between the front-end developer and the browser. The underlying code is so solid that we can take it purely at face value.
While I don’t always think heavy frameworks like React are necessary for every job, I do think that their inner workings are treasure troves of sophisticated code, written by hundreds of smart folks who have deep knowledge of the inner workings of the browser and the JavaScript interpreter. Below are some notes and some additional readings I’ve found on the subject.
Aside on Continuations
Before we get into the real meat and potatoes, it’s important to understand continuations. To oversimplify, continuations are essentially what we call callbacks in JavaScript. Continuations are functions passed to other functions that will be called upon the calculation of the result rather than returning up the stack. In functional programming languages like Scheme, continuation-passing style is often written to bypass the negative effects recursion has on the call stack, meaning that these functions can either be called instantly, or put into an event queue (like JavaScript’s event loop). In fact, because JavaScript does not implement tail call optimization, it must rely either on the event queue or trampoline (see Dr. Axel Rauschmayer’s “Asynchronous programming and continuation-passing style in JavaScript” for a ton of detail on these strategies). As you’ll see if Fiber, the implementation isn’t necessarily to utilize the call stack for workloads, but rather to sidestep these issues altogether by implementing a custom workload queue.
Fibers
Early versions of React revolutionized front-end frameworks by introducing reconciliation, using a virtual DOM and a diffing algorithm to make fewer costly render changes. Rather than write imperative JavaScript to tell the browser how to modify the view, you could instead write declarative React code, and internally the React core would determine which changes were necessary. In version 16.0.0, the core was rearchitected to introduce fibers. From a high level, React Fiber was to the call stack what the virtual dom is to the DOM.
Prior to fibers, a problem with large React applications with hundreds or thousands of components is that a rerender would cause a huge number changes to pile up in the call stack. If enough of these pile up, this pushes asynchronous callbacks and animations back while the interpreter finishes working through its current workload. By re-implementing the call stack in a way that better suits rendering components, it can prioritize, pause, and resume work, what the React team refers to as time slicing.
Suspense is a similar scheduling mechanism around suspending components that depend on some asynchronous data. Suspense allows other components to take priority until the data is returned, and gives the developer more control over what happens while awaiting this data.
Hooks
Hooks are simplified functions that can be called only at the top level of functional components that tap into the renderer and in a way to allow them access to state and lifecycle methods that are normally reserved for class components. The biggest barrier to understanding how these work is the fact that from the outside, you have a single function calling some arcane React method whose only perceivable trait is that it starts with use
.
import { useState } from 'react';
function MyComponent() {
const [score, setScore] = useState(0);
return (
<div>
<p>Score: {score}</p>
</div>
)
}
React registers this component and on its first pass will run through any hooks and create state and lifecycle callbacks necessary. The core will keep track of which component is being processed (now referred to as a fiber) and will be able to reuse this existing state on subsequent renders. setScore
are set up to dispatch back to this state and flag this component as changed.
Resources
- Didact Fiber: Incremental reconciliation
- Continuations, coroutines, fibers, effects
- Getting Closure on React Hooks by Shawn Wang
- Why React is Not Reactive by Shawn Wang
- React Query
- To Understand Concurrent React, Look Outside React
- A journey through the implementation of the useState hook
- A journey through ReactDOM.render — An explanation of how React manages the DOM and state
- Under the hood of React’s hooks system
- Building React from Scratch
- A deep dive into React Fiber internals
- To Understand React Fiber, You Need to Know About Threads
- React Fiber Architecture
- Get Inside the React Source Code - Nir Kaufman