React Router V6 入门指南
说明
最近使用React Router V6较多,记录一些常用的功能和API。
详细内容请自行查阅官方文档:传送门
一、路由配置
1. Router组件
它本身不做任何展示,仅提供路由模式配置,另外,该组件会产生一个上下文,上下文中会提供一些实用的对象和方法,供其他相关组件使用。
<HashRouter>
:使用hash模式匹配<BrowserRouter>
:使用BrowserHistory模式匹配
通常情况下,Router组件只有一个,包裹整个<App/>
。
//- src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { store } from "./store";
import { Provider } from "react-redux";
import { BrowserRouter as Router } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>
</React.StrictMode>
);
2. Routes组件
v6版本中移出了先前的<Switch>
,引入了新的替代者:<Routes>
。
<Routes>
的子元素只能有<Route>
组件,因为 <Routes>
组件会循环所有子元素,然后让每个子元素去完成匹配,若匹配到,则渲染对应的组件,然后停止循环。
路由是根据最佳匹配而不是按顺序选择的
嵌套的 parent route 的 path 不用加。但如果不是嵌套,而是分散在子组件中,就需要尾部加上。
推荐使用React Router V6提供的**useRoutes()
**配置 路由表 。
3. Route组件
Route 组件必须使用 Routes 嵌套。
根据不同的地址,展示不同的组件。
重要属性:
-
path:匹配的路径
1. 默认不区分大小写,可以设置**
caseSensitive
**属性,来区分大小写 2. 默认精准匹配,可以设置path路径为/a/*,来动态匹配路由
3. 如果不写path,默认匹配不到路由
4. 设置path路径为*,则匹配除已设置的路由以外的任意路径
5. 设置path路径为 / ,则匹配为整个网站的空路径。
-
index:是否是主路由,如果设置为 true 的话不能有 children
-
element:匹配成功后显示的组件,可以向组件传入props
嵌套路由
-
嵌套路由的 path 可以不用写父级,会直接拼接。
-
动态路由通过 :id 的形式实现。
-
/a/b 的匹配度大于 /a/* ,输入精确地址会精确匹配,而不是匹配到动态路由。
-
嵌套路由必须在父级追加
<Outlet/>
组件,作为子级组件的占位符。 -
除了使用
<Route>
组件作为<Routes>
组件的children的方式,还可以通过useRoutes()
生成对应的element。
路由配置流程
step1: 使用 Router组件包裹整个 <App/>
。
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { store } from "./store";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>
);
step2: 配置路由,有两种方案,使用**useRoutes()
钩子配置路由表**,或使用组件进行嵌套。推荐使用第一种。
方案一: 使用**useRoutes()
配置路由表**:
- 使用
<Suspense>
组件用来进行路由组件的懒加载,进行性能优化。
//- src/App.js
import { routes } from "./router";
import { useRoutes } from "react-router-dom";
import "./App.css";
function App() {
const element = useRoutes(routes);
return element;
}
export default App;
//- src/router/index.js
import { Suspense } from "react";
import Home from "../pages/home";
import Login from "../pages/login";
import Demo from "../pages/demo";
import BaseLayout from "../layouts/BaseLayout";
//- 懒加载优化
const lazyLoad = (children) => {
return <Suspense fallback={<h1>Loading...</h1>}>{children}</Suspense>;
};
export const routes = [
{
path: "/",
element: <BaseLayout />,
children: [
{
index: true,
element: lazyLoad(<Home />),
},
{
path: "/demo",
element: lazyLoad(<Demo />),
},
],
},
{
path: "/login",
element: lazyLoad(<Login />),
},
];
方案二: 直接使用<Routes>
组件配置路由:
//- src/App.js
import { Routes, Route } from "react-router-dom";
import "./App.css";
import BaseLayout from "./layouts/BaseLayout";
import Home from "./pages/home";
import Counter from "./pages/counter";
import Demo from "./pages/demo";
import Login from "./pages/login";
function App() {
return (
<Routes>
<Route path="/" element={<BaseLayout />}>
<Route index element={<Home />} />
<Route path="/counter" element={<Counter />} />
<Route path="/demo" element={<Demo />} />
</Route>
<Route path="/login" element={<Login />} />
</Routes>
);
}
export default App;
step3: 在Layout中添加 <Outlet/>
组件,作为子级路由的占位符。
//- src/layouts/BaseLayout.jsx
import React from "react";
import { Link, Outlet } from "react-router-dom";
const BaseLayout = () => {
return (
<div>
<main>
<Link to="/">首页</Link>
<Link to="/demo">测试页</Link>
</main>
<div>
<Outlet />
</div>
</div>
);
};
export default BaseLayout;
二、路由跳转
1. link跳转
<Link>
组件
组件属性:
- **to:**是基于父级 Route 的 path。
- 字符串:跳转的目标。
- 对象:pathname(url路径)、search(字符串类型)、hash(字符串类型)。
- **replace:**boolean,默认 false,即跳转路由要用 push 还是 replace。
- target:_self、_blank、_parent、_top。
- **state:**object,即点击后可以给 to 传对应的 state。
- **ref:**可以将内部a元素的ref附着在传递的对象或函数参数上。
- **reloadDocument:**boolean,是否重新加载文档。
<Link to="/路径">按钮</Link>
2. NavLink组件跳转
- 通常用作导航栏中的跳转。
- 默认会给当前活动的NavLink的组件加一个.active。
- 会给当前活动的NavLink传递一个isActive属性,可以在className、style、children中使用该属性。
- **caseSensitive:**boolean,是否区分大小写。
<NavLink className={({ isActive }) => isActive ? "red" : "blue"} />
3. Navigate 重定向组件
- 可以用来匹配未定义的路由。
- to: 基于父级 Route 的 path。
- 字符串:跳转的目标。
- 对象:pathname(url路径)、search(字符串类型)、hash(字符串类型)。
- **replace:**boolean,默认 false,即跳转路由要用 push 还是 replace。
- **state:**object,传递 state。
<Route path="*" element={<Navigate to="/"/>} />
4. useNavigate()跳转
useNavigate()
函数用来实现编程式导航。
函数参数:
- path:跳转的路径
naviaget("xxx")
默认就是history.push
。naviaget("xxx", { replace: true })
就是history.replace
。naviaget(to: number)
就是history.go
。- 如果携带params参数或search参数。
- option:配置对象。
replace:
boolean,相当于history.replace
。state:
object,用于传递信息。
import React from 'react'
import {useNavigate} from 'react-router-dom'
export default function Demo() {
const navigate = useNavigate()
const handleTo = () => {
navigate('/login', {
replace: false,
state: {a:1, b:2}
})
}
return (
<div>
<button onClick={handleTo}>跳转</button>
</div>
)
三、获取参数
1. 获取state数据
useLocation()
获取state数据。
import { useLocation } from "react-router-dom";
let location = useLocation()
let data=location.state
2. 获取地址栏数据
useParams()
获取动态路由的值。useSearchParams()
获取查询字符串的值。
// 获取动态路由的值
const params = useParams();
const data=params.参数名
// 获取查询字符串的值
const [searchParams, setSearchParams] = useSearchParams();
const data=searchParams.get('参数名')
// 修改查询字符串的值
setSearchParams('a=1&b=2&c=3')
3. 解析Url中的参数值
useResolvedPath()
给定一个 URL,解析其中的path
、search
、hash
值。
console.log('useResolvedPath',useResolvedPath('/user?id=001&name=tom#qws'))
//输出:{pathname: "/user", search: "?id=001&name=tom", hash: "#qws"}
四、导航守卫
应用场景:当进入首页或任意页面时,先判断是否已经登录,如果未登录,会自动重定向到登录页。
实现原理:创建一个高阶组件,将原来的组件包裹,通过高阶组件进行判断,如果条件成立返回children,否则进行重定向。
//- src/router/AuthProvider.jsx
import { useSelector } from 'react-redux';
import { Navigate, useLocation } from 'react-router-dom';
const AuthProvider = ({ children }) => {
const location = useLocation();
const isLogin = useSelector(state => state.user.isLogin);
const userInfo = useSelector(state => state.user.userInfo);
// 如果是登录页面,直接跳转
if (location.pathname === '/login' || location.pathname === '/register') {
return children;
};
// 如果登录信息有问题,重新登录。否则直接跳转
if (!isLogin || !userInfo) {
return <Navigate to="/login" />;
} else {
return children;
}
};
export default AuthProvider;
//- src/router/index.jsx
import { Navigate, useRoutes } from 'react-router-dom';
import Home from '../pages/Home';
import AddOrEdit from '../pages/AddOrEdit';
import Detail from '../pages/Detail';
import About from '../pages/About';
import Email from '../components/Email';
import Tel from '../components/Tel';
import Login from '../pages/Login';
import BaseLayout from '../components/BaseLayout';
import AuthProvider from './AuthProvider';
export default function RouterConfig() {
const routes = useRoutes([
{
path: '/',
element: <BaseLayout />,
children: [
{
path: '/home',
element: <Home />
}, {
path: '/add',
element: <AddOrEdit />
}, {
path: '/edit/:id',
element: <AddOrEdit />
}, {
path: '/detail/:id',
element: <Detail />
}, {
path: '/about',
element: <About />
}
]
}, {
path: '/login',
element: <Login />
}, {
path: '*',
element: <Navigate to='/home' replace />
}
]);
return <AuthProvider>{routes}</AuthProvider>;
}
五、路由过渡动画
实现思路:
- 用
<TransitionGroup/>
组件将<Routes/>
组件包裹,因为<Routes/>
组件下只能包含<Route/>
组件 - 创建一个
<TransitionRoute/>
的高阶组件,将要加入动画的组件包裹,传入in、key属性,以供内部的<CSSTransition/>
组件使用。 - 内容使用
<CSSTransition/>
组件包裹props.children
,通过自定义css文件或者引入animate.css
库,添加动态效果。 - className使用对象的方式添加:
classNames={{ enter: "animate__fadeIn" }}
。
//- App.js
import React from "react";
import { Routes, Route, Navigate } from "react-router-dom";
import StudentList from "./pages/student/StudentList";
import StudentAdd from "./pages/student/StudentAdd";
import TransitionRoute from "./TransitionRoute";
import { useLocation } from "react-router-dom";
import { TransitionGroup } from "react-transition-group";
function App() {
const location = useLocation();
return (
<TransitionGroup>
<Route
path="/student/list"
element={
<TransitionRoute keyText={location.key} in={location.pathname==="/student/list"}>
<StudentList />
</TransitionRoute>
}
/>
<Route
path="/student/add"
element={
<TransitionRoute keyText={location.key} in={location.pathname==="/student/add"}>
<StudentAdd />
</TransitionRoute>
}
/>
</Route>
</Routes>
</TransitionGroup>
);
}
export default App;
//- src/TransitionRoute.js
import React from "react";
import { CSSTransition } from "react-transition-group";
import "animate.css";
// import './TransitionRoute.css'
/**
* 传入两个属性
* key:csstransition组件的key值
* in:是否改变动画状态
* timeout:多长时间后,去掉之前的样式,添加样式enter-done/exit-done,默认0.8秒
*/
export default function TransitionRoute(props) {
return (
<CSSTransition
key={props.keyText}
in={props.in}
timeout={props.timeout}
classNames={{ enter: "animate__bounceIn",exit:"animate__bounceIn" }}
>
{props.children}
</CSSTransition>
);
}
TransitionRoute.defaultProps={
timeout:800
}
六、滚动条复位
使用场景:当用户切换路由时,滚动条默认不会变化,需要我们手动将滚动条恢复至顶部。
实现方法:
step1: 首先封装一个滚动回顶部的函数resetScroll()
。
resetScroll()
函数实现思路:
- 创建一个
animate()
函数,接受动画开始位置start
,结束位置end
,和要执行的回调函数callback
,返回一个定时器。 - 调用两个
animate()
函数,传入scrollTop
和scrollLeft
等参数。
let timer1, timer2;
/**
* 滚动条横向和纵向动画复位
*/
export default function resetScroll() {
clearInterval(timer1);
clearInterval(timer2);
var html = document.documentElement;
timer1 = animate(html.scrollTop, 0, (val) => {
html.scrollTop = val;
})
timer2 = animate(html.scrollLeft, 0, (val) => {
html.scrollLeft = val;
})
}
/**
* 在300毫秒之内,从指定的初始值,变化到结束值
* @param {*} start
* @param {*} end
*/
function animate(start, end, callback) {
var tick = 16; //每隔16毫秒完成一次变化
var total = 300; //总时间为1000毫秒
var times = Math.ceil(total / tick); //变化的次数
var curTimes = 0;
var dis = (end - start) / times; //总距离/次数,每次运动的距离
var timer = setInterval(() => {
curTimes++;
start += dis;
if (curTimes === times) {
start = end;
clearInterval(timer);
}
callback(start);
}, tick)
return timer;
}
**step2:**通过各种方式使用resetScroll()
函数
- 高阶组件
//- withScroll.js:
import React from "react"
import reset from "./resetScroll"
export default function withScroll(Comp) {
return class ScrollWrapper extends React.Component {
componentDidMount() {
reset();
}
render() {
return <Comp {...this.props} />
}
}
}
使用:
const PageWithScroll = withScroll(Page1)
- 自定义Hook
//- useScroll.js
import { useEffect } from "react"
import reset from "./resetScroll"
export default function useScroll(pathname) {
useEffect(reset, [pathname])
}
使用:
import useScroll from "./useScroll"
useScroll(props.location.pathname);//调用时滑动
- 导航守卫判断
实现思路:在当前页面中传入location.pathname
给跳转后的页面,在跳转后的页面判断两个path
是否相同,不同则调用resetScroll
。
// ResetScrollRoute.jsx:
import React from 'react'
import { useLocation } from "react-router-dom";
import reset from "./resetScroll"
export default function ResetScrollRoute({oldPathname}) {
const location = useLocation();
const nowPathname = location.pathname;
if(oldPathname!==nowPathname){
reset()
}
return props.children;
}
总结
以上就是个人总结的一些基本用法,上述仅为个人理解,如果有错误,还请路过的大佬斧正!