前端路由的奇妙冒险:从稚嫩走向成熟的全过程(下)

导航

现代路由的进阶特性

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>

看看此时的效果:

可以看到,组件只要在需要的时候才加载。

总结

  1. 鉴权路由 :始终保护敏感路由
  2. 懒加载 :提升首屏性能
  3. 错误处理 :提供友好的404页面
  4. 状态管理 :合理使用location state

现代前端路由已经发展成为一个功能丰富、性能优化的完整解决方案, 项目完美展示了这些先进特性。

相关推荐
薄荷椰果抹茶1 分钟前
前端技术之---应用国际化(vue-i18n)
前端·javascript·vue.js
鹿啦啦SHARE1 分钟前
volta了解和使用
前端
小高00716 分钟前
掌握 requestFullscreen:网页全屏功能的实用指南与技巧
前端
前端付豪24 分钟前
22、前端工程化深度实践:构建、发布、CI/CD 流程重构指南
前端·javascript·架构
我是若尘30 分钟前
从“全大图”到“响应式加载”:企业级前端图片优化全攻略(含Vue/React自动化方案)
前端
北北~Simple30 分钟前
css 如何实现大屏4个占位 中屏2个 小屏幕1个
前端·css
在逃的吗喽33 分钟前
APIs案例及知识点串讲(上)
前端·javascript
qq_5829434537 分钟前
html5侧边提示框
前端·javascript·html5
孟陬43 分钟前
写一个根据屏幕尺寸动态隐藏元素的插件 🧩 - tailwindcss 系列
react.js