DEV Community

Cover image for Memory Leak em React Native
Emerson Vieira
Emerson Vieira

Posted on

3

Memory Leak em React Native

O que é Memory Leak?

Memory leak (vazamento de memória) ocorre quando uma aplicação não libera a memória que não está mais em uso, fazendo com que o consumo de memória aumente continuamente. No React Native, isso pode causar problemas de desempenho, como travamentos, queda de FPS e, em casos graves, a aplicação pode ser fechada pelo sistema operacional.

Causas Comuns de Memory Leak em React Native

  1. Componentes não desmontados corretamente: Quando um componente continua a "viver" mesmo após ser removido da árvore de componentes.
  2. Event Listeners não removidos: Listeners continuam ativos mesmo após o componente ser desmontado.
  3. Timers (setTimeout ou setInterval): Se não forem limpos corretamente, esses timers continuarão ativos, mesmo quando não forem mais necessários.
  4. Async Operations (Promises, Fetch, Axios): Promessas ou requisições assíncronas que continuam sendo resolvidas ou rejeitadas mesmo depois de o componente ser desmontado.
  5. Referências circulares: Isso acontece quando dois objetos fazem referência um ao outro, impedindo o coletor de lixo de liberar a memória corretamente.

Como Identificar Memory Leaks?

Algumas técnicas para detectar vazamentos de memória em aplicações React Native incluem:

  • Uso do DevTools do React Native: Monitore o uso de memória no profiler.
  • Ferramentas de análise de desempenho: No Android, o Android Studio Profiler ajuda a rastrear o uso de memória.
  • Alertas de uso excessivo de memória: O monitoramento de erros em produção com ferramentas como Sentry pode ajudar a identificar padrões de aumento de memória.

Como Prevenir Memory Leaks?

  1. Desmonte corretamente os componentes: Certifique-se de limpar timers, listeners e operações assíncronas no componentWillUnmount (ou no retorno do useEffect).
  2. Uso do AbortController com Axios ou Fetch: Para garantir que uma requisição seja cancelada quando o componente desmonta.
  3. Gerencie corretamente dependências no useEffect: Assegure-se de que a lista de dependências do useEffect está correta para evitar chamadas desnecessárias e possíveis vazamentos.
  4. Limpe os estados: Ao desmontar o componente, garanta que as operações que dependem de um estado sejam adequadamente tratadas, evitando a atualização de estados em componentes desmontados.

Exemplo

// HomeScreen.tsx
import React, { useEffect, useState } from "react";
import { View, Button } from "react-native";
import checkLocationPermission from "./location";

const HomeScreen = ({ navigation }) => {
  const [hasPermission, setHasPermission] = useState(false);

  useEffect(() => {
    const checkPermission = async () => {
      const permission = await checkLocationPermission();
      setHasPermission(permission);
    };

    checkPermission();
  }, []);

  if (!hasPermission) {
    return null;
  }

  return (
    <View style={{ padding: 20 }}>
      <Button
        title="Ir para Mapa"
        onPress={() => navigation.navigate("Map")}
        disabled={!hasPermission}
      />
    </View>
  );
};

export default HomeScreen;
Enter fullscreen mode Exit fullscreen mode

O código de exemplo da HomeScreen realiza a verificação das permissões de acesso à localização do dispositivo e, em seguida, redireciona o usuário para a tela de Mapa (MapScreen).

// MapScreen.tsx
import React, { useEffect, useState, useCallback, useRef } from "react";
import { View, Button, Text } from "react-native";
import MapView, { Marker } from "react-native-maps";
import Geolocation from "@react-native-community/geolocation";

