searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

React-Router V6.x 实现前端路由鉴权

2023-06-30 08:30:15
984
0

React-Router是React生态里面很重要的一环,现在React的单页应用的路由基本都是前端自己管理的,而不像以前是后端路由,React管理路由的库常用的就是React-Router。React-Router V6 相对 V5 版本存在很多改动和升级,功能更为强大。关于React-Router V6的API,官方文档已经写得很好了,这里就不做过多介绍。这里就用一个常见的前端鉴权模型场景来看看React-Router V6是怎么用的吧。 

开始

通过运行以下命令打开终端并创建一个新的React项目(项目引入typescript):

> npx create-react-app ReactRouterAuthDemo --template typescript
> cd ReactRouterAuthDemo

接下来,将React Router作为依赖项安装在React应用程序中:

npm install react-router-dom

一旦安装了React-Router依赖项,我们就需要编辑src/index.tsx文件。

React-Router V6 相对 V5 多了几种定义路由的方式,这里我们不采用Router Components的方式,采用新的数据路由器对象方式。

从react-router-dom导入 createBrowserRouter,然后将路由对象数组传递给 createBrowserRouter,如下所示:

// router.tsx
import { createBrowserRouter } from 'react-router-dom'
import Home from '../pages/home/Home'

const router = createBrowserRouter([
	{
		path: '/',
		element: <Home />,
	},
])

export default router

同时在 App.tsx 中使用 RouterProvider RouterProvider 所有数据路由器对象都会传递到此组件,以呈现您的应用程序并启用其余的数据API)。

// App.tsx
import { RouterProvider } from 'react-router-dom'
import router from './routes/routes'

function App() {
	return <RouterProvider router={router} />
}

export default App

现在,我们都可以在应用程序的任何地方使用React-Router组件和挂钩。

基本路由

现在根据前端路由鉴权demo的需要,我们使用一些真实路由先简单替换 router.tsx 和 App.tsx 文件中的样板代码。

// router.tsx
import { lazy } from 'react'
import { createBrowserRouter } from 'react-router-dom'
import Layout from '../layout'
import Home from '../pages/home/Home'
// lazy-loading 需要结合 React.Suspense
const Backend = lazy(() => import('../pages/backend/Backend'))
const Admin = lazy(() => import('../pages/admin/Admin'))

const router = createBrowserRouter([
	{
		path: '/',
		element: <Layout />,
		children: [
			{
				index: true,
				element: <Home />,
			},
			{
				path: 'backend',
				element: <Backend />,
			},
			{
				path: 'admin',
				element: <Admin />,
			},
		],
	},
])

export default router

App.tsx 引入 React.Suspense,配合 React.lazy() 实现 lazy-loading

// App.tsx
import { Suspense } from 'react'
import { RouterProvider } from 'react-router-dom'
import router from './routes/routes'

function App() {
	return (
		<Suspense fallback={<div>loading...</div>}>
			<RouterProvider router={router} />
		</Suspense>
	)
}

export default App
基本的路由页面组件文件代码 (Layout页面组件使用嵌套路由和<Outlet/>)
// Layout.tsx
import { Outlet } from 'react-router-dom'

export default function Layout() {
	return (
		<div>
			<Outlet />
		</div>
	)
}

// Home.tsx
import { Link } from 'react-router-dom'

export default function Home() {
	return (
		<ul>
			<li>
				<Link to="/login">登录</Link>
			</li>
			<li>
				<Link to="/backend">后台</Link>
			</li>
			<li>
				<Link to="/admin">管理员</Link>
			</li>
		</ul>
	)
}

// Backend.tsx
import { Link } from 'react-router-dom'

export default function Backend() {
	return (
		<>
			<h1>后台页面</h1>
			<ul>
				<li>
					<Link to="/">回到首页</Link>
				</li>
			</ul>
		</>
	)
}

// Admin.tsx
import { Link } from 'react-router-dom'

export default function Admin() {
	return (
		<>
			<h1>管理员页面</h1>
			<ul>
				<li>
					<Link to="/">回到首页</Link>
				</li>
			</ul>
		</>
	)
}

接下来,我们将运行此命令来启动应用程序:

> npm start

现在基本设置已经完成,让我们看看如何创建受保护的路由,使未经身份验证的用户无法访问我们应用程序中的某些内容。

 

创建封装鉴权相关context,hooks

在创建受保护的路由(也称为专用路由)之前,让我们创建一个自定义hook,该hook将使用Context API和useContext挂钩处理经过身份验证的用户的状态:

模拟登录的相关代码

// auth.ts
// 模拟登录的相关代码
export const fakeAuthProvider = {
	isAuthenticated: false,
	signin(callback: VoidFunction) {
		fakeAuthProvider.isAuthenticated = true
		setTimeout(callback, 100) // fake async
	},
	signout(callback: VoidFunction) {
		fakeAuthProvider.isAuthenticated = false
		setTimeout(callback, 100)
	},
}

创建权限 context

// AuthContext.ts
// 创建权限 context
import { createContext } from 'react'

interface AuthContextType {
	user: any
	role?: string // '' | 'public' | 'admin' | 'user'
	signin: (user: string, callback: VoidFunction) => void
	signout: (callback: VoidFunction) => void
}

const AuthContext = createContext<AuthContextType>(null!)

export default AuthContext

传递权限信息 Provider

为了即使在页面刷新时也能保持用户的状态,使用sessionStorage做临时持久化存储,我们将使用 useSessionStorage hook,它将同步浏览器本地存储中的状态值:

// useSessionStorage.ts
import { useState } from 'react'

export const useSessionStorage = (keyName: any, defaultValue: any) => {
	const [storedValue, setStoredValue] = useState(() => {
		try {
			const value = window.sessionStorage.getItem(keyName)
			if (value) {
				return JSON.parse(value)
			} else {
				window.sessionStorage.setItem(keyName, JSON.stringify(defaultValue))
				return defaultValue
			}
		} catch (err) {
			return defaultValue
		}
	})

	const setValue = (newValue: any) => {
		try {
			window.sessionStorage.setItem(keyName, JSON.stringify(newValue))
		} catch (err) {}
		setStoredValue(newValue)
	}

	return [storedValue, setValue]
}

AuthProvider.tsx

// AuthProvider.tsx
import { useMemo } from 'react'
import { useLocalStorage } from '../hooks/useLocalStorage'
import { fakeAuthProvider } from './auth'
import AuthContext from './AuthContext'

export default function AuthProvider({ children }: { children: React.ReactNode }) {
	const [user, setUser] = useSessionStorage('user', null)
	const [role, setRole] = useSessionStorage('role', null)

	const signin = (newUser: string, callback: VoidFunction) => {
		return fakeAuthProvider.signin(() => {
			setUser(newUser)
			// 模拟管理员登录
			newUser === 'admin' ? setRole('admin') : setRole('user')
			callback()
		})
	}

	const signout = (callback: VoidFunction) => {
		return fakeAuthProvider.signout(() => {
			setUser(null)
			setRole(null)
			callback()
		})
	}

	const value = useMemo(() => ({ user, role, signin, signout }), [role, user])

	return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}

获取权限信息的hooks

useAuth.ts

import { useContext } from "react";
import AuthContext from "../auth/AuthContext";

export default function useAuth() {
	return useContext(AuthContext)
}

使用 useAuth 钩子,我们将公开用户的状态以及用于用户登录和注销的两种方法。当用户注销时,我们可以使用React-RouteruseNavigate 钩子将他们重定向到主页。

路由鉴权

创建鉴权路由

<AuthRoute /> 组件将简单地从 useAuth 钩子检查当前用户状态,然后在用户未通过身份验证的情况下重定向到主屏幕:

AuthRoute.tsx

import { Navigate, Outlet, useLocation } from 'react-router-dom'
import useAuth from '../hooks/useAuth'

export default function AuthRoute(props: any) {
	const auth = useAuth()
	const location = useLocation()

	if (!auth.user) {
		return <Navigate to="/login" state={{ from: location }} replace />
	}
	return props.children ? props.children : <Outlet />
}

