React & Frontend

Guia Completo de React Scheduler: Prioridades de Renderização e Time Slicing

13 min de leitura

Guia Completo de React Scheduler: Prioridades de Renderização e Time Slicing

O Scheduler do React: Entendendo o Motor de Renderização O React é uma biblioteca JavaScript que gerencia a renderização eficiente de componentes em interfaces de usuário. No entanto, quando você tem centenas de componentes atualizando simultaneamente, ou operações pesadas ocorrendo em paralelo com interações do usuário, o desempenho pode degradar rapidamente. O Scheduler do React é o mecanismo responsável por organizar quando e como essas atualizações acontecem, garantindo que a aplicação permaneça responsiva. O Scheduler não é apenas um gerenciador de fila. Ele é uma camada sofisticada que trabalha em conjunto com o algoritmo de reconciliação do React (a forma como o React descobre quais mudanças precisam ser aplicadas ao DOM). Quando você clica em um botão, digita em um input ou uma requisição HTTP completa, o Scheduler recebe essas "tarefas" e decide a ordem e o tempo ideal para executá-las, equilibrando a responsividade da interface com a completude das atualizações. Prioridades de Renderização: O Conceito Fundamental Por que Prioridades

<h2>O Scheduler do React: Entendendo o Motor de Renderização</h2>

<p>O React é uma biblioteca JavaScript que gerencia a renderização eficiente de componentes em interfaces de usuário. No entanto, quando você tem centenas de componentes atualizando simultaneamente, ou operações pesadas ocorrendo em paralelo com interações do usuário, o desempenho pode degradar rapidamente. O <strong>Scheduler do React</strong> é o mecanismo responsável por organizar quando e como essas atualizações acontecem, garantindo que a aplicação permaneça responsiva.</p>

<p>O Scheduler não é apenas um gerenciador de fila. Ele é uma camada sofisticada que trabalha em conjunto com o algoritmo de reconciliação do React (a forma como o React descobre quais mudanças precisam ser aplicadas ao DOM). Quando você clica em um botão, digita em um input ou uma requisição HTTP completa, o Scheduler recebe essas &quot;tarefas&quot; e decide a ordem e o tempo ideal para executá-las, equilibrando a responsividade da interface com a completude das atualizações.</p>

<h2>Prioridades de Renderização: O Conceito Fundamental</h2>

<h3>Por que Prioridades Existem?</h3>

<p>Nem todas as atualizações do React são iguais. Quando você digita em um campo de input, essa atualização deve ser instantânea — é uma interação direta do usuário. Já uma atualização de dados em background (como sincronizar dados com um servidor) pode esperar alguns milissegundos sem prejudicar a experiência. O React define diferentes <strong>níveis de prioridade</strong> para distinguir essas situações.</p>

<p>As prioridades no React estão organizadas em um espectro, da mais alta para a mais baixa:</p>

<ol>

<li><strong>ImmediatePriority (Urgente)</strong> — Sincronização com o navegador, eventos de click/touch imediatos</li>

<li><strong>UserBlockingPriority (Bloqueadora ao Usuário)</strong> — Eventos de input, hover, animações</li>

<li><strong>NormalPriority (Normal)</strong> — Atualizações de dados, requisições HTTP completadas</li>

<li><strong>LowPriority (Baixa)</strong> — Pré-carregamento, análises, logs</li>

<li><strong>IdlePriority (Ociosa)</strong> — Tarefas que podem esperar indefinidamente</li>

</ol>

<h3>Implementação Prática com Prioridades</h3>

<p>Para trabalhar com prioridades no React moderno, você utiliza a API <code>useTransition</code> ou <code>useDeferredValue</code>, que permitem marcar atualizações como de menor prioridade. Vamos ver um exemplo real:</p>

<pre><code class="language-javascript">import { useState, useTransition } from &#039;react&#039;;

