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

Offloading Heavy Computation in React with Web Workers and useRef

By dzhd ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A 50ms synchronous task drops frames and locks the UI; React's concurrent features don't fix that. Web Workers are the only browser-native way to run expensive computation without jank, and pairing them with useRef and useEffect gives a clean, leak-free integration pattern that works today in any React project.

Summary

JavaScript's single-threaded design keeps DOM operations safe but chokes on heavy computation. The Event Loop only reschedules work on the same thread; it cannot prevent a 5-billion-iteration loop from freezing the page. Web Workers solve this by running code in an independent V8 instance with its own memory heap, communicating with the main thread through structured-cloned messages via postMessage.

In a React component, a Worker instance belongs in a useRef, not useState, because its lifecycle changes should not trigger re-renders. The Worker is created inside useEffect after the first paint, and terminated in the cleanup function to prevent memory leaks. The main thread sends task parameters and receives results through onmessage, updating only the result and loading states that actually drive the UI.

This pattern suits any pure computation that exceeds a 50ms frame budget: game physics, local LLM inference, encryption, large dataset processing, and media manipulation. It fails for anything touching the DOM, window, or requiring shared memory without serialization overhead.

Takeaways
JavaScript remains single-threaded; the browser opens a separate V8 instance for each Web Worker, giving true parallel execution without changing the language model.
Web Workers cannot access the DOM, window, or any UI APIs; they communicate with the main thread exclusively through postMessage, which uses structured cloning to copy data across thread boundaries.
Store a Worker instance in useRef, not useState, because creating, posting, and receiving messages should not trigger React re-renders.
Create the Worker inside useEffect to avoid blocking the first paint, and call terminate() plus null the ref in the cleanup function to prevent memory leaks and orphaned threads.
A 5-billion-iteration loop running in a Worker leaves the main thread free to handle clicks, scrolls, and input, so the page stays responsive throughout.
Structured cloning copies data, so passing enormous objects has a cost; send only the necessary parameters, not entire datasets.
Use a Worker when a task is pure computation that takes longer than 50ms; for lightweight async work, fetch and standard state management are sufficient.
Conclusions

The architectural insight is not that Workers exist, but that React's hook model maps cleanly onto their lifecycle: useRef for non-reactive instance holding, useEffect for symmetric setup and teardown, and useState only for the computed results that the UI actually renders.

Many developers reach for setTimeout or requestIdleCallback to break up long tasks, but those still run on the main thread and produce jank. The threshold for a Worker is lower than most assume: any synchronous block exceeding 50ms, not just exotic workloads like LLMs or game engines.

The structured-clone constraint is both a safety feature and a performance ceiling. It prevents shared-memory bugs but makes Workers impractical for tasks that need frequent access to large, mutable state without copying overhead.

Concepts & terms
Web Worker
A browser API that runs JavaScript in a separate thread with its own V8 engine instance, memory heap, and event loop. Workers cannot access the DOM or window object and communicate with the main thread via postMessage using structured cloning.
Structured Clone Algorithm
The serialization mechanism used by postMessage to copy data between threads. It creates a deep clone of the data, meaning the sending and receiving threads hold independent copies with no shared references. Functions, DOM nodes, and certain objects cannot be cloned.
useRef
A React hook that returns a mutable object whose .current property persists across renders without triggering re-renders when changed. Ideal for holding non-reactive values like timer IDs, Worker instances, or DOM references.
Event Loop
The single-threaded task scheduling mechanism in JavaScript that processes synchronous code, microtasks (Promises), and macrotasks (setTimeout, I/O) in order. It enables asynchronous, non-blocking execution but does not provide parallel computation.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