首頁 > 軟體

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

2022-05-29 14:00:12

此範例演示了一個包含三個頁面的簡單登入流程:公共頁面、受保護頁面和登入頁面。 為了檢視受保護的頁面,你必須先登入。
首先,存取公共頁面。 然後,存取受保護的頁面。 你尚未登入,因此你將被重定向到登入頁面。 登入後,你將被重定向回受保護的頁面。

封裝 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登入驗證內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com