我们在创建一个登录状态的组件,方便我们观察登录以及执行退出登录操作

AuthStatus.tsx

import { useNavigate } from 'react-router-dom'
import useAuth from '../hooks/useAuth'

export default function AuthStatus() {
	const auth = useAuth()
	const navigate = useNavigate()

	if (!auth.user) {
		return <h3>You are not logged in.</h3>
	}

	return (
		<p>
			Welcome {auth.user}!{' '}
			<button
				onClick={() => {
					auth.signout(() => navigate('/'))
				}}
			>
				Sign out
			</button>
		</p>
	)
}

接下来我们对 layout文件、页面组件文件和 router文件进行一些改造

Layout.tsx 引入 AuthStatus

// Layout.tsx
import { Outlet } from 'react-router-dom'
import AuthStatus from '../components/AuthStatus'

export default function Layout() {
	return (
		<div>
			<AuthStatus />
			<Outlet />
		</div>
	)
}

首页对跳转链接进行权限盘

// Home.tsx
import { Link } from 'react-router-dom'
import useAuth from '../../hooks/useAuth'

export default function Home() {
	const auth = useAuth()

	return (
		<ul>
			{!auth.user ? (
				<li>
					<Link to="/login">登录</Link>
				</li>
			) : (
				<li>
					<Link to="/backend">后台</Link>
				</li>
			)}
			{auth.role === 'admin' ? (
				<li>
					<Link to="/admin">管理员</Link>
				</li>
			) : null}
		</ul>
	)
}

登录页面

// Login.tsx
import { Link, useLocation, useNavigate } from 'react-router-dom'
import useAuth from '../../hooks/useAuth'

export default function Login() {
  const navigate = useNavigate()
  const location = useLocation()
  const auth = useAuth()
  const from = location.state?.from?.pathname || '/'

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

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

    auth.signin(username, () => {
      navigate(from, { replace: true })
    })
  }

  return (
    <>
      <h1>登录页</h1>
      <div>
        <form onSubmit={handleSubmit}>
          <label>
            Username: <input name="username" type="text" />
          </label>
          <button type="submit">Login</button>
        </form>
      </div>
      <Link to="/">回首页</Link>
    </>
  )
}

修改 router 文件

// router.tsx
import { lazy } from 'react'
import { createBrowserRouter } from 'react-router-dom'
import Layout from '../layout'
import AuthRoute from '../components/AuthRoute'
import Home from '../pages/home/Home'
// lazy-loading 需要结合 React.Suspense
const Login = lazy(() => import('../pages/login/Login'))
const Backend = lazy(() => import('../pages/backend/Backend'))
const Admin = lazy(() => import('../pages/admin/Admin'))

const router = createBrowserRouter([
	{
		path: '/',
		element: <Layout />,
		children: [
			{
				index: true,
				element: <Home />,
			},
			{
				path: 'login',
				element: <Login />,
			},
			{
				path: 'backend',
				element: (
					<AuthRoute>
						<Backend />
					</AuthRoute>
				),
			},
			{
				path: 'admin',
				element: (
					<AuthRoute>
						<Admin />
					</AuthRoute>
				),
			},
		],
	},
])

export default router

到此,我们便使用 React-Router V6.x 实现了一个前端路由鉴权。

附上代码文件路径截图

总结

React-Router v6比以前的版本有了巨大的改进。它快速、稳定、可靠。除了更容易使用外,它还有许多新功能,如<Outlet/>和改进的<Route/>组件,大大简化了React应用程序中的路由。

有了v6.x中提供的新路由器和 Data API,您可以轻松处理UI、pending 和错误状态。您可以在组件外部抽象和加载数据,直到数据准备就绪,再显示UI

我希望您本文的介绍,能够对 React-Router v6的使用有一定帮助,以及如何使用 React-Router v6 处理用户身份验证有了更好的了解。

