跪拜 Guibai
← All articles
Frontend · React.js · React Native

How React's Reconciler/Renderer Split Runs the Same Code Across Web, Native, and Test

By 老王以为 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Multi-platform React apps that share component logic across web, native, and desktop are possible because the reconciler never learned what a DOM is. Any team facing a "rewrite everything for a new platform" mandate can avoid it by defining a similar HostConfig-style contract — the business logic stays put, and only the platform adapter changes.

Summary

A single `throw` in a 20-line file called ReactFiberConfig.js defines the entire boundary. The reconciler imports `createInstance`, `appendChild`, and `commitUpdate` from that abstract module, never from a concrete platform binding. At build time, Rollup aliases redirect those imports to a 6,669-line DOM implementation for the browser, a Native UIManager implementation for mobile, or a composable set of noop modules for testing. The reconciler's tens of thousands of lines of scheduling and diffing logic remain identical across all targets.

Unsupported capabilities are expressed explicitly — renderers export `supportsMutation = false` and stub functions that throw descriptive errors rather than silently returning `undefined`. The noop test renderer takes this further by splitting capabilities into separate modules (hydration, mutation, persistence, singletons) and composing them on demand, so a single test can simulate exactly the platform profile it needs.

This protocol-driven architecture means the reconciler can upgrade its scheduling algorithm without touching any renderer, and a renderer can add platform features like View Transitions without touching the reconciler. The same pattern applies beyond React: a `StorageConfig` interface lets business logic run on IndexedDB, SQLite, or an in-memory Map depending on the build target.

Takeaways
React's reconciler calls abstract functions like `createInstance` and `appendChild` imported from `ReactFiberConfig.js`, never `document.createElement` or `UIManager.createView` directly.
The actual `ReactFiberConfig.js` file is 20 lines long and contains only a `throw` — it exists to catch misconfigured builds, not to provide an implementation.
At build time, Rollup aliases redirect `ReactFiberConfig` imports to `ReactFiberConfigDOM.js` (6,669 lines of DOM logic) for react-dom, or to a Native HostConfig for react-native.
Renderers declare unsupported capabilities with `supportsMutation = false` and stub functions that throw clear errors, so the reconciler can choose an alternative code path at runtime.
The noop test renderer splits HostConfig into capability modules (hydration, mutation, persistence, singletons) and composes them via `export *` plus selective overrides.
DOM attribute updates in `commitUpdate` are not simple `setAttribute` calls — they handle CSS parsing, controlled form inputs, event delegation, and Fiber node caching, all hidden from the reconciler.
React's core team, the react-dom team, and the react-native community can ship independently because the HostConfig interface is the only contract they share.
The same protocol-driven architecture works for storage (IndexedDB vs SQLite), networking (fetch vs wx.request), and filesystem layers in any multi-platform application.
Conclusions

React's multi-platform story is not a runtime abstraction — it's a build-time module swap. The reconciler source is identical; only the linked HostConfig changes.

The `throw` in ReactFiberConfig.js is a deliberate design choice: a missing build alias produces an immediate, loud failure instead of a silent `undefined` call that would be nearly impossible to debug.

Capability flags like `supportsMutation` let the reconciler branch its algorithm at runtime, which means a single reconciler binary can drive both mutation-based platforms (DOM) and persistence-based platforms (some declarative UIs) without recompilation.

The noop renderer's module-per-capability design is a cleaner alternative to a monolithic mock — it lets tests assemble exactly the platform profile they need and nothing more.

React's architecture inverts the typical multi-platform approach: instead of writing platform-specific components, you write platform-agnostic reconciler logic and let each renderer supply the platform-specific operations.

HostConfig is effectively an internal plugin system. Any platform that implements those dozen functions gets the full React programming model for free, which is why community renderers for Canvas, WebGL, and terminal UIs can exist.

The real cost of platform coupling is not the initial rewrite — it's the ongoing divergence in behavior, features, and bug fixes across platforms that multiplies maintenance burden indefinitely.

Concepts & terms
Reconciler
The platform-agnostic part of React that compares old and new virtual trees, determines what changed, and produces a list of side effects (insert, update, delete). It never directly touches DOM or native APIs.
Renderer
The platform-specific part of React that receives the reconciler's side-effect list and executes it using platform APIs — `document.createElement` for the browser, `UIManager.createView` for React Native, or in-memory objects for tests.
HostConfig
The interface contract between reconciler and renderer, defined as a set of functions like `createInstance`, `appendChild`, `commitUpdate`. Each renderer provides its own implementation; the reconciler imports from an abstract module that gets resolved at build time.
Module alias (Rollup alias)
A build-time mechanism that redirects imports from one module path to another. React uses it to swap `react-reconciler/src/ReactFiberConfig` with `react-dom-bindings/src/client/ReactFiberConfigDOM` when building react-dom.
Null Object Pattern (shim)
Instead of leaving a function undefined when a platform doesn't support a capability, React exports a stub that throws a descriptive error and sets a boolean flag like `supportsMutation = false`. This prevents silent failures and lets the reconciler check capabilities at runtime.
Capability composition
The noop renderer splits HostConfig into separate modules (hydration, mutation, persistence, etc.) and combines them via `export *`. Tests override specific modules to simulate exactly the platform capabilities needed.
Mutation vs. Persistence
Two strategies for applying updates. Mutation modifies existing nodes in place (like DOM's `appendChild`). Persistence creates a new tree and replaces the old one wholesale. The reconciler checks `supportsMutation` and `supportsPersistence` flags to choose the right algorithm.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