const MapScreen = ({ navigation }) => {
  const [location, setLocation] = useState({
    latitude: 37.78825,
    longitude: -122.4324,
    latitudeDelta: 0.005,
    longitudeDelta: 0.005,
  });
  const [errorMsg, setErrorMsg] = useState(null);
  const [loading, setLoading] = useState(true);
  const [isLocationValid, setIsLocationValid] = useState(false);
  const watchId = useRef(null);

  const getGpsLocation = useCallback(() => {
    console.log("Iniciando escuta de localização...");
    watchId.current = Geolocation.watchPosition(
      (position) => {
        if (position && position.coords) {
          const { latitude, longitude } = position.coords;
          setLocation({
            latitude,
            longitude,
            latitudeDelta: 0.005,
            longitudeDelta: 0.005,
          });
          setLoading(false);
          setIsLocationValid(true);
          console.log("Localização atualizada:", position);
        } else {
          console.error("Posição ou coordenadas não definidas");
        }
      },
      (error) => {
        console.error("Error getting GPS location:", error);
        setErrorMsg(error.message);
        setLoading(false);
      },
      {
        enableHighAccuracy: true,
        timeout: 30000,
        maximumAge: 10000,
        distanceFilter: 0,
        interval: 5000,
        fastestInterval: 5000,
      },
    );
  }, []);

  useEffect(() => {
    getGpsLocation();

    /* return () => {
      if (watchId.current !== null) {
        Geolocation.clearWatch(watchId.current);
        console.log('Escuta de localização removida');
        watchId.current = null; // Resetar para evitar limpeza dupla
      }
    }; */
    return () => {
      // A escuta de localização não será removida, causando um memory leak
      console.log(
        "Componente desmontado, mas a escuta de localização continua",
      );
    };
  }, [getGpsLocation]);

  return (
    <View style={{ flex: 1 }}>
      {isLocationValid ? (
        <MapView
          style={{ flex: 1 }}
          initialRegion={{
            latitude: location.latitude,
            longitude: location.longitude,
            latitudeDelta: location.latitudeDelta,
            longitudeDelta: location.longitudeDelta,
          }}
          loadingEnabled={loading}
          region={{
            latitude: location.latitude,
            longitude: location.longitude,
            latitudeDelta: location.latitudeDelta,
            longitudeDelta: location.longitudeDelta,
          }}
          showsUserLocation={true}
        >
          <Marker
            coordinate={{
              latitude: location.latitude,
              longitude: location.longitude,
            }}
            title="Sua localização"
          />
        </MapView>
      ) : (
        <View
          style={{ flex: 1, justifyContent: "center", alignItems: "center" }}
        >
          <Text>Obtendo localização...</Text>
        </View>
      )}
      <View style={{ padding: 20 }}>
        {errorMsg && <Text>{errorMsg}</Text>}
        <Button title="Voltar para Home" onPress={() => navigation.pop()} />
      </View>
    </View>
  );
};

export default MapScreen;
Enter fullscreen mode Exit fullscreen mode

O código exemplo de MapScreen, um memory leak ocorre porque a escuta de localização (Geolocation.watchPosition) não é limpa corretamente quando o componente MapScreen é desmontado. Isso faz com que a aplicação continue a escutar a localização mesmo quando o componente já não está em uso, resultando em um consumo desnecessário de memória.

Ao abrir a tela de Mapa e obter a localização, ao voltar para a tela Home, a coleta da posição continua sendo realizada. Isso pode ser observado no console através do seguinte log da tela de Mapa: console.log('Localização atualizada:', position);.

A correção é simples: basta limpar a escuta de localização no retorno da função useEffect:

useEffect(() => {
  getGpsLocation();
  return () => {
    if (watchId.current !== null) {
      Geolocation.clearWatch(watchId.current);
      console.log("Escuta de localização removida");
      watchId.current = null; // Resetar para evitar limpeza dupla
    }
  };
}, [getGpsLocation]);
Enter fullscreen mode Exit fullscreen mode

Projeto de Exemplo

ReactNativeMemoryLeak

Conclusão

Memory leaks podem impactar severamente a experiência do usuário em uma aplicação React Native. Identificar e mitigar esses vazamentos é essencial para manter uma performance estável e evitar problemas maiores, como travamentos ou encerramento inesperado da aplicação. Ao seguir boas práticas, como desmontar corretamente os componentes e cancelar operações assíncronas, você pode evitar a maioria dos problemas de vazamento de memória.

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more