0条评论
0 / 1000
z****n
2文章数
0粉丝数
z****n
2 文章 | 0 粉丝
z****n
2文章数
0粉丝数
z****n
2 文章 | 0 粉丝
原创

React-Router V6.x 实现前端路由鉴权

2023-06-30 08:30:15
984
0

React-Router是React生态里面很重要的一环,现在React的单页应用的路由基本都是前端自己管理的,而不像以前是后端路由,React管理路由的库常用的就是React-Router。React-Router V6 相对 V5 版本存在很多改动和升级,功能更为强大。关于React-Router V6的API,官方文档已经写得很好了,这里就不做过多介绍。这里就用一个常见的前端鉴权模型场景来看看React-Router V6是怎么用的吧。 

开始

通过运行以下命令打开终端并创建一个新的React项目(项目引入typescript):

> npx create-react-app ReactRouterAuthDemo --template typescript
> cd ReactRouterAuthDemo

接下来,将React Router作为依赖项安装在React应用程序中:

npm install react-router-dom

一旦安装了React-Router依赖项,我们就需要编辑src/index.tsx文件。

React-Router V6 相对 V5 多了几种定义路由的方式,这里我们不采用Router Components的方式,采用新的数据路由器对象方式。

从react-router-dom导入 createBrowserRouter,然后将路由对象数组传递给 createBrowserRouter,如下所示:

// router.tsx
import { createBrowserRouter } from 'react-router-dom'
import Home from '../pages/home/Home'

const router = createBrowserRouter([
	{
		path: '/',
		element: <Home />,
	},
])

export default router

同时在 App.tsx 中使用 RouterProvider RouterProvider 所有数据路由器对象都会传递到此组件,以呈现您的应用程序并启用其余的数据API)。

// App.tsx
import { RouterProvider } from 'react-router-dom'
import router from './routes/routes'

function App() {
	return <RouterProvider router={router} />
}

export default App

现在,我们都可以在应用程序的任何地方使用React-Router组件和挂钩。

基本路由

现在根据前端路由鉴权demo的需要,我们使用一些真实路由先简单替换 router.tsx 和 App.tsx 文件中的样板代码。

// router.tsx
import { lazy } from 'react'
import { createBrowserRouter } from 'react-router-dom'
import Layout from '../layout'
import Home from '../pages/home/Home'
// lazy-loading 需要结合 React.Suspense
const Backend = lazy(() => import('../pages/backend/Backend'))
const Admin = lazy(() => import('../pages/admin/Admin'))

const router = createBrowserRouter([
	{
		path: '/',
		element: <Layout />,
		children: [
			{
				index: true,
				element: <Home />,
			},
			{
				path: 'backend',
				element: <Backend />,
			},
			{
				path: 'admin',
				element: <Admin />,
			},
		],
	},
])

export default router

App.tsx 引入 React.Suspense,配合 React.lazy() 实现 lazy-loading

// App.tsx
import { Suspense } from 'react'
import { RouterProvider } from 'react-router-dom'
import router from './routes/routes'

function App() {
	return (
		<Suspense fallback={<div>loading...</div>}>
			<RouterProvider router={router} />
		</Suspense>
	)
}

export default App
基本的路由页面组件文件代码 (Layout页面组件使用嵌套路由和<Outlet/>)
// Layout.tsx
import { Outlet } from 'react-router-dom'

export default function Layout() {
	return (
		<div>
			<Outlet />
		</div>
	)
}

// Home.tsx
import { Link } from 'react-router-dom'

export default function Home() {
	return (
		<ul>
			<li>
				<Link to="/login">登录</Link>
			</li>
			<li>
				<Link to="/backend">后台</Link>
			</li>
			<li>
				<Link to="/admin">管理员</Link>
			</li>
		</ul>
	)
}

// Backend.tsx
import { Link } from 'react-router-dom'

export default function Backend() {
	return (
		<>
			<h1>后台页面</h1>
			<ul>
				<li>
					<Link to="/">回到首页</Link>
				</li>
			</ul>
		</>
	)
}

// Admin.tsx
import { Link } from 'react-router-dom'

export default function Admin() {
	return (
		<>
			<h1>管理员页面</h1>
			<ul>
				<li>
					<Link to="/">回到首页</Link>
				</li>
			</ul>
		</>
	)
}

接下来,我们将运行此命令来启动应用程序:

> npm start

现在基本设置已经完成,让我们看看如何创建受保护的路由,使未经身份验证的用户无法访问我们应用程序中的某些内容。

 

创建封装鉴权相关context,hooks

在创建受保护的路由(也称为专用路由)之前,让我们创建一个自定义hook,该hook将使用Context API和useContext挂钩处理经过身份验证的用户的状态:

模拟登录的相关代码

// auth.ts
// 模拟登录的相关代码
export const fakeAuthProvider = {
	isAuthenticated: false,
	signin(callback: VoidFunction) {
		fakeAuthProvider.isAuthenticated = true
		setTimeout(callback, 100) // fake async
	},
	signout(callback: VoidFunction) {
		fakeAuthProvider.isAuthenticated = false
		setTimeout(callback, 100)
	},
}

创建权限 context

// AuthContext.ts
// 创建权限 context
import { createContext } from 'react'

interface AuthContextType {
	user: any
	role?: string // '' | 'public' | 'admin' | 'user'
	signin: (user: string, callback: VoidFunction) => void
	signout: (callback: VoidFunction) => void
}

const AuthContext = createContext<AuthContextType>(null!)

export default AuthContext

传递权限信息 Provider

为了即使在页面刷新时也能保持用户的状态,使用sessionStorage做临时持久化存储,我们将使用 useSessionStorage hook,它将同步浏览器本地存储中的状态值:

// useSessionStorage.ts
import { useState } from 'react'

export const useSessionStorage = (keyName: any, defaultValue: any) => {
	const [storedValue, setStoredValue] = useState(() => {
		try {
			const value = window.sessionStorage.getItem(keyName)
			if (value) {
				return JSON.parse(value)
			} else {
				window.sessionStorage.setItem(keyName, JSON.stringify(defaultValue))
				return defaultValue
			}
		} catch (err) {
			return defaultValue
		}
	})

	const setValue = (newValue: any) => {
		try {
			window.sessionStorage.setItem(keyName, JSON.stringify(newValue))
		} catch (err) {}
		setStoredValue(newValue)
	}

	return [storedValue, setValue]
}

AuthProvider.tsx

// AuthProvider.tsx
import { useMemo } from 'react'
import { useLocalStorage } from '../hooks/useLocalStorage'
import { fakeAuthProvider } from './auth'
import AuthContext from './AuthContext'

export default function AuthProvider({ children }: { children: React.ReactNode }) {
	const [user, setUser] = useSessionStorage('user', null)
	const [role, setRole] = useSessionStorage('role', null)

	const signin = (newUser: string, callback: VoidFunction) => {
		return fakeAuthProvider.signin(() => {
			setUser(newUser)
			// 模拟管理员登录
			newUser === 'admin' ? setRole('admin') : setRole('user')
			callback()
		})
	}

	const signout = (callback: VoidFunction) => {
		return fakeAuthProvider.signout(() => {
			setUser(null)
			setRole(null)
			callback()
		})
	}

	const value = useMemo(() => ({ user, role, signin, signout }), [role, user])

	return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}

获取权限信息的hooks

useAuth.ts

import { useContext } from "react";
import AuthContext from "../auth/AuthContext";

export default function useAuth() {
	return useContext(AuthContext)
}

使用 useAuth 钩子,我们将公开用户的状态以及用于用户登录和注销的两种方法。当用户注销时,我们可以使用React-RouteruseNavigate 钩子将他们重定向到主页。

路由鉴权

创建鉴权路由

<AuthRoute /> 组件将简单地从 useAuth 钩子检查当前用户状态,然后在用户未通过身份验证的情况下重定向到主屏幕:

AuthRoute.tsx

import { Navigate, Outlet, useLocation } from 'react-router-dom'
import useAuth from '../hooks/useAuth'

export default function AuthRoute(props: any) {
	const auth = useAuth()
	const location = useLocation()

	if (!auth.user) {
		return <Navigate to="/login" state={{ from: location }} replace />
	}
	return props.children ? props.children : <Outlet />
}

我们在创建一个登录状态的组件,方便我们观察登录以及执行退出登录操作

AuthStatus.tsx

import { useNavigate } from 'react-router-dom'
import useAuth from '../hooks/useAuth'

export default function AuthStatus() {
	const auth = useAuth()
	const navigate = useNavigate()

	if (!auth.user) {
		return <h3>You are not logged in.</h3>
	}

	return (
		<p>
			Welcome {auth.user}!{' '}
			<button
				onClick={() => {
					auth.signout(() => navigate('/'))
				}}
			>
				Sign out
			</button>
		</p>
	)
}

接下来我们对 layout文件、页面组件文件和 router文件进行一些改造

Layout.tsx 引入 AuthStatus

// Layout.tsx
import { Outlet } from 'react-router-dom'
import AuthStatus from '../components/AuthStatus'

export default function Layout() {
	return (
		<div>
			<AuthStatus />
			<Outlet />
		</div>
	)
}

首页对跳转链接进行权限盘

// Home.tsx
import { Link } from 'react-router-dom'
import useAuth from '../../hooks/useAuth'

export default function Home() {
	const auth = useAuth()

	return (
		<ul>
			{!auth.user ? (
				<li>
					<Link to="/login">登录</Link>
				</li>
			) : (
				<li>
					<Link to="/backend">后台</Link>
				</li>
			)}
			{auth.role === 'admin' ? (
				<li>
					<Link to="/admin">管理员</Link>
				</li>
			) : null}
		</ul>
	)
}

登录页面

// Login.tsx
import { Link, useLocation, useNavigate } from 'react-router-dom'
import useAuth from '../../hooks/useAuth'

export default function Login() {
  const navigate = useNavigate()
  const location = useLocation()
  const auth = useAuth()
  const from = location.state?.from?.pathname || '/'

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

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

    auth.signin(username, () => {
      navigate(from, { replace: true })
    })
  }

  return (
    <>
      <h1>登录页</h1>
      <div>
        <form onSubmit={handleSubmit}>
          <label>
            Username: <input name="username" type="text" />
          </label>
          <button type="submit">Login</button>
        </form>
      </div>
      <Link to="/">回首页</Link>
    </>
  )
}

修改 router 文件

// router.tsx
import { lazy } from 'react'
import { createBrowserRouter } from 'react-router-dom'
import Layout from '../layout'
import AuthRoute from '../components/AuthRoute'
import Home from '../pages/home/Home'
// lazy-loading 需要结合 React.Suspense
const Login = lazy(() => import('../pages/login/Login'))
const Backend = lazy(() => import('../pages/backend/Backend'))
const Admin = lazy(() => import('../pages/admin/Admin'))

const router = createBrowserRouter([
	{
		path: '/',
		element: <Layout />,
		children: [
			{
				index: true,
				element: <Home />,
			},
			{
				path: 'login',
				element: <Login />,
			},
			{
				path: 'backend',
				element: (
					<AuthRoute>
						<Backend />
					</AuthRoute>
				),
			},
			{
				path: 'admin',
				element: (
					<AuthRoute>
						<Admin />
					</AuthRoute>
				),
			},
		],
	},
])

export default router

到此,我们便使用 React-Router V6.x 实现了一个前端路由鉴权。

附上代码文件路径截图

总结

React-Router v6比以前的版本有了巨大的改进。它快速、稳定、可靠。除了更容易使用外,它还有许多新功能,如<Outlet/>和改进的<Route/>组件,大大简化了React应用程序中的路由。

有了v6.x中提供的新路由器和 Data API,您可以轻松处理UI、pending 和错误状态。您可以在组件外部抽象和加载数据,直到数据准备就绪,再显示UI

我希望您本文的介绍,能够对 React-Router v6的使用有一定帮助,以及如何使用 React-Router v6 处理用户身份验证有了更好的了解。

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0