Boas Práticas de Batching Automático em React 18: Atualizações Síncronas e Assíncronas para Times Ágeis Já leu

O que é Batching Automático no React 18? Batching é o processo pelo qual o React agrupa múltiplas atualizações de estado em uma única renderização. Antes do React 18, esse comportamento era inconsistente: funcionava perfeitamente dentro de event handlers, mas falhava em Promises, setTimeout ou callbacks de bibliotecas externas. O React 18 introduziu o Automatic Batching, que aplica essa otimização de forma consistente em praticamente todos os cenários. Essa mudança reduz significativamente o número de renderizações desnecessárias, melhorando a performance de aplicações que lidam com múltiplas atualizações de estado simultâneas. Para entender por que isso importa, imagine um formulário onde você precisa atualizar cinco campos diferentes — sem batching, React renderizaria cinco vezes; com batching, renderiza uma única vez. Entendendo o Batching Automático em Cenários Síncronos Event Handlers e Atualizações Imediatas No React 18, quando você dispara múltiplas atualizações de estado dentro de um event handler (como um clique de botão), elas são automaticamente agrupadas em um único ciclo de

O que é Batching Automático no React 18?

Batching é o processo pelo qual o React agrupa múltiplas atualizações de estado em uma única renderização. Antes do React 18, esse comportamento era inconsistente: funcionava perfeitamente dentro de event handlers, mas falhava em Promises, setTimeout ou callbacks de bibliotecas externas. O React 18 introduziu o Automatic Batching, que aplica essa otimização de forma consistente em praticamente todos os cenários.

Essa mudança reduz significativamente o número de renderizações desnecessárias, melhorando a performance de aplicações que lidam com múltiplas atualizações de estado simultâneas. Para entender por que isso importa, imagine um formulário onde você precisa atualizar cinco campos diferentes — sem batching, React renderizaria cinco vezes; com batching, renderiza uma única vez.

Entendendo o Batching Automático em Cenários Síncronos

Event Handlers e Atualizações Imediatas

No React 18, quando você dispara múltiplas atualizações de estado dentro de um event handler (como um clique de botão), elas são automaticamente agrupadas em um único ciclo de renderização. Isso era verdade também no React 17, mas é importante revisar esse comportamento como base.

import React, { useState } from 'react';

export function SyncBatchingExample() {
  const [count, setCount] = useState(0);
  const [isLoading, setIsLoading] = useState(false);
  const [message, setMessage] = useState('');

  console.log('Renderização');

  const handleClick = () => {
    // Todas essas três atualizações serão agrupadas em uma ÚNICA renderização
    setCount(prev => prev + 1);
    setIsLoading(true);
    setMessage('Atualizando...');
  };

  return (
    <div>
      <p>Count: {count}</p>
      <p>Loading: {isLoading ? 'Sim' : 'Não'}</p>
      <p>Message: {message}</p>
      <button onClick={handleClick}>Atualizar Estado</button>
    </div>
  );
}

Quando você clica no botão acima, verá "Renderização" impressa apenas uma vez no console, não três. As três chamadas setState são agrupadas automaticamente. Esse comportamento também ocorre em outros contextos síncronos como callbacks diretos e manipulações imediatas de estado.

Batching Automático em Cenários Assíncronos

Promises e Callbacks Assíncronos

Essa é a grande inovação do React 18. Anteriormente, atualizações dentro de Promises, setTimeout ou callbacks de bibliotecas externas não eram batched. Agora, elas são automaticamente agrupadas também.

import React, { useState } from 'react';

export function AsyncBatchingExample() {
  const [count, setCount] = useState(0);
  const [isLoading, setIsLoading] = useState(false);
  const [data, setData] = useState(null);

  console.log('Renderização');

  const handleAsyncUpdate = async () => {
    // Iniciar carregamento
    setIsLoading(true);

    try {
      // Simular chamada à API
      const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
      const json = await response.json();

      // No React 18, essas duas atualizações serão agrupadas
      // mesmo dentro de uma Promise
      setIsLoading(false);
      setData(json);
      setCount(prev => prev + 1);
    } catch (error) {
      setIsLoading(false);
      setData(null);
    }
  };

  return (
    <div>
      <p>Count: {count}</p>
      <p>Loading: {isLoading ? 'Carregando...' : 'Concluído'}</p>
      {data && <p>Data Title: {data.title}</p>}
      <button onClick={handleAsyncUpdate}>Carregar Dados</button>
    </div>
  );
}

Quando você clica em "Carregar Dados", a chamada à API é iniciada. Após a resposta, as três atualizações de estado (setIsLoading, setData e setCount) são agrupadas em uma única renderização. No React 17, isso causaria renderizações separadas, reduzindo a performance em aplicações data-heavy.

setTimeout e Callbacks de Bibliotecas Externas

O batching automático também funciona com setTimeout e outros padrões assíncronos que eram problemáticos anteriormente.

import React, { useState } from 'react';

export function TimeoutBatchingExample() {
  const [firstValue, setFirstValue] = useState('');
  const [secondValue, setSecondValue] = useState('');
  const [thirdValue, setThirdValue] = useState('');

  console.log('Renderização');

  const handleComplexUpdate = () => {
    // Simular múltiplas operações assíncronas com setTimeout
    setTimeout(() => {
      // React 18 agrupa essas três atualizações em uma renderização
      setFirstValue('Valor 1');
      setSecondValue('Valor 2');
      setThirdValue('Valor 3');
    }, 1000);
  };

  return (
    <div>
      <p>1: {firstValue}</p>
      <p>2: {secondValue}</p>
      <p>3: {thirdValue}</p>
      <button onClick={handleComplexUpdate}>
        Iniciar Atualização com Delay
      </button>
    </div>
  );
}

