前言
今天新建了一个新项目,使用到了react-router-dom,当我引入Switch的时候发现有个下划线,报错说是react-router-dom没有导出Switch,去了官网查看官网都变了,才发现已经是6.x版本了。
新特性
1.移除Switch和Redirect组件
react-router移除了Switch,但是引入了功能更强大的Routes, Routes相对于Switch的有点如下:
- 所有的Route和Link位于Routes里面,旨在有更多简洁且可预测的代码出现在Route和Link内。
- Routes会根据最佳匹配路线进行匹配,而不是按顺序进行匹配。
- 路由可以嵌套再一个地方,而不是分散在各个组件,在中小程序中一眼就能看到应用的路由配置,这也是我最喜欢的一点。
这是在V5 react-router中的Switch使用
// This is a React Router v5 app
import React, { FC, ReactElement } from 'react';
import {
BrowserRouter,
Route,
Routes,
Navigate
} from "react-router-dom";
import Home from '../views/Home';
import Detail from '../views/Detail';
const Router: FC = (): ReactElement => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />}></Route>
<Route path="/detail" element={<Detail />}></Route>
</Routes>
</BrowserRouter>
)
}
2.移除Redirect
react-router删除所有位于Switch内的Redirect元素,在V6版本中使用Navigate代替 我们使用一个简单的示例来了解新的组件怎么使用
// router.tsx
import React, { FC } from 'react';
import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";
import Layout from '../views/Layout';
import Login from '../views/Login';
import Home from '../views/Home';
import Detail from '../views/Detail';
import Cart from '../views/Cart';
import NotFound from '../views/NotFound';
import Article from '../views/Article';
const Router: FC = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}> //嵌套路由-所嵌套的子Route使用新的API Outlet 在父组件呈现出来,(类似与Vue的router-view)
<Route path="home" element={<Home />}></Route>
<Route path="detail" element={<Detail />}></Route>
<Route path="cart" element={<Cart />}></Route>
<Route path="article">
<Route path=":id" element={<Article />}>
</Route>
</Route>
<Route path="/*" element={<Navigate to="/home" replace={true} />} /> //输入根目录默认跳转到/home 用Navigate代替Redirect
<Route path="*" element={<NotFound />} /> //用来匹配未知的路由配置
</Route>
<Route path="login" element={<Login />}></Route>
</Routes>
</BrowserRouter>
)
}
//Layout.tsx
const Layout: FC = (): ReactElement => {
return (
<div className="container">
<div className="nav">
<Link to="/home">首页</Link>
<Link to="/cart">购物车</Link>
<Link to="/detail">详情</Link>
<Link to="/article/520">去特定文章</Link>
</div>
<div className="content">
<Outlet />
</div>
</div>
)
}
// Home.tsx
import { useNavigate } from "react-router-dom";
const Home: FC = (): ReactElement => {
//使用useNavigate代替useHistory navigate(to, { state })
const navigate = useNavigate();
return (
<div>
Home
<div>
<button onClick={() => navigate('/article/1314')}>去第1314篇文章</button>
</div>
</div>
)
}
//Detail.tsx
import { useSearchParams } from "react-router-dom";
const Detail: FC = (): ReactElement => {
// 获取路径/detail?name=danceli 匹配的name值 -> danceli
const [searchParams] = useSearchParams();
return (
<div>detail - {searchParams.get("name")}</div>
)
}
//Cart.tsx
const Cart: FC = (): ReactElement => {
return (
<div className="container">
cart
</div>
)
}
// Login.tsx
const Login: FC = (): ReactElement => {
return (
<div>
login
</div>
)
}
//NotFound.tsx
const NotFound: FC = (): ReactElement => {
return (
<div>
404
</div>
)
}
// Article.tsx
import React, { FC, ReactElement } from 'react';
import { useParams } from 'react-router-dom';
const Article: FC = (): ReactElement => {
//获取/article/:id匹配的article/520的id值520
const params = useParams();
return (
<div>
article文章: {params.id}
</div>
)
}
export default Router;
上面代码已经可以对新的API和hooks的有一定的熟悉,包括获取params,query, 以及嵌套路由,404NotFound, 模拟Redirect(Navigate)。
路由守卫
React中没有封装好的路由守卫钩子,需要我们自己进行封装配置。 首先我们需要分析我们的需求:
我们需要对/detail 和 /article 进行鉴权守卫,在点击进入经过守卫路由的时候,如果当前用户没有登录,就要重定向到/login,且进入Login页面的时候把要进入的页面的pathname传递给Login,以便登录完成后再自动跳转到登录前要进入的页面。
// router.jsx
const Router = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route path="home" element={<Home />}></Route>
<Route path="article" element={
<ProviderRouter>
<Article />
</ProviderRouter>
}>
</Route>
<Route path="detail" element={
<ProviderRouter>
<Detail />
</ProviderRouter>
}>
</Route>
<Route path="/" element={<Navigate to="/home" replace={true} />}/>
</Route>
<Route path="/login" element={<Login />}></Route>
</Routes>
</BrowserRouter>
)
}
// Layout.jsx
import { Link, Outlet } from 'react-router-dom';
const Layout = () => {
return (
<div>
<Link to="/home">首页</Link>
<Link to="/detail">详情</Link>
<Link to="/article">去特定文章</Link>
<div>
<Outlet />
</div>
</div>
)
}
// ProviderRouter.js
import { Navigate, useLocation } from 'react-router-dom';
const ProviderRouter = ({children}) => {
//拿到当前的页面的pathname
const location = useLocation();
//取到当前的用户信息 isLogin
const user = useSelector(state => state.user);
const isLogin = user.isLogin
//如果没有登录,重定向到Login页面,并且把用户要去的页面通过state传过去
if(!isLogin) {
return <Navigate to="/login" state={{from: location}} />
}
// 如果已经登录,就直接返回Route匹配的组件
return children
}
// Login.jsx
import { useLocation, useNavigate } from 'react-router-dom';
const Login = () => {
const dispatch = useDispatch(),
location = useLocation(), //拿到当前的路由信息,包括守卫中传来的跳转信息state
navigate = useNavigate();
const { pathname } = location.state.from;
//登录 并且跳转到刚才守卫传来的用户要跳转的页面
const handleLogin = () => {
dispatch({ type: "login" });
navigate(pathname)
}
return (
<div>
Login
<button onClick={handleLogin}>登录</button>
</div>
)
}
// Article.jsx
const Article = () => {
return (
<div>
Article
</div>
)
}
// Detail.jsx
const Detail = () => {
return (
<div>
Detail
</div>
)
}
// Home.jsx
const Home = () => {
return (
<div>
Home
</div>
)
}
结果如下
其他
React Router v6使用简化的路径格,仅支持2种占位符:动态:id样式参数和*通配符。
最后
参考文章