React-Router V6 使用详解
- Remix 团队在2020年6月发布了第一个V6.0.0-beta.0版本的React-Router,也预示着V6版本的正式开始,相比V5版本的确有很多方面的升级。
- 本文将结合V6特性和V5如何升级V6两方面来为大家详细讲解React-Router的使用。
- (使用版本:V6.0.2稳定版)
一、基本用法
React-Router的安装方法:
npm:npm install react-router-dom
yarn:yarn add react-router-dom
- 目前官方从5开始已经放弃原有的react-router库,统一命名为react-router-dom
使用方法
- React-Router本身在React开发中就是一个组件,因此在使用时基本遵循组件开发相关原则。这里采用create-react-app来创建一个基础的demo工程演示使用过程。
- 创建demo
create-react-app my-first-react
安装react-router组件
- 启用全局路由模式
全局路由有常用两种路由模式可选:HashRouter 和 BrowserRouter
HashRouter:URL中采用的是hash(#)部分去创建路由,类似www.example.com/#/ BrowserRouter:URL采用真实的URL资源
后续有文章会详细讲HashRouter的原理和实现,这里我们采用BrowserRouter来创建路由
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
这样我们在yarn start 或者 npm run start的时候访问/就可以访问这个组件了,具体效果大家可以自行运行
- 路由功能
React-Router V6版本常用路由组件和hooks,其他不常用的大家可以看下官网的介绍
组件名 | 作用 | 说明 |
---|---|---|
<Routers> | 一组路由 | 代替原有<Switch> ,所有子路由都用基础的Router children来表示 |
<Router> | 基础路由 | Router是可以嵌套的,解决原有V5中严格模式,后面与V5区别会详细介绍 |
<Link> | 导航组件 | 在实际页面中跳转使用 |
<Outlet/> | 自适应渲染组件 | 根据实际路由url自动选择组件 |
hooks名 | 作用 | 说明 |
---|---|---|
useParams | 返回当前参数 | 根据路径读取参数 |
useNavigate | 返回当前路由 | 代替原有V5中的 useHistory |
useOutlet | 返回根据路由生成的element | |
useLocation | 返回当前的location 对象 | |
useRoutes | 同Routers组件一样,只不过是在js中使用 | |
useSearchParams | 用来匹配URL中?后面的搜索参数 |
App.js
-
这里创建了两个组件Home和About,然后分别注册/和about,在每个页面还有Link来进行导航
import './App.css'; import { Routes, Route, Link } from "react-router-dom" function App() { return ( <div className="App"> <header className="App-header"> <Routes> <Route path="/" element={<Home />}></Route> <Route path="/about" element={<About />}></Route> </Routes> </header> </div> ); } function Home() { return <div> <main> <h2>Welcome to the homepage</h2> </main> <nav> <Link to="/about">about</Link> </nav> </div> } function About() { return <div> <main> <h2>Welcome to the about page</h2> </main> <nav> <ol> <Link to="/">home</Link> <Link to="/about">about</Link> </ol> </nav> </div> } export default App;
运行后
至此,一个最简单的路由demo就正常运行了。
- 嵌套路由
嵌套路由是V6版本对之前版本一个较大的升级,采用嵌套路由会智能的识别
function App() {
return (
<Routes>
<Route path="user" element={<Users />}>
<Route path=":id" element={<UserDetail />} />
<Route path="create" element={<NewUser />} />
</Route>
</Routes>
);
}
当访问 /user/123 的时候,组件树将会变成这样
<App>
<Users>
<UserDetail/>
</Users>
</App>
当访问/user/create的时候,组件树将变成这样
<App>
<Users>
<NewUser/>
</Users>
</App>
如果只是内部组件修改,也可以采用<Outlet/>
来直接实现,如下所示
function App() {
return (
<Routes>
<Route path="user" element={<Users />}>
<Route path=":id" element={<UserDetail />} />
<Route path="create" element={<NewUser />} />
</Route>
</Routes>
);
}
function Users() {
return (
<div>
<h1>Users</h1>
<Outlet />
</div>
);
}
- index路由
方式1:index属性解决当嵌套路由有多个子路由但本身无法确认默认渲染哪个子路由的时候,可以增加index属性来指定默认路由,或者使用使用useNavigate
import React, { useEffect } from 'react'
import { Routes, Route, useNavigate } from 'react-router-dom'
import Layout from '@/layout/Index'
import NotFound from '@/pages/NotFound'
import Login from '@/pages/Login'
import Home from '@/pages/Home'
// 跳转路由
const Redirect: React.FC<{ to: string }> = props => {
const navigate = useNavigate()
useEffect(() => {
navigate(props.to)
})
return null
}
const App: React.FC = () => {
// 默认跳转/home
// return (
// <div>
// <Routes>
// <Route path="/" element={<Layout />}>
// {/* 方式1 添加index属性 把它自己的path去掉 */}
// <Route index element={<Home />} />
// </Route>
// <Route path="/Login" element={<Login />} />
// <Route path="*" element={<NotFound />} />
// </Routes>
// </div>
// )
return (
<div>
<Routes>
{/* 方式2: */}
<Route path="/" element={<Redirect to="/home" />} />
<Route path="/" element={<Layout />}>
<Route path="/home" element={<Home />} />
</Route>
<Route path="/Login" element={<Login />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
)
}
export default App
这样当访问/的时候<Outlet/>
会默认渲染About组件
- 路由通配符
- 整个react-router支持以下几种通配符
/groups
/groups/admin
/users/:id
/users/:id/messages
/files/*
/files/:id/*
123456
注意,以下这些正则方式在V6里面是不支持的
/users/:id?
/tweets/:id(\d+)
/files/*/cat.jpg
/files-*
1234
这里的*
只能用在/后面,不能用在实际路径中间
关于NotFound类路由,可以用*
来代替
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="*" element={<NotFound />} />
</Routes>
);
}
123456789
- 获取参数 useParams 和useSearchParams
假设现有App路由
function App() {
return (
<Routes>
<Route path="user" element={<Users />}>
<Route path=":id" element={<UserDetail />} />
<Route path="create" element={<NewUser />} />
</Route>
</Routes>
);
}
12345678910
那么在UserDetail内部需要用useParams来获取对应的参数
import { useParams } from "react-router-dom";
export default function UserDetail() {
let params = useParams();
return <h2>User: {params.id}</h2>;
}
123456
useSearchParams相对复杂,他返回的是一个当前值和set方法
let [searchParams, setSearchParams] = useSearchParams();
使用时可以用searchParams.get("id")
来获取参数,同时页面内也可以setSearchParams({“id”:2})来改变路由,这样当访问 http://URL/user?id=111 时就可以获取和设置路径
- useNavigate
useNavigate是替代原有V5中的useHistory的新hooks,其用法和useHistory类似,整体使用起来更轻量,他的声明方式如下:
declare function useNavigate(): NavigateFunction;
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: State }
): void;
(delta: number): void;
}
123456789
//js写法
let navigate = useNavigate();
function handleClick() {
navigate("/home");
}
//组件写法
function App() {
return <Navigate to="/home" replace state={state} />;
}
//替代原有的go goBack和goForward
<button onClick={() => navigate(-2)}>
Go 2 pages back
</button>
<button onClick={() => navigate(-1)}>Go back</button>
<button onClick={() => navigate(1)}> Go forward </button>
<button onClick={() => navigate(2)}> Go 2 pages forward </button>
12345678910111213141516
二、与V5的区别
1.用<Routes> children形式
替代<Switch>
V5写法:
function App() {
return (
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/users/:id" children={<User />} />
</Switch>
);
}
V6写法
function App() {
return (
<Routes>
<Route index path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="/users/:id" element={<User />} />
</Routes>
);
}
2.去除Switch中的<Redirect>
,用react-router-dom中的Redirect 替代,或者用 <Navigate>
实现
V5写法:
<Switch>
<Redirect from="about" to="about-us" />
</Switch>
V6写法:
<Route path="about" render={() => <Redirect to="about-us" />}
3.<Link to>
支持相对位置
V5版本的to属性只支持绝对位置,如<Lint to="me">
表示<Lint to="/me">
,如果当时正在Users组件内,想跳转需要<Lint to="/users/me">
。在V6中,Link默认支持相对位置,也就是<Lint to="me">
在Users组件内会等价于<Lint to="/users/me">
,同时支持’…’ 和’.'等相对路径写法。
// If your routes look like this
<Route path="app">
<Route path="dashboard">
<Route path="stats" />
</Route>
</Route>
// and the current URL is /app/dashboard (with or without
// a trailing slash)
<Link to="stats"> => <a href="/app/dashboard/stats">
<Link to="../stats"> => <a href="/app/stats">
<Link to="../../stats"> => <a href="/stats">
<Link to="../../../stats"> => <a href="/stats">
4.使用useNavigate代替useHistory
可以参考上面useNavigate使用,这里不再赘述
三、路由配置,嵌套路由
React Router 提供了两个接口来配置路由
-
:通过在jsx中组件方式
-
useRouters通过对象配置方式
例:在index.js里通过配置
import React from 'react';
import ReactDOM from 'react-dom';
import { store } from './app/store';
import { Provider } from 'react-redux';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import App from './App';
import { PostsList } from './features/posts/PostsList';
import { SinglePostPage } from './features/posts/SinglePostPage';
import * as serviceWorker from './serviceWorker';
import './index.css';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<Routes>
<Route path='/' element={<App />} />
{/* 嵌套路由 */}
<Route path="/postList" element={<PostsList />}>
<Route index element={<div>我是index默认子路由</div>} />
<Route path=":id" element={<div>我是子路由元素</div>} />
</Route>
<Route path="/singlePostPage/:postId" element={<SinglePostPage />} />
<Route path="*" element={<App />} />
</Routes>
</BrowserRouter>
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
例:在index.js里通过useRouters配置,此处配置可提出至单独文件
import React from 'react';
import ReactDOM from 'react-dom';
import { store } from './app/store';
import { Provider } from 'react-redux';
import { BrowserRouter, useRoutes } from 'react-router-dom';
import App from './App';
import { PostsList } from './features/posts/PostsList';
import { SinglePostPage } from './features/posts/SinglePostPage';
import './index.css';
const GetRoutes = () => {
const routes = useRoutes([
{ path: '/', element: <App /> },
{
path: '/postList',
element: <PostsList />,
children: [
{ path: '/postList/:id', element: <div>我是子路由元素</div> },
{ path: '*', element: <div>404</div>}
],
},
{ path: '/singlePostPage/:postId', element: <SinglePostPage /> },
{ path: '*', element: <div>404</div>}
])
return routes;
}
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<GetRoutes />
</BrowserRouter>
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
注意嵌套路由需要在父路由使用Outlet呈现其子路由元素
import React from 'react';
import { useSelector } from 'react-redux';
import { Link, Outlet } from 'react-router-dom'
export const PostsList = () => {
const posts = useSelector(state => state.posts);
const renderedPosts = posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content.substring(0,100)}</p>
<Link to={`/singlePostPage/${post.id}`}> 查看详情</Link>
</div>
))
return (
<section>
<h2>posts</h2>
{renderedPosts}
{/* 子路由渲染位置 */}
<Outlet />
</section>
)
}
四、总结
V6版本的react-router升级了原有嵌套路由写法,并且重新实现了useNavigate来替代useHistory,整体上更加好理解。当然还有些其他属性和方法没有再介绍,大家如果有其他想知道的也可以回复我来补充。
参考链接:
React-Router V6 使用详解 原文链接:https://blog.csdn.net/weixin_44733660/article/details/123204029
React Router v6 路由配置,嵌套路由 原文链接:https://blog.csdn.net/Snow_GX/article/details/123656412