Você observará que, quando o setTimeout executa as três atualizações, o console mostra "Renderização" apenas uma vez. No React 17, seriam três renderizações separadas.

Casos Especiais e Controle Manual de Batching

Quando Você Quer Renderizar Imediatamente: flushSync

Ocasionalmente, você pode precisar que uma atualização de estado seja aplicada imediatamente, sem aguardar o batching automático. Para isso, use a função flushSync do React.

import React, { useState } from 'react';
import { flushSync } from 'react-dom';

export function FlushSyncExample() {
  const [count, setCount] = useState(0);
  const [multiplier, setMultiplier] = useState(1);

  console.log('Renderização');

  const handleFlushSync = () => {
    // A primeira atualização será aplicada imediatamente (renderização 1)
    flushSync(() => {
      setCount(prev => prev + 1);
    });
    // Neste ponto, o DOM foi atualizado

    // A segunda atualização será batched normalmente (renderização 2)
    setMultiplier(2);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <p>Multiplier: {multiplier}</p>
      <button onClick={handleFlushSync}>Usar flushSync</button>
    </div>
  );
}

O flushSync força a renderização síncrona da atualização envolvida. Use com moderação, pois quebra o batching automático e pode prejudicar a performance se abusado. Situações legítimas incluem sincronização com bibliotecas do DOM ou quando você precisa acessar valores atualizados imediatamente.

Exemplo Prático: Integração com Biblioteca Externa

import React, { useState, useRef } from 'react';
import { flushSync } from 'react-dom';

export function ExternalLibraryIntegration() {
  const [inputValue, setInputValue] = useState('');
  const inputRef = useRef(null);

  const handleInputChange = (e) => {
    const value = e.target.value;

    // Força a atualização do estado antes de chamar uma função externa
    flushSync(() => {
      setInputValue(value);
    });

    // Agora podemos interagir com APIs externas que dependem do valor atualizado
    if (inputRef.current && value.length > 3) {
      // Exemplo: triggar validação ou autocomplete
      console.log('Validação acionada para:', value);
    }
  };

  return (
    <div>
      <input
        ref={inputRef}
        value={inputValue}
        onChange={handleInputChange}
        placeholder="Digite algo (3+ caracteres)"
      />
      <p>Valor: {inputValue}</p>
    </div>
  );
}

Impacto na Performance e Boas Práticas

O batching automático do React 18 oferece ganhos de performance significativos, especialmente em aplicações que executam múltiplas atualizações de estado em contextos assíncronos. Em um teste simples com 10 atualizações de estado simultâneas, você pode esperar uma redução de até 10 vezes no número de renderizações.

Contudo, existem armadilhas comuns que você deve evitar. A primeira é não medir a performance real da sua aplicação — use React DevTools Profiler para identificar gargalos reais antes de otimizar. A segunda é não entender quando usar flushSync; a maioria das aplicações nunca precisará dessa função. A terceira é acumular lógica complexa dentro de event handlers ou callbacks — separe a lógica em custom hooks e funções puras para manter o código limpo e previsível.

import React, { useState, useCallback } from 'react';

export function BestPracticesExample() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: ''
  });
  const [isSubmitting, setIsSubmitting] = useState(false);

  // Separar lógica em funções puras
  const updateFormField = useCallback((field, value) => {
    setFormData(prev => ({
      ...prev,
      [field]: value
    }));
  }, []);

  // Separar lógica de submissão
  const handleSubmit = useCallback(async () => {
    setIsSubmitting(true);

    try {
      // Simular envio
      await new Promise(resolve => setTimeout(resolve, 1000));
      console.log('Dados enviados:', formData);
      // Reset seria agrupado automaticamente
      setFormData({ name: '', email: '', age: '' });
    } finally {
      // Ambas as atualizações aqui são batched automaticamente
      setIsSubmitting(false);
    }
  }, [formData]);

  return (
    <div>
      <input
        value={formData.name}
        onChange={(e) => updateFormField('name', e.target.value)}
        placeholder="Nome"
      />
      <input
        value={formData.email}
        onChange={(e) => updateFormField('email', e.target.value)}
        placeholder="Email"
      />
      <input
        value={formData.age}
        onChange={(e) => updateFormField('age', e.target.value)}
        placeholder="Idade"
      />
      <button onClick={handleSubmit} disabled={isSubmitting}>
        {isSubmitting ? 'Enviando...' : 'Enviar'}
      </button>
    </div>
  );
}

Conclusão

O Automatic Batching do React 18 resolve um problema histórico: a inconsistência nas atualizações de estado entre contextos síncronos e assíncronos. Agora, múltiplas chamadas setState são agrupadas em uma única renderização automaticamente, independentemente de onde são disparadas — em event handlers, Promises, setTimeout ou callbacks de bibliotecas externas.

O segundo ponto crucial é entender que você geralmente não precisa fazer nada para aproveitar essa otimização. O comportamento automático é o padrão, e apenas em casos raros (integração com código legado ou APIs externas sensíveis ao timing) você usará flushSync. Essa é uma melhoria que funciona nos bastidores.

Por último, lembre-se que batching automático não elimina a necessidade de pensar sobre estrutura de componentes, memoização ou derivação de estado. Use React DevTools Profiler para medir o impacto real em sua aplicação. As otimizações mais importantes continuam sendo aquelas no nível arquitetural: decomposição de componentes, evitar state desnecessário e usar reducers para lógica complexa.

Referências


Artigos relacionados