Caso de estudio · Código abierto

react-inp-chunker

Una biblioteca de React y TypeScript headless y de código abierto que evita que el trabajo síncrono pesado bloquee la entrada del usuario. Procesa el trabajo en franjas de pocos milisegundos y cede el hilo principal entre ellas, para que las actualizaciones largas no perjudiquen la métrica Interaction to Next Paint (INP).

npm install react-inp-chunker

Prueba del proyecto

Mi rol

  • Diseñé la API, el motor de planificación y el modelo de estados, y escribí la biblioteca en TypeScript estricto.
  • Redacté la documentación y el playground, que ejecuta el mismo trabajo con y sin división en franjas.
  • Publico y mantengo el paquete en npm bajo la licencia MIT.

Restricciones

  • La biblioteca no podía hacer el trabajo más rápido, solo garantizar que nunca ocupe el hilo principal más de unos milisegundos seguidos.
  • Debía ser headless y funcionar en React 18 y 19, con renderizado en servidor y fuera de React.
  • Los resultados debían ser coherentes en React concurrente, sin nada en ejecución tras cancelar o desmontar.

Decisiones clave

  • Separé un núcleo de planificación sin React de los hooks para que el mismo motor funcione en TypeScript, un Web Worker o Node.
  • Leo el reloj después de cada elemento para limitar el exceso de cada franja a un elemento, y añadí batchSize para trabajo demasiado barato para medirlo elemento a elemento.
  • Elegí el mecanismo para ceder el control según el navegador: scheduler.yield(), luego MessageChannel y luego setTimeout.
Tipo de proyecto
Biblioteca React de código abierto en npm
Rol
Autor y mantenedor
Enfoque principal
INP, planificación del hilo principal, DX
Área técnica
TypeScript, React 18+, Scheduler API
  • Código abierto
  • React
  • TypeScript
  • INP
  • Core Web Vitals
  • Paquete npm

Contexto

Un solo hilo ejecuta el JavaScript de una página, atiende cada clic y cada tecla y pinta la pantalla. Filtrar miles de filas, analizar un CSV grande o construir un índice de búsqueda dentro de un componente de React ocupa ese hilo, y cualquier interacción que llegue mientras tanto espera en una cola.

Una tarea de más de 50 ms es una tarea larga, y una interacción que coincide con una es la causa más común de no superar Interaction to Next Paint (INP), la Core Web Vital de capacidad de respuesta.

Problema

El trabajo suele ser necesario y no siempre puede hacerse más rápido. El objetivo era una primitiva pequeña y tipada que garantice que el trabajo nunca ocupe el hilo principal más de unos milisegundos seguidos, sin obligar a los equipos a reestructurar sus componentes.

  • Un bucle síncrono largo bloquea los clics, la escritura y el pintado hasta que termina.
  • Ceder el control a mano con setTimeout anidados tiene un retardo mínimo de unos 4 ms y reparte la lógica de planificación por los componentes.
  • El progreso, la pausa, la reanudación y la cancelación deben llegar al estado de React sin inconsistencias en el renderizado concurrente.
  • Ninguna actualización de estado puede llegar después de desmontar un componente.

Enfoque

La biblioteca procesa elementos uno tras otro hasta agotar la franja de tiempo (5 ms por defecto) o hasta que el navegador indica que hay una interacción pendiente. Entonces cede el control para que el navegador atienda la entrada y pinte, y continúa donde se detuvo.

Cede el control con la mejor herramienta disponible en el navegador: scheduler.yield() donde se admite, MessageChannel en Safari y navegadores antiguos, y setTimeout(0) como último recurso.

  • useChunkedTask aplica una función a una lista y vuelve a renderizar con el progreso una vez por franja, no una vez por elemento.
  • useDeferredTask ejecuta trabajo escrito como generador y se detiene en cada yield cuando se agota la franja.
  • react-inp-chunker/core ofrece createChunkScheduler y createDeferredTask sin importar React, para TypeScript sin framework, un Web Worker o un script de Node.
  • Ambos motores ofrecen start, pause, resume, cancel, un par subscribe y getSnapshot, y una promesa settled que siempre se resuelve.
  • batchSize y collectResults ajustan los casos extremos; la mayoría del código no necesita ninguno.

Garantías y avisos para desarrollo

Una franja se excede como máximo en un elemento, o en batchSize elementos si se aumenta esa opción, porque el reloj se lee después de cada elemento. Una tarea terminada nunca informa de sus resultados un render tarde, nada se ejecuta después de cancel() y ninguna actualización de estado llega tras desmontar.

En desarrollo, un elemento que bloquea más de 50 ms se informa con su índice, y el trabajo tan barato que domina la lectura del reloj recibe una sugerencia de batchSize. Ambos avisos se eliminan del build de producción que los bundlers eligen mediante la condición de exportación production.

Compatibilidad, calidad y tamaño

El paquete admite React 18 y 19, Node 18, 20, 22 y 24, ESM y CommonJS, y renderizado en servidor, donde ambos hooks renderizan un estado inactivo y no ejecutan nada. La suite de pruebas se ejecuta en ambas versiones mayores de React con un 100% de cobertura de sentencias, ramas, funciones y líneas.

No tiene dependencias además de React. El README indica 1,83 kB para useChunkedTask, 1,72 kB para useDeferredTask, 1,34 kB para createChunkScheduler sin React y 2,35 kB para todo, minificados y comprimidos con Brotli. Las versiones se publican desde CI mediante npm Trusted Publishing con procedencia firmada.

Resultados

  • Publicado en npm como react-inp-chunker bajo la licencia MIT, con documentación y playground en GitHub Pages.
  • Ofrece dos hooks de React, useChunkedTask y useDeferredTask, y un núcleo sin React que ejecuta el mismo motor de planificación.
  • Mantiene cada franja de trabajo en pocos milisegundos por defecto, para que la entrada y el pintado puedan ejecutarse entre franjas.
  • Comprueba en cada push su compatibilidad declarada, incluidos React 18 y 19, cuatro versiones de Node, ESM y CommonJS.

Próximos pasos

  • Lee la documentación y prueba el playground.
  • Instálalo con npm install react-inp-chunker.
  • Informa de problemas o contribuye en GitHub.