淺談React-router v6 實現登錄驗證流程

此示例演示瞭一個包含三個頁面的簡單登錄流程:公共頁面、受保護頁面和登錄頁面。 為瞭查看受保護的頁面,你必須先登錄。
首先,訪問公共頁面。 然後,訪問受保護的頁面。 你尚未登錄,因此你將被重定向到登錄頁面。 登錄後,你將被重定向回受保護的頁面。

封裝 Context 包裹容器

首先封裝AuthProvider組件,利用Context特性共享那些對於一個組件樹而言是“全局”的數據。
全局定義usersignInsignOut數據和方法,signInsignOut使用瞭高階函數,也方便後續擴展和修改。

Context主要應用場景在於很多不同層級的組件需要訪問同樣一些的數據。請謹慎使用,因為這會使得組件的復用性變差。
如果你隻是想避免層層傳遞一些屬性,組件組合(component composition)有時候是一個比 Context更好的解決方案。

import { ReactNode, createContext, useState } from "react";

export interface AuthContextType {
  user: any;
  signIn: (user: string, callback: VoidFunction) => void;
  signOut: (callback: VoidFunction) => void;
}

export let AuthContext = createContext<AuthContextType | null>(null);

const fakeAuthProvider = {
  isAuthenticated: false,
  signIn(callback: VoidFunction) {
    this.isAuthenticated = true;
    setTimeout(callback, 100);
  },
  signOut(callback: VoidFunction) {
    this.isAuthenticated = false;
    setTimeout(callback, 100);
  },
};

const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [user, setUser] = useState<any>(null);

  let signIn = (newUser: string, callback: VoidFunction) => {
    return fakeAuthProvider.signIn(() => {
      setUser(newUser);
      callback();
    });
  };

  let signOut = (callback: VoidFunction) => {
    return fakeAuthProvider.signOut(() => {
      setUser(null);
      callback();
    });
  };

  return (
    <AuthContext.Provider value={{ user, signIn, signOut }}>
      {children}
    </AuthContext.Provider>
  );
};

export default AuthProvider;

封裝 Layout 父級容器

Layout組件主要是針對登錄狀態進行校驗,然後做相應處理。利用react-router v6中<Outlet />組件顯示嵌套路由,相比於v5版本v6實現嵌套路由更加方便,省略瞭很多冗餘的判斷代碼。

import { useContext } from "react";
import { useNavigate, Link, Outlet } from "react-router-dom";
import { AuthContext, AuthContextType } from "../AuthProvider";

const useAuth = () => useContext(AuthContext);

const AuthStatus = () => {
  let auth = useAuth();
  let { user, signOut } = auth as AuthContextType;
  let navigate = useNavigate();

  if (!user) return <p>沒有登錄</p>;
  return (
    <>
      <p>你好 {user}! </p>
      <button onClick={() => signOut(() => navigate("/"))}>退出</button>
    </>
  );
};

const Layout = () => {
  return (
    <div>
      <AuthStatus />
      <ul>
        <li>
          <Link to="/">公共頁面</Link>
        </li>
        <li>
          <Link to="/protected">受保護頁面</Link>
        </li>
      </ul>
      <Outlet />
    </div>
  );
};

export default Layout;

開發 Login 模塊

import { useContext, FormEvent } from "react";
import { useNavigate, useLocation, Location } from "react-router-dom";
import { AuthContext, AuthContextType } from "../AuthProvider";

interface State extends Omit<Location, "state"> {
  state: {
    from: {
      pathname: string;
    };
  };
}

const useAuth = () => useContext(AuthContext);

const Login = () => {
  let auth = useAuth();
  let { signIn } = auth as AuthContextType;
  const { state } = useLocation() as State;
  let from = state.from.pathname || "/";
  let navigate = useNavigate();

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    let formData = new FormData(event.currentTarget);
    let username = formData.get("username") as string;

    signIn(username, () => navigate(from, { replace: true }));
  };

  return (
    <div>
      <p>您必須登錄才能查看該頁面 {from}</p>

      <form onSubmit={handleSubmit}>
        <label>
          用戶名: <input name="username" type="text" />
        </label>
        <button type="submit">登錄</button>
      </form>
    </div>
  );
};

export default Login;

開發 Protected 包裹容器

主要就是對登錄狀態進行校驗,成功則渲染子組件,否則跳轉回登錄頁面

import { useContext } from "react";
import { useLocation, Navigate } from "react-router-dom";
import { AuthContext, AuthContextType } from "../AuthProvider";

const useAuth = () => useContext(AuthContext);

const RequireAuth = ({ children }: { children: JSX.Element }) => {
  let auth = useAuth();
  let { user } = auth as AuthContextType;
  let location = useLocation();

  if (!user) return <Navigate to="/login" state={{ from: location }} replace />;

  return children;
};

export default RequireAuth;

App 入口文件

入口文件沒有對路由進行懶加載優化,因為是小應用,所以實際開發還是要考慮性能優化的。

import { Routes, Route } from "react-router-dom";

import AuthProvider from "src/views/AuthProvider";
import Layout from "src/views/auth/layout";
import LoginPage from "src/views/auth/login";
import PublicPage from "src/views/auth/publicPage";
import RequireAuth from "src/views/auth/requireAuth";
import ProtectedPage from "src/views/auth/protectedPage";

const App = () => {
  return (
    <AuthProvider>
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<PublicPage />} />
          <Route path="/login" element={<LoginPage />} />
          <Route
            path="/protected"
            element={
              <RequireAuth>
                <ProtectedPage />
              </RequireAuth>
            }
          />
        </Route>
      </Routes>
    </AuthProvider>
  );
};

export default App;

到此這篇關於淺談React-router v6 實現登錄驗證流程的文章就介紹到這瞭,更多相關React-router登錄驗證內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: