导航
现代路由的进阶特性
1. 路由守卫与鉴权
引入
我们先在App.jsx上写下如下的代码
jsx
// App.jsx
import {
useState
} from 'react'
import './App.css'
import {
BrowserRouter as Router, // 前端路由
Routes, // 路由设置容器
Route, // 单条路由
} from "react-router-dom";
import Nav from './components/Nav';
import Pay from './pages/Pay';
import Home from './pages/Home'
import About from './pages/About'
import NotFound from './pages/NotFound'
function App() {
return (
<>
<Router>
<Nav/>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/pay" element={<Pay />} />
<Route path='*' element={<NotFound />} />
</Routes>
</Router>
</>
)
}
export default App
Home、About、Pay 等页面组件的构建与前端路由的奇妙冒险:从稚嫩走向成熟的全过程(上)中一致,这里就不加赘述了。Nav 页面可以使用Link组件,来实现无刷:
jsx
// Nav 文件中的 index.jsx
import { Link } from 'react-router-dom';
const Nav = () => {
return (
<nav>
<ul>
<li><Link to="/">Home</Link></li>
</ul>
<ul>
<li><Link to="/about">About</Link></li>
</ul>
</nav>
)
}
export default Nav
我们先来看看此时的页面效果:

很完美的实现了页面的切换吧。但是有个问题,支付(Pay)页面这么简单就能进去,那不是很容易就把你的money给拿走了吗?
有没有一个办法可以解决这个问题呢?

有的,有的,我们可以在跳转到Pay页面的时候,再跳转到一个登录页面Login),只有验证成功才跳转到Pay页面,验证失败则跳转到Home页面。
ProtectRoute 路由守卫者
我们在App.jsx中用ProtectRoute
包裹住Pay的route,并且编写Login
页面的路由。
jsx
// App.jsx
<Router>
<Nav/>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
<Route path="/pay" element={
<ProtectRoute>
<Pay />
</ProtectRoute>
} />
<Route path='*' element={<NotFound />} />
</Routes>
</Router>
这样Pay组件就成了传递给ProtectRoute
的一个children
组件。
注意:这里的chlidren组件,并不是指Pay为ProtectRoute的子组件,Pay只是传参进了ProtectRoute, 而参数名为children。
在网页上输入Pay页面所匹配的地址后。先进入的是ProtectRoute
页面,而不是Pay页面。这时我们便可以在ProtectRoute
中来进行一些操作来控制页面,什么时候跳转到Login
,什么时候跳转到Pay页面。
编写ProtectRoute页面:
jsx
// ProtectRoute 文件夹 中的 index.jsx
import {
Navigate,
useLocation
} from 'react-router-dom';
const ProtectRoute = ( props ) => {
const { children } = props;
const { pathname } = useLocation(); //获取当前路径 /pay
const isLogin = localStorage.getItem('isLogin') === 'true';
if (!isLogin) {
return <Navigate to= "/login" state={{from: pathname}}/>
}
return (children);
}
export default ProtectRoute
这里我们设置了一个isLogin
用来判断用户是否登入。开始时从localStorage本地存储中获取isLogin。若为fasle,!isLogin = true
进入if判断语句。用Navigate
组件跳转到/login路由对应的Login界面,并且传递了一个状态state = {from : /pay}
。
可以发现,如果isLogin
为true,即登录成功,就不会进入if判断,我们就返回Pay页面。如果为false,就跳转到Login页面,并且把/pay的路由传递过去。
Login 登入页面
jsx
// Login 文件夹中的 index.jsx
import { useState } from "react";
import {
useNavigate,
useLocation
} from 'react-router-dom'
const Login = () => {
const location = useLocation();
const navigate = useNavigate();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (event) => {
event.preventDefault();
if (username === 'admin' && password === '123456') {
localStorage.setItem('isLogin', 'true');
navigate(location?.state?.from || '/');
} else {
alert('用户名或密码错误');
}
}
return (
<>
<form onSubmit={handleSubmit}>
<h1>登入页面</h1>
<input
type="text"
placeholder="请输入用户名"
value={username}
required
onChange={(event) => setUsername(event.target.value)}
/>
<input
type="password"
placeholder="请输入密码"
required
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
<button type="submit">登录</button>
</form>
</>
)
}
export default Login
是不是感觉看的头都大的,没事的。我带你一步一步地解析它。

我们先来看看return中的,先易后难嘛
jsx
return (
<>
<form onSubmit={handleSubmit}>
<h1>登入页面</h1>
<input
type="text"
placeholder="请输入用户名"
value={username}
required
onChange={(event) => setUsername(event.target.value)}
/>
<input
type="password"
placeholder="请输入密码"
required
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
<button type="submit">登录</button>
</form>
</>
)
这里我们写了一个表单,但表单提交后,执行handleSubmit函数来验证用户名、密码是否正确。
有两个提交的文本框,一个是用户名,一个是密码。用onChange动态改变状态。
再来看看上面的代码。
jsx
const location = useLocation();
const navigate = useNavigate();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (event) => {
event.preventDefault();
if (username === 'admin' && password === '123456') {
localStorage.setItem('isLogin', 'true');
navigate(location?.state?.from || '/');
} else {
alert('用户名或密码错误');
}
}
const location = useLocation();
用于获取/pay的地址的
const navigate = useNavigate();
用于跳转页面的
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
用户名和密码的响应式状态
最后是handleSubmit函数。
event.preventDefault();
阻止表单提交后带来的页面刷新
jsx
if (username === 'admin' && password === '123456') {
localStorage.setItem('isLogin', 'true');
navigate(location?.state?.from || '/');
} else {
alert('用户名或密码错误');
}
判断登入是否成功的逻辑代码。如果username和password与我们设置的相等,就使用localStorage 存储 key='isLogin' value='true。 并且用navigate跳转到localtion.state.from ------------ /pay。
有的人可能会想?.state?
是什么意思啊
?. 是可选链操作符。- 用于安全访问可能为 null 或 undefined 的对象属性。如果前面的值为 null 或 undefined ,表达式会短路返回 undefined 而不会报错
因为有的时候,有人会直接在网址上输入/login,而不是通过/pay跳转到Login页面。因为没有经过ProtectRoute页面,所有ProtectRoute中传递的state也就不存在了,为undefined。对undefined.from会报错,所以我们要用?.state?。
没加?.state?的效果
加上?.state?的效果

可以看到不会报错了。
路由守卫效果
把这些所有的代码理解过后,我们最后再来看看整体的效果吧。

可以看到输入pay页面的地址,跳转到了登录界面。这就实现了我们路由守卫的效果。
恭喜你,基本完成了前端路由的基本构建。
聪明的你一定能想到,这里面还有很多能优化的地方。这里我就提一个吧。
2.路由懒加载
当我们在Home页面和Pay页面上使用console输出语句。
jsx
console.log('Home');
const Home = () => {
return <h1>Home</h1>
}
export default Home
jsx
console.log('About');
const About = () => {
return <h1>About</h1>
}
export default About

我们发现明明不在About页面,却输出了About。
事实上,在浏览器打开的时候,我们的所有页面的组件都会加载一遍。这很明显会造成性能的问题,我们要优化它。
我们可以使用lazy 和 Suspense 来优化体验。
我们在App.jsx 中导入lazy, Suspense
jsx
// App.jsx
import {
lazy,
Suspense
} from 'react'
把页面的import换成lazy
jsx
import ProtectRoute from './pages/ProtectRoute';
import Nav from './components/Nav';
import Pay from './pages/Pay';
import Home from './pages/Home'
import About from './pages/About'
import NotFound from './pages/NotFound'
import Login from './pages/Login'
换成
jsx
// lazy会返回一个新组件
const ProtectRoute = lazy(() => import ('./pages/ProtectRoute'))
const Nav = lazy(() => import('./components/Nav'));
const Pay = lazy(() => import('./pages/Pay'));
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const NotFound = lazy(() => import('./pages/NotFound'));
const Login = lazy(() => import('./pages/Login'));
为了让用户体验更好,我们再用Suspense来让等待组件加载时用户的体验更好。
jsx
<Router>
<Nav/>
<Suspense fallback={<div>loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
<Route path="/pay" element={
<ProtectRoute>
<Pay />
</ProtectRoute>
} />
<Route path='*' element={<NotFound />} />
</Routes>
</Suspense>
</Router>
看看此时的效果:

可以看到,组件只要在需要的时候才加载。
总结
- 鉴权路由 :始终保护敏感路由
- 懒加载 :提升首屏性能
- 错误处理 :提供友好的404页面
- 状态管理 :合理使用location state
现代前端路由已经发展成为一个功能丰富、性能优化的完整解决方案, 项目完美展示了这些先进特性。