export default function SearchUsers() {

const [input, setInput] = useState(&#039;&#039;);

const [results, setResults] = useState([]);

// isPending indica se há uma atualização pendente em background

const [isPending, startTransition] = useTransition();

const handleSearch = (value) =&gt; {

// Atualizar o input é imediato e responsivo

setInput(value);

// A filtragem dos resultados é uma tarefa de menor prioridade

startTransition(() =&gt; {

// Simulando uma busca pesada em um grande array

const filtered = generateLargeDataset().filter(user =&gt;

user.name.toLowerCase().includes(value.toLowerCase())

);

setResults(filtered);

});

};

return (

&lt;div&gt;

&lt;input

type=&quot;text&quot;

value={input}

onChange={(e) =&gt; handleSearch(e.target.value)}

placeholder=&quot;Buscar usuários...&quot;

/&gt;

{isPending &amp;&amp; &lt;p&gt;Buscando...&lt;/p&gt;}

&lt;ul&gt;

{results.map((user) =&gt; (

&lt;li key={user.id}&gt;{user.name}&lt;/li&gt;

))}

&lt;/ul&gt;

&lt;/div&gt;

);

}

function generateLargeDataset() {

// Simula um grande conjunto de dados

return Array.from({ length: 100000 }, (_, i) =&gt; ({

id: i,

name: User ${i},

}));

}</code></pre>

<p>Neste exemplo, quando o usuário digita, <code>setInput</code> é chamado imediatamente (alta prioridade), mantendo o campo responsivo. A filtragem dos resultados, dentro do <code>startTransition</code>, é uma tarefa de menor prioridade que pode ser pausada se o usuário digitar novamente.</p>

<h2>Time Slicing: Dividindo Trabalho em Fatias</h2>

<h3>O Problema da Renderização Bloqueante</h3>

<p>Historicamente, quando o React precisava renderizar componentes, ele fazia tudo de uma vez. Se você tinha uma lista de 1000 itens para renderizar, o React processaria todos eles antes de devolver o controle ao navegador. Durante esse tempo, qualquer clique do usuário, digitação ou animação ficaria congelado, esperando. Essa era a essência do problema de <strong>bloqueio da thread principal</strong> (main thread).</p>

<p>O <strong>Time Slicing</strong> resolve isso dividindo o trabalho em pequenas &quot;fatias&quot; de tempo. O React renderiza um pouco, interrompe, verifica se há interações do usuário de alta prioridade, processa isso, e retorna ao trabalho de renderização. Tudo isso acontece em milissegundos, criando a ilusão de que o React está executando múltiplas tarefas simultaneamente.</p>

<h3>Como o Time Slicing Funciona Internamente</h3>

<p>Quando você usa <code>useTransition</code> ou marca algo como <code>startTransition</code>, o React não apenas reduz a prioridade — também fragmenta o trabalho. O Scheduler usa <code>requestIdleCallback</code> ou <code>MessageChannel</code> (dependendo do navegador) para criar essas fatias. Vamos ver um exemplo mais complexo que demonstra o efeito:</p>

<pre><code class="language-javascript">import { useState, useTransition, useCallback } from &#039;react&#039;;

export default function HeavyListRenderer() {

const [count, setCount] = useState(0);

const [items, setItems] = useState([]);

const [isPending, startTransition] = useTransition();

const generateItems = useCallback(() =&gt; {

startTransition(() =&gt; {

// Esta é uma operação pesada que será fatiadaem tempo

const newItems = Array.from({ length: 10000 }, (_, i) =&gt; ({

id: i,

value: Math.random() * 100,

timestamp: Date.now() + i,

}));

setItems(newItems);

});

}, []);

return (

&lt;div style={{ padding: &#039;20px&#039; }}&gt;

&lt;h1&gt;Contador: {count}&lt;/h1&gt;

{/ Este botão permanece responsivo mesmo durante renderização pesada /}

&lt;button onClick={() =&gt; setCount(c =&gt; c + 1)}&gt;

Incrementar (sempre rápido)

&lt;/button&gt;

&lt;button onClick={generateItems}&gt;

Gerar 10.000 itens

&lt;/button&gt;

{isPending &amp;&amp; &lt;p&gt;⏳ Renderizando itens...&lt;/p&gt;}

&lt;div style={{ marginTop: &#039;20px&#039; }}&gt;

{items.map((item) =&gt; (

&lt;div key={item.id} style={{ padding: &#039;4px&#039; }}&gt;

Item {item.id}: {item.value.toFixed(2)}

&lt;/div&gt;

))}

&lt;/div&gt;

&lt;/div&gt;

);

}</code></pre>

<p>Sem <code>startTransition</code>, clicar em &quot;Incrementar&quot; durante a renderização dos 10.000 itens causaria um atraso visível. Com <code>startTransition</code>, o click é processado quase instantaneamente, pois o React pausa a renderização dos itens quando detecta uma interação de alta prioridade.</p>

<h3>Diferença entre useDeferredValue e useTransition</h3>

<p>Existem duas maneiras de integrar time slicing em seus componentes, e é importante conhecer a diferença:</p>

<pre><code class="language-javascript">import { useState, useTransition, useDeferredValue } from &#039;react&#039;;

// Exemplo 1: useTransition (para ações)

function SearchWithTransition() {

const [input, setInput] = useState(&#039;&#039;);

const [isPending, startTransition] = useTransition();

const handleChange = (e) =&gt; {

// input muda imediatamente

setInput(e.target.value);

// atualizar resultados é adiado

startTransition(() =&gt; {

// simulação de operação pesada

console.log(&#039;Processando busca para:&#039;, e.target.value);

});

};

return (

&lt;&gt;

&lt;input value={input} onChange={handleChange} /&gt;

{isPending &amp;&amp; &lt;span&gt;Atualizando...&lt;/span&gt;}

&lt;/&gt;

);

}

// Exemplo 2: useDeferredValue (para valores)

function FilterListWithDeferred() {

const [input, setInput] = useState(&#039;&#039;);

const deferredInput = useDeferredValue(input);

// O deferredInput será atualizado com atraso

// permitindo que o input em si permaneça responsivo

return (

&lt;&gt;

&lt;input

value={input}

onChange={(e) =&gt; setInput(e.target.value)}

/&gt;

&lt;FilteredList searchTerm={deferredInput} /&gt;

&lt;/&gt;

);

}

function FilteredList({ searchTerm }) {

// Este componente será renderizado com deferredInput

// podendo estar um passo atrás do input real

return &lt;div&gt;Resultados para: {searchTerm}&lt;/div&gt;;

}</code></pre>

<p><strong>Quando usar cada um?</strong> Use <code>useTransition</code> quando você quer controlar explicitamente uma ação que dispara atualizações. Use <code>useDeferredValue</code> quando você quer que um valor seja atualizado com atraso automaticamente, sem precisar encapsular a lógica em <code>startTransition</code>.</p>

<h2>Monitoramento e Medição de Performance</h2>

<h3>Identificando Gargalos</h3>

<p>Entender as prioridades e o time slicing é uma coisa. Saber identificar quando sua aplicação <strong>não está</strong> usando esses conceitos corretamente é outra. As DevTools do React mostram quais componentes estão sendo renderizados e quanto tempo leva:</p>

<pre><code class="language-javascript">import { Profiler } from &#039;react&#039;;

function onRenderCallback(

id, // &quot;SearchUsers&quot; ou outro nome do componente

phase, // &quot;mount&quot; ou &quot;update&quot;

actualDuration, // tempo gasto renderizando este componente

baseDuration, // tempo para renderizar sem memoização

startTime, // quando React começou a renderizar

commitTime, // quando a renderização foi concluída

interactions // que transições causaram esta renderização

) {

console.log(${id} (${phase}) levou ${actualDuration}ms);

}

export default function App() {

return (

&lt;Profiler id=&quot;SearchApp&quot; onRender={onRenderCallback}&gt;

&lt;SearchUsers /&gt;

&lt;/Profiler&gt;

);

}</code></pre>

<p>Quando você vir que um componente está levando mais de 16ms para renderizar (o que causa perda de frames em 60fps), é um sinal de que você deve aplicar time slicing ou otimizações.</p>

<h3>Exemplo Completo: Integração de Tudo</h3>

<p>Vamos criar um exemplo que demonstra prioridades, time slicing e monitoramento juntos:</p>

<pre><code class="language-javascript">import {

useState,

useTransition,

useCallback,

Profiler,

} from &#039;react&#039;;

function CompleteExample() {

const [query, setQuery] = useState(&#039;&#039;);

const [results, setResults] = useState([]);

const [isPending, startTransition] = useTransition();

// Simula uma busca pesada em um grande dataset

const performSearch = useCallback((searchQuery) =&gt; {

startTransition(() =&gt; {

const allData = Array.from({ length: 100000 }, (_, i) =&gt; Item ${i});

const filtered = allData.filter((item) =&gt;

item.toLowerCase().includes(searchQuery.toLowerCase())

);

setResults(filtered.slice(0, 100)); // Mostra apenas os primeiros 100

});

}, []);

const handleInputChange = (e) =&gt; {

const newQuery = e.target.value;

setQuery(newQuery); // Atualiza imediatamente

performSearch(newQuery); // Busca em background

};

return (

&lt;Profiler id=&quot;SearchApp&quot; onRender={(id, phase, duration) =&gt; {

if (duration &gt; 16) console.warn(${id} levou ${duration}ms);

}}&gt;

&lt;div style={{ padding: &#039;20px&#039;, fontFamily: &#039;Arial&#039; }}&gt;

&lt;h1&gt;Busca com Time Slicing&lt;/h1&gt;

&lt;input

type=&quot;text&quot;

value={query}

onChange={handleInputChange}

placeholder=&quot;Digite para buscar...&quot;

style={{ padding: &#039;8px&#039;, width: &#039;300px&#039; }}

/&gt;

{isPending &amp;&amp; &lt;p style={{ color: &#039;orange&#039; }}&gt;🔄 Buscando...&lt;/p&gt;}

&lt;p&gt;Encontrados: {results.length} resultados&lt;/p&gt;

&lt;ul style={{ maxHeight: &#039;400px&#039;, overflow: &#039;auto&#039; }}&gt;

{results.map((result, idx) =&gt; (

&lt;li key={idx}&gt;{result}&lt;/li&gt;

))}

&lt;/ul&gt;

&lt;/div&gt;

&lt;/Profiler&gt;

);

}

export default CompleteExample;</code></pre>

<h2>Conclusão</h2>

<p>Durante este artigo, você aprendeu que o React Scheduler é muito mais que um gerenciador de fila — é uma camada inteligente que equilibra a responsividade com a completude das atualizações através de <strong>prioridades diferenciadas</strong>. As APIs como <code>useTransition</code> e <code>useDeferredValue</code> permitem que você comunique ao React qual trabalho pode ser adiado, resultando em interfaces que permanecem responsivas mesmo durante operações pesadas.</p>

<p>O <strong>Time Slicing</strong> transforma renderizações bloqueantes em fatias pequenas e não-bloqueantes, permitindo que interações do usuário sejam processadas rapidamente sem perder a capacidade de atualizar grandes quantidades de dados. Isso é crítico para aplicações modernas que lidam com grandes listas, gráficos complexos ou dados em tempo real. A chave é reconhecer quando aplicar essas técnicas e usar as ferramentas de profiling para validar que suas otimizações estão realmente melhorando a experiência do usuário.</p>

<h2>Referências</h2>

<ol>

<li><a href="https://github.com/facebook/react/tree/main/packages/scheduler" target="_blank" rel="noopener noreferrer">React Documentation: Scheduler</a> — Documentação oficial do Scheduler do React no repositório de código-fonte</li>

</ol>

<ol>

<li><a href="https://react.dev/reference/react/useTransition" target="_blank" rel="noopener noreferrer">React Docs: useTransition</a> — Documentação oficial da API useTransition</li>

</ol>

<ol>

<li><a href="https://react.dev/reference/react/useDeferredValue" target="_blank" rel="noopener noreferrer">React Docs: useDeferredValue</a> — Documentação oficial da API useDeferredValue</li>

</ol>

<ol>

<li><a href="https://react.dev/reference/react/startTransition" target="_blank" rel="noopener noreferrer">Concurrent Features in React</a> — Guia completo sobre renderização concorrente</li>

</ol>

<ol>

<li><a href="https://web.dev/vitals/" target="_blank" rel="noopener noreferrer">Web Vitals and React Performance</a> — Métricas de performance em navegadores Web</li>

</ol>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

O que Todo Dev Deve Saber sobre Hooks para Formulários: Abstraindo Validação e Estado de Campos
O que Todo Dev Deve Saber sobre Hooks para Formulários: Abstraindo Validação e Estado de Campos

Entendendo o Problema: Estado e Validação em Formulários Quando começamos a t...

Como Usar Virtualização de Listas em React: react-window e react-virtual em Produção
Como Usar Virtualização de Listas em React: react-window e react-virtual em Produção

O Problema da Renderização em Listas Grandes Quando trabalha com listas conte...

Como Usar Bundle Analysis em React: Webpack Bundle Analyzer e Tree Shaking em Produção
Como Usar Bundle Analysis em React: Webpack Bundle Analyzer e Tree Shaking em Produção

Entendendo Bundle Analysis e sua Importância Bundle analysis é o processo de...