由于卸载的组件,React 状态更新内存泄漏

时间:2021-03-29 17:36:07

标签: reactjs react-native async-await

我的应用程序使用 firebase 进行身份验证。在登录过程中,我收到“无法在卸载的组件上执行 React 状态更新”的消息,它建议在 useEffect 中使用清理函数。我以为我正在使用

清理异步函数中的函数
finally {
      setLoading(false);
    }

任何帮助将不胜感激。代码如下:

import React, { useState, useContext } from "react";
import styled from "styled-components/native";
import { Image, Text, StyleSheet } from "react-native";

import { FirebaseContext } from "../context/FirebaseContext";
import { UserContext } from "../context/UserContext";

export default function SignInScreen() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [loading, setLoading] = useState(false);
  const firebase = useContext(FirebaseContext);
  const [_, setUser] = useContext(UserContext);

  const signIn = async () => {
    setLoading(true);
    try {
      await firebase.signIn(email, password);
      const uid = firebase.getCurrentUser().uid;
      const userInfo = await firebase.getUserInfo(uid);
      const emailArr = userInfo.email.split("@");
      setUser({
        username: emailArr[0],
        email: userInfo.email,
        uid,
        isLoggedIn: true,
      });
    } catch (error) {
      alert(error.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <Container>
      <Main>
        <Text style={styles.welcomeText}>Welcome</Text>
      </Main>

      <Auth>
        <AuthContainer>
          <AuthTitle>Email Address</AuthTitle>
          <AuthField
            autoCapitalize="none"
            autoCompleteType="email"
            autoCorrect={false}
            autoFocus={true}
            keyboardType="email-address"
            onChangeText={(email) => setEmail(email.trim())}
            value={email}
          />
        </AuthContainer>

        <AuthContainer>
          <AuthTitle>Password</AuthTitle>
          <AuthField
            autoCapitalize="none"
            autoCompleteType="password"
            autoCorrect={false}
            autoFocus={true}
            secureTextEntry={true}
            onChangeText={(password) => setPassword(password.trim())}
            value={password}
          />
        </AuthContainer>
      </Auth>

      <SignInContainer onPress={signIn} disabled={loading}>
        {loading ? <Loading /> : <Text style={styles.text}>Sign In</Text>}
      </SignInContainer>
      <HeaderGraphic>
        <Image
          source={require("../images/heritage-films-logo.png")}
          style={{ height: 150, width: 300, resizeMode: "contain" }}
        />
      </HeaderGraphic>
    </Container>
  );
}

1 个答案:

答案 0 :(得分:2)

在以某种方式调用 setState 之前,您应该检查组件是否仍处于挂载状态。这是一个典型的 React 泄漏问题。您可以使用 isMounted 钩子实现 useRef 变量,尽管 React 的作者将其称为反模式,因为您应该在组件卸载时取消异步例程。

function Component() {
    const isMounted = React.useRef(true);

    React.useEffect(() => () => (isMounted.current = false), []);

      const signIn = async () => {
        setLoading(true);
        try {
          await firebase.signIn(email, password);
          const uid = firebase.getCurrentUser().uid;
          const userInfo = await firebase.getUserInfo(uid);
          const emailArr = userInfo.email.split("@");
          isMounted.current && setUser({
            username: emailArr[0],
            email: userInfo.email,
            uid,
            isLoggedIn: true,
          });
        } catch (error) {
          alert(error.message);
        } finally {
          isMounted.current && setLoading(false);
        }
   };
}

或者另一种有点神奇的方式:

import { useAsyncCallback, E_REASON_UNMOUNTED } from "use-async-effect2";
import { CanceledError } from "c-promise2";

export default function SignInScreen() {
  //...

  const signIn = useAsyncCallback(function*() {
    setLoading(true);
    try {
      yield firebase.signIn(email, password);
      const uid = firebase.getCurrentUser().uid;
      const userInfo = yield firebase.getUserInfo(uid);
      const emailArr = userInfo.email.split("@");
      setUser({
        username: emailArr[0],
        email: userInfo.email,
        uid,
        isLoggedIn: true,
      });
      setLoading(false);
    } catch (error) {
      CanceledError.rethrow(error, E_REASON_UNMOUNTED);
      setLoading(false);
      alert(error.message);
    }
  }, []);

  return (<YourJSX onPress={signIn}>);
}