首页 > 解决方案 > 在我的加载状态上设置一个 setTimeout 以防止登录页面在用户完成身份验证之前闪烁是不好的做法吗?

问题描述

我正在开发一个 React Native 应用程序,在用户通过身份验证之前,我能够让登录页面停止闪烁的唯一方法是添加一个 setTimeout,如下所示:

export default function App() {
  const [IsReady, setIsReady] = useState(false);

  const LoadFonts = async () => {
    await useFonts();
  };

  if (!IsReady) {
    return (
      <AppLoading
        startAsync={LoadFonts}
        onFinish={() =>
          setTimeout(() => {
            setIsReady(true);
          }, 1000)
        }
        onError={(error) => {
          console.log(error);
        }}
      />
    );
  }
  return <Providers />;

这是不好的做法吗?有什么更好的方法来解决这个问题?我必须这样做的原因是,在我的 Routes.js 文件中,我检查用户是否经过身份验证,如果没有,他们将获得登录堆栈导航。如果是,他们将获得主页。

路由.js

export default function Routes() {
  const { user, setUser, setFirestoreUserData } = useContext(AuthUserContext);

  useEffect(() => {
    const unsubscribeAuth = auth.onAuthStateChanged(async (authUser) => {
      await (authUser ? setUser(authUser) : setUser(null));
    });

    return unsubscribeAuth;
  }, []);

  useEffect(() => {
    const fetchData = async () => {
      const getUserObject = await getUserFromFirestore(user.uid);
      setFirestoreUserData(getUserObject);
    };
    if (user) {
      fetchData();
    }
  }, [user]);

  return (
    <NavigationContainer theme={navigationTheme}>
      {user ? <TabStack /> : <AuthStack />}
    </NavigationContainer>
  );
}

如果有人对如何重构有任何建议,非常感谢!谢谢你。

标签: javascriptreactjsfirebasereact-nativegoogle-cloud-firestore

解决方案


您遇到的闪烁是由您的Routes组件引起的。

这是因为当用户首次加载您的站点时,他们的身份验证状态为pending。在 SDK 表明用户已正确登录之前,它必须首先打电话到 Firebase 身份验证服务器以检查该用户会话是否有效。如果您firebase.auth().currentUser在此过程中调用,它将返回null

因此,因为您的Routes组件包含用户来自的这些行firebase.auth().currentUser

return (
  <NavigationContainer theme={navigationTheme}>
    {user ? <TabStack /> : <AuthStack />}
  </NavigationContainer>
);

您的页面会在is期间呈现AuthStack片刻,然后在确认用户会话后重新呈现。usernullTabStack

无论您在其中使用什么值,这都会始终发生setTimeoutApp因为它与该值无关,而是在您的Providers组件下。

要更正此问题,您必须在确认身份验证会话时null从您的组件返回。Routes

由于我不熟悉您的AuthUserContext实现,因此我将根据这个来回答(它同时处理用户的状态和他们的主要用户数据):

import auth from '...';       // import from appropriate libraries
import firestore from '...';

export const FirebaseAuthUserContext = React.createContext({
  // the current user's data
  data: undefined,
  // more information about the current user's data
  dataInfo: { status: "loading" },
  // the status of fetching the current user's data
  dataStatus: "loading",
  // the status of checking the user's auth session
  initializing: true,
  // the current user object
  user: undefined
});

export function FirebaseAuthUserProvider({children}) {
  ​// If authentication state has been determined already,
 ​ // use the current user object. Otherwise, fall back to `undefined`.
 ​ const [user, setUser] = useState(() => auth().currentUser || undefined);
 ​ // If initial `user` value is `undefined`, we need to initialize.
 ​ const [initializing, setInitializing] = useState(user === undefined);

  ​// Prep a space to hold their user data
  ​//  - data?:   user data object, as applicable
  ​//  - error?:  error related to the user's data, as applicable
  ​//  - ref?:    reference to user data's location, as applicable
  ​//  - status:  status of the user data
  ​const [userDataInfo, setUserDataInfo] = useState({ status: "loading" });

  ​function onAuthStateChanged(user) {
    ​setUser(user); // user is firebase.auth.User | null
    ​if (initializing) setInitializing(false);
  ​}

  ​useEffect(() => auth().onAuthStateChanged(onAuthStateChanged), []);

  ​useEffect(() => {
   ​ if (initializing) return; // do nothing, still loading auth state.

    ​if (user === null) {
      ​setUserDataInfo({ status: 'signed-out', data: null });
      ​return;
    ​}
    ​
    ​const userDataRef = firestore()
      ​.collection("users")
      ​.doc(user.uid);
 
    ​return userDataRef
      ​.onSnapshot({
        ​next: (snapshot) => {
          if (snapshot.exists) {
            setUserDataInfo({
              status: "loaded",
              get data() { return snapshot.data() },
              ref: userDataRef
            });
          } else {
            setUserDataInfo({
          ​    status: "not-found",
              data: null,
              ref: userDataRef
            ​})
          }
        });,
        ​error: (error) => setUserDataInfo({
          ​status: 'error',
          ​data: null,
          ​error
        ​})
      ​});
  ​}, [user]);

  ​// you can rename these as desired:
  ​return (
    ​&lt;FirebaseAuthUserContext.Provider value={{​
      ​data: userDataInfo.data, // for convenience
      ​dataInfo: userDataInfo,
      ​dataStatus: userDataInfo.status, // for convenience
      ​initializing,
      user
    ​}}>
      ​{children}
    ​&lt;/FirebaseAuthUserContext.Provider>
 ​ );
}

注意:您可以将用户数据从这里FirebaseAuthUserContext拆分到另一个上下文中,例如FirebaseAuthUserDataContext,但通常情况下,无论如何您都需要两者,因此您可以将它们放在一起。

使用上面的Context对象,您可以将Routes组件更新为:

import FirebaseAuthUserContext from '...';

export default function Routes() {
  ​const userInfo = useContext(FirebaseAuthUserContext);

  if (​userInfo.initializing) return null; // hide while loading

  ​return (
    ​&lt;NavigationContainer theme={navigationTheme}>
      ​{userInfo.user ? <TabStack /> : <AuthStack />}
    ​&lt;/NavigationContainer>
  ​);
}

这是另一个使用此Context对象的示例,该对象需要使用用户数据:

import FirebaseAuthUserContext from '...';

export default function CurrentUserIcon() {
  ​const userInfo = useContext(FirebaseAuthUserContext);

  switch (userInfo.dataStatus) {
    case "loaded":
      return (
        <div class="user-icon">
          <a href="/profile/settings">
            <img src={userInfo.data.profileImage} />
            <span>@{userInfo.data.username}</span>
          </a>
        </div>
      );
    case "not-found": 
      return (
        <div class="user-icon">
          <a href="/profile/settings">
            <img src={PLACEHOLDER_IMAGE_URL} />
            <span>New user</span>
          </a>
        </div>
      );
    default:
      // unexpected status: loading/error/signed-out
      return null;
  }
}

如果您正在使用 Typescript,您可以通过定义上下文可能处于的各种不同状态来帮助自己:

interface UserData {
  ​// shape of your user's data
}

interface UserDataInfo$Error {
 ​ status: "error";
 ​ data: null;
  ​error: firebase.firestore.FirestoreError;
}
interface UserDataInfo$Loaded {
  ​status: "loaded";
  ​data: UserData;
  ​ref: firebase.firestore.DocumentReference<UserData>;
}
interface UserDataInfo$Loading {
 ​ status: "loading";
  ​data?: undefined;
}
interface UserDataInfo$NotFound {
 ​ status: "not-found";
  ​data: null;
  ​ref: firebase.firestore.DocumentReference<UserData>;
}
interface UserDataInfo$SignedOut {
 ​ status: "signed-out";
 ​ data: null;
}

type UserDataInfo =
 | UserDataInfo$Error
 | UserDataInfo$Loaded
 | UserDataInfo$Loading
 | UserDataInfo$NotFound
 ​| UserDataInfo$SignedOut;

interface LiftedUserDataInfo<T extends UserDataInfo> {
  data: T["data"];
  dataInfo: T;
  ​dataStatus: T["status"];
}

​interface FirebaseAuthUserContextType$Initializing extends LiftedUserDataInfo<UserDataInfo$Loading> {
  ​user: undefined;
  initializing: true;
}

interface FirebaseAuthUserContextType$SignedIn<T extends 
 ​| UserDataInfo$Error
 ​| UserDataInfo$Loaded
 ​| UserDataInfo$Loading
 ​| UserDataInfo$NotFound
> extends LiftedUserDataInfo<T> {
  ​user: firebase.auth.User;
 ​ initializing: false;
}

interface FirebaseAuthUserContextType$SignedOut extends LiftedUserDataInfo<UserDataInfo$SignedOut> {
  user: null;
  initializing: false;
}

type FirebaseAuthUserContextType =
 ​| FirebaseAuthUserContextType$Initializing
 ​| FirebaseAuthUserContextType$SignedIn<UserDataInfo$Error>
 | FirebaseAuthUserContextType$SignedIn<UserDataInfo$Loaded>
 ​| FirebaseAuthUserContextType$SignedIn<UserDataInfo$Loading>
 ​| FirebaseAuthUserContextType$SignedIn<UserDataInfo$NotFound>
 ​| FirebaseAuthUserContextType$SignedOut;

export const FirebaseAuthUserContext = React.createContext<FirebaseAuthUserContextType>({
  data: undefined,
  dataInfo: { status: "loading" },
  dataStatus: "loading",
  initializing: true,
  user: undefined
} as FirebaseAuthUserContextType$Initializing);

推荐阅读