簡體   English   中英

在我加載 state 時設置超時以防止登錄頁面在用戶完成身份驗證之前閃爍是不好的做法嗎?

[英]Is it bad practice to have a setTimeout on my loading state to prevent the log in page from flashing before the user is finished being authenticated?

我正在開發一個 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>
  );
}

如果有人對如何重構它有任何建議,非常感謝。 謝謝你。

更新:此答案已更新為使用較新的模塊 Firebase SDK (v9+)。 請參閱舊版命名空間代碼的舊版本Firebase SDK(v8 或更早版本)。

如果您不確定使用哪個,請使用模塊化 Firebase SDK。舊版本不會看到任何更新。


您遇到的閃爍是由您的Routes組件引起的。

這是因為當用戶首次加載您的站點時,他們的身份驗證 state 處於待處理狀態。 在 SDK 表示用戶已正確登錄之前,它必須首先致電 Firebase 身份驗證服務器以檢查該用戶 session 是否有效。 如果您在這仍在發生時調用firebase.auth().currentUser ,它將返回null

因此,因為您的Routes組件包含用戶來自firebase.auth().currentUser的這些行:

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

您的頁面在usernull時短暫呈現AuthStack ,然后在確認用戶的TabStack后重新呈現 TabStack。

無論您在App中為setTimeout使用什么值,這都會始終發生,因為它與此無關,但在您的Providers組件下。

要更正此問題,您必須在確認身份驗證 session 時從Routes組件返回null

由於我不熟悉您的AuthUserContext實現,因此我的回答將基於這個(它處理用戶的 state 及其主要用戶數據)。

// ./FirebaseAuthUserContext.jsx
import { createContext, useContext } from "react";
import { app } from './firebase.js' // should contain `export const app = initializeApp(...)`
import { getAuth, onAuthStateChanged } from 'firebase/auth';
import { getFirestore, doc, onSnapshot } from 'firebase/firestore';

export const FirebaseAuthUserContext = 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 const useAuth = () => useContext(FirebaseAuthUserContext);

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(() => getAuth(app).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" });

  ​useEffect(() => onAuthStateChanged(getAuth(app), (user) => {
    ​setUser(user); // user is User | null
    ​if (initializing) setInitializing(false);
  ​}), []);

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

    ​if (user === null) {
      ​setUserDataInfo({ status: 'signed-out', data: null });
      ​return;
    ​}
    ​
    ​const userDataDocRef = doc(getFirestore(app), "users", user.uid);
 
    ​return onSnapshot( // <- returns an unsubscribe callback
      userDataDocRef,
      {
        ​next: (snapshot) => {
          if (snapshot.exists) {
            setUserDataInfo({
              status: "loaded",
              get data() { return snapshot.data() },
              ref: userDataDocRef
            });
          } else {
            setUserDataInfo({
          ​    status: "not-found",
              data: null,
              ref: userDataDocRef
            ​})
          }
        },
        ​error: (error) => setUserDataInfo({
          ​status: 'error',
          ​data: null,
          ​error
        ​})
      ​}
    );
  ​}, [user]);

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

注意:您可以將此FirebaseAuthUserContext中的用戶數據拆分到另一個上下文中,例如FirebaseAuthUserDataContext ,但通常情況下,您無論如何都需要兩者,因此您可以將它們放在一起。

使用上面的Context object,您可以將Routes組件更新為:

import useAuth from './FirebaseAuthUserContext.jsx';

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

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

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

下面是另一個使用這個Context object 來處理需要使用用戶數據的例子:

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,您可以通過定義上下文可能處於的各種不同狀態來幫助自己:

// at top of ./FirebaseAuthUserContext.jsx
import type { User } from "firebase/auth";
import type { DocumentReference, FirestoreError } from "firebase/firestore";

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

interface UserDataInfo$Error {
 ​ status: "error";
 ​ data: null;
  ​error: FirestoreError;
}
interface UserDataInfo$Loaded {
  ​status: "loaded";
  ​data: UserData;
  ​ref: DocumentReference<UserData>;
}
interface UserDataInfo$Loading {
 ​ status: "loading";
  ​data?: undefined;
}
interface UserDataInfo$NotFound {
 ​ status: "not-found";
  ​data: null;
  ​ref: 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: User;
 ​ initializing: false;
}

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

export 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 = createContext<FirebaseAuthUserContextType>({
  data: undefined,
  dataInfo: { status: "loading" },
  dataStatus: "loading",
  initializing: true,
  user: undefined
} as FirebaseAuthUserContextType$Initializing);

最好的方法是使用啟動畫面,然后導航到其他堆棧,但是您仍然可以像下面那樣在沒有啟動畫面的情況下進行操作

{user === 'loading' ? <LoadingPage /> : user ? <TabStack /> : <AuthStack />}

我假設初始用戶值為“正在加載”

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM