避免React中Firebase认证保护路由的无限重定向

36次阅读

避免React中Firebase认证保护路由的无限重定向

本文旨在解决React应用中结合Firebase认证实现受保护路由时常见的无限重定向问题。核心在于理解onAuthStateChanged的异步特性及组件初始渲染时的状态。我们将通过引入useEffect钩子和管理加载状态,确保在认证状态明确前避免不必要的重定向,从而构建稳定可靠的私有路由。

引言:理解React中的受保护路由

在现代web应用中,用户认证和授权是不可或缺的功能。react应用常常需要根据用户的登录状态来限制对某些页面的访问,这便是“受保护路由”(protected routes)的概念。当用户尝试访问一个受保护的页面时,如果他们未登录,则会被重定向到登录页面;如果已登录,则允许访问。结合firebase authentication服务,我们可以轻松实现这一功能。然而,如果不正确处理异步的认证状态,很容易陷入无限重定向的困境。

问题剖析:为什么会出现无限重定向?

原始的PrivateRoute组件设计存在一个常见的逻辑缺陷,导致即使在用户已登录的情况下,也会不断被重定向到/sign-in页面。其根本原因在于以下两点:

  1. 初始状态与异步认证: 在组件首次渲染时,authorised状态被初始化为false。这意味着在Firebase的onAuthStateChanged回调函数有机会执行并更新authorised状态之前,组件就已经渲染了一次。此时,由于authorised为false,PrivateRoute会立即触发<Navigate to=”/sign-in”/>,将用户重定向到登录页。
  2. onAuthStateChanged的执行时机:onAuthStateChanged是一个异步操作,它会在用户认证状态发生变化时触发。将其直接放置在组件的函数体中,每次组件重新渲染时都会重新注册监听器(尽管Firebase内部会处理重复注册,但这并非最佳实践),更重要的是,它的回调执行是异步的。这意味着在回调执行并更新authorised状态之前,重定向已经发生。一旦重定向到/sign-in,PrivateRoute组件就不再挂载,因此即使onAuthStateChanged最终识别到用户已登录,也无法阻止已经发生的重定向。

简而言之,组件在不知道用户真实认证状态的情况下,根据其初始的false状态做出了重定向决策,从而引发了循环。

解决方案:引入useEffect和加载状态

要解决这个问题,我们需要确保在onAuthStateChanged完成其异步检查并确定用户的认证状态之前,不进行任何重定向。这可以通过引入一个“加载状态”(loading state)并结合useEffect钩子来实现。

核心思想:

  1. 加载状态: 在组件首次渲染时,设置一个loading状态为true,表示我们正在等待Firebase确认认证状态。
  2. useEffect封装监听: 使用useEffect钩子来订阅onAuthStateChanged事件。这确保了监听器只在组件挂载时注册一次,并在组件卸载时进行清理。
  3. 状态更新: 在onAuthStateChanged的回调中,根据user对象的存在与否更新authorised状态,并在确定认证状态后,将loading状态设置为false。
  4. 条件渲染:
    • 如果loading为true,则显示一个加载指示器(或者不渲染任何内容,等待状态确定)。
    • 如果loading为false:
      • 如果authorised为true,则渲染受保护的内容(<Outlet />)。
      • 如果authorised为false,则重定向到登录页面。

代码示例:重构后的PrivateRoute组件

以下是修正后的PrivateRoute组件代码,它遵循了上述最佳实践:

避免React中Firebase认证保护路由的无限重定向

Melodrive

Melodrive -一个AI音乐引擎,根据用户的情绪状态和喜好生成个性化的音乐。

避免React中Firebase认证保护路由的无限重定向59

查看详情 避免React中Firebase认证保护路由的无限重定向

import { getAuth, onAuthStateChanged } from "firebase/auth"; import { Navigate, Outlet } from "react-router-dom"; import { useState, useEffect } from "react";  const PrivateRoute = () => {     const auth = getAuth();     const [authorised, setAuthorised] = useState(false);     const [loading, setLoading] = useState(true); // 引入加载状态      useEffect(() => {         // 订阅认证状态变化         const unsubscribe = onAuthStateChanged(auth, (user) => {             if (user) {                 setAuthorised(true);             } else {                 setAuthorised(false);             }             setLoading(false); // 认证状态确定后,设置加载为false         });          // 清理函数:在组件卸载时取消订阅         return () => unsubscribe();     }, [auth]); // 依赖项数组,确保auth对象变化时重新订阅      if (loading) {         // 在等待认证状态时,可以显示加载指示器或返回null         return <div>Loading authentication...</div>;     }      // 根据最终的认证状态进行渲染     return authorised ? <Outlet /> : <Navigate to="/sign-in" replace />; };  export default PrivateRoute;

关键改进点:

  • useState(true) for loading: 确保在Firebase认证状态未确定前,组件处于加载状态。
  • useEffect: 将onAuthStateChanged放入useEffect中,确保它只在组件挂载时执行一次,并在组件卸载时正确清理订阅,避免内存泄漏。
  • setLoading(false): 在onAuthStateChanged的回调中,无论用户是否登录,一旦状态被确认,就将loading设置为false。
  • if (loading) 检查: 在认证状态未确定时,显示一个加载提示,避免立即重定向。
  • replace prop for Navigate: 在重定向时使用replace属性,可以防止用户在未登录状态下通过浏览器后退按钮返回受保护页面。

集成到React应用

App.js中的路由配置保持不变,因为它已经正确地使用了嵌套路由来保护/profile路径。

import { Route, Routes } from "react-router-dom"; import Profile from "./pages/Profile"; import SignIn from "./pages/SignIn"; // 假设你有一个SignIn组件 import PrivateRoute from "./components/PrivateRoute"; // 导入修正后的PrivateRoute  function App() {   return (     <Routes> {/* 使用<Routes>包裹所有<Route> */}       <Route path="/sign-in" element={<SignIn />} />        <Route element={<PrivateRoute />}>          <Route path="/profile" element={<Profile />} />       </Route>       {/* 其他路由 */}     </Routes>   ); }  export default App;

最佳实践与注意事项

  1. 用户体验:加载指示器 当loading为true时,显示一个友好的加载指示器(如加载动画或骨架屏),可以显著提升用户体验,避免页面突然空白或闪烁。

  2. Auth Context API: 对于更复杂的应用,建议将Firebase认证状态(user对象和loading状态)提升到React Context中。这样,任何组件都可以轻松访问用户的认证信息,而无需在每个PrivateRoute中重复订阅onAuthStateChanged。

    // 示例:AuthContext.js import React, { createContext, useContext, useState, useEffect } from 'react'; import { getAuth, onAuthStateChanged } from 'firebase/auth';  const AuthContext = createContext();  export const AuthProvider = ({ children }) => {   const [currentUser, setCurrentUser] = useState(null);   const [loading, setLoading] = useState(true);   const auth = getAuth();    useEffect(() => {     const unsubscribe = onAuthStateChanged(auth, (user) => {       setCurrentUser(user);       setLoading(false);     });     return unsubscribe;   }, [auth]);    return (     <AuthContext.Provider value={{ currentUser, loading }}>       {!loading && children} {/* 确保在加载完成后才渲染子组件 */}     </AuthContext.Provider>   ); };  export const useAuth = () => useContext(AuthContext);  // 在PrivateRoute中使用 // const { currentUser, loading } = useAuth(); // if (loading) return <div>Loading...</div>; // return currentUser ? <Outlet /> : <Navigate to="/sign-in" replace />;
  3. 错误处理: 在实际应用中,你可能还需要考虑Firebase认证过程中可能出现的错误,例如网络问题或无效凭据。在onAuthStateChanged之外,处理登录、注册等操作时,应包含适当的错误捕获和用户反馈机制。

总结

通过将Firebase onAuthStateChanged监听器封装在useEffect钩子中,并引入一个loading状态来管理异步认证流程,我们成功解决了React受保护路由中的无限重定向问题。这种模式确保了在用户认证状态明确之前,组件不会做出错误的重定向决策,从而构建出更健壮、用户体验更好的React应用。遵循这些最佳实践,可以有效避免异步操作在React组件生命周期中引发的常见陷阱。

以上就是避免React中Firebase认证保护react js 浏览器 app 路由 网络问题 为什么 gate if for 封装 回调函数 循环 protected JS 对象 事件 异步 重构

react js 浏览器 app 路由 网络问题 为什么 gate if for 封装 回调函数 循环 protected JS 对象 事件 异步 重构

text=ZqhQzanResources