REACT:react-router-dom详解
文章目录
- 一、基本使用
- 二、组件分类
- 1. 路由器组件 - BrowserRouter & HashRouter
- HashRouter:hash模式
- BrowserRouter:history模式
- 区别:
- 2. 导航组件 - Link & NavLink
- Link:
- NavLink:一个更特殊的 `Link` 组件,可以用于指定当前导航高亮
- 编程式跳转 - `useNavigate`
- 3. 路由匹配组件 - `Route` `Switch` (v6删除Switch,新增Routes)
- Route:决定路由匹配规则
- search参数
- Switch/Routes:包裹多个Route组件
- 三、嵌套路由
- 1. 路由定义
- 2. 在父组件中展示
- 3. 在组件中定义
- 四、默认路由
- 五、全匹配路由
- 六、多组路由
- 七、路由重定向
- 八、布局路由
- 九、订阅和操作 history stack的原理
- 1. History对象
- 2. Location对象
- state
- key
- 十、使用JS对象定义路由:useRoutes
一、基本使用
- 安装依赖
npm i react-router-dom
- 引入实现路由所需要的组件及组件页面
import { BrowserRouter, Routes, Route } from "react-router-dom"; import Page1 from "./Page1"; import Page2 from "./Page2"; function App() { return ( <BrowserRouter> <Routes> <Route path="/page1" element={<Page1 />} /> <Route path="/page2" element={<Page2 />} /> </Routes> </BrowserRouter> ); }
path
:跳转路径element
:渲染组件
BrowserRouter
组件最好放在最顶层所有组件之外,这样能确保内部组件使用 Link 做路由跳转时不出错
二、组件分类
1. 路由器组件 - BrowserRouter & HashRouter
路由组件包裹整个应用,一个React应用只需要使用一次。
HashRouter:hash模式
使用URL的hash
实现。
原理:监听window的hashchange
事件来实现。(http://localhost:3000/#/frist)
BrowserRouter:history模式
使用H5的history.pushState()
API实现。
原理:监听window的popstate
事件来实现。(http://localhost:3000/first)
区别:
HashRouter | BrowseRouter |
---|---|
只会修改URL的哈希值部分 | 修改URL本身 |
纯前端路由,可以通过输入URL直接访问 | 直接输入URL访问会报404 错误,需要Nginx将请求指向对应的HTML文件。初次进入/ 路径或点击Link 组件跳转时不会发送请求 |
ES6规范
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
import { HashRouter as Router, Route, Link } from 'react-router-dom'
//使用es6的导入重命名来统一名字: 无论导入的是哪个路由对象,都叫Router
<Router>
2. 导航组件 - Link & NavLink
在跳转路由时,如果路径是
/
开头的则是绝对路由,否则为相对路由,即相对于当前 URL进行改变。
Link
或者 NavLink
最终会渲染成<a></a>
标签,用于指定路由导航。
Link:
Link
组件只能在Router
内部使用,因此使用到Link
的组件一定要放在顶层的Router
之内。- 属性:
to
属性,将来会渲染成a
标签的href
属性Link
组件无法展示哪个link
处于选中的效果
- 用法:
import { Link } from "react-router-dom"; <Link to="page1">to page1</Link>;
NavLink:一个更特殊的 Link
组件,可以用于指定当前导航高亮
NavLink
组件和Link
组件的功能是一致的,区别在于可以判断其to
属性是否是当前匹配到的路由。NavLink
组件的style
或className
可以接收一个函数,函数接收一个含有isActive
字段的对象为参数,可根据该参数调整样式- 属性:
to
属性,用于指定地址,会渲染成a
标签的href
属性activeClassName
: 用于指定高亮的类名,默认active
。一般不去修改。exact
: 精确匹配,表示必须地址栏和to
的属性值 精确匹配类名才生效
- 用法:
import { NavLink } from "react-router-dom"; function Page1() { return ( <NavLink style={({ isActive }) => ({ color: isActive ? "red" : "#fff" })}> Click here </NavLink> ); }
编程式跳转 - useNavigate
使用useNavigate
钩子函数生成navigate
函数,可以通过 JS 代码完成路由跳转
useNavigate
取代了原先版本中的useHistory
。
import { useNavigate } from 'react-router-dom';
function Page1(){
const navigate = useNavigate();
return (
// 上一个路径:/a; 当前路径: /a/a1
<div onClick={() => navigate('/b')}>跳转到/b</div> // 绝对路径
<div onClick={() => navigate('a11')}>跳转到/a/a1/a11</div> // 相对路径
<div onClick={() => navigate('../a2')}>跳转到/a/a2</div> // 相对路径
<div onClick={() => navigate(-1)}>跳转到/a</div> // 后退
)
}
- 可以直接传入要跳转的目标路由(可以使用相对路径,语法和 JS 相同)
- 传入
-1
表示后退
3. 路由匹配组件 - Route
Switch
(v6删除Switch,新增Routes)
Route:决定路由匹配规则
-
路径参数
- 在
Route
组件中的path
属性中定义路径参数 - 在组件内通过
useParams
hook 访问路径参数
<BrowserRouter> <Routes> <Route path="/page1/:id" element={<Page1 />} /> </Routes> </BrowserRouter>; import { useParams } from "react-router-dom"; export default function Page1() { const params = useParams(); return ( <div> <h1>{params.id}</h1> </div> ); }
- 在
-
路径匹配规则
当URL同时匹配到含有路径参数的路径和无参数路径时,有限匹配没有参数的”具体的“(specific)路径。
<Route path="teams/:teamId" element={<Team />} /> <Route path="teams/new" element={<NewTeamForm />} />
如上的两个路径,将会匹配
teams/new
。 -
兼容类组件
在以前版本中,组件的
props
会包含一个match
对象,在其中可以取到路径参数。但在最新的 6.x 版本中,无法从
props
获取参数。并且,针对类组件的
withRouter
高阶组件已被移除。因此对于类组件来说,使用参数有两种兼容方法:
- 将类组件改写为函数组件
- 自己写一个 HOC 来包裹类组件,用useParams
获取参数后通过props
传入原本的类组件
search参数
- 查询参数不需要在路由中定义
- 使用
useSearchParams
hook 来访问和修改查询参数。其用法和useState
类似,会返回当前对象和更改它的方法 - 使用
setSearchParams
时,必须传入所有的查询参数,否则会覆盖已有参数
import { useSearchParams } from "react-router-dom";
// 当前路径为 /page1?id=12
function Page1() {
const [searchParams, setSearchParams] = useSearchParams();
console.log(searchParams.get("id")); // 12
setSearchParams({
name: "page1",
}); // /page1?name=page1
return <div>page1</div>;
}
Switch/Routes:包裹多个Route组件
在Switch组件下,不管有多少个Route的路由规则匹配成功,都只会渲染第一个匹配的组件
三、嵌套路由
1. 路由定义
通过嵌套的书写Route
组件实现对嵌套路由的定义。
path
开头为/
的为绝对路径,反之为相对路径。
<Routes>
<Route path="/" element={<Home />}></Route>
<Route path="/father" element={<Father />}>
<Route path="child" element={<Child />}></Route>
<Route path=":name" element={<Another />}></Route>
</Route>
</Routes>
2. 在父组件中展示
在父组件中使用Outlet
来显示匹配到的子组件。
import { Outlet } from "react-router-dom";
function Father() {
return (
<div>
// ... 自己组件的内容 // 留给子组件Child的出口
<Outlet />
</div>
);
}
3. 在组件中定义
可以在任何组件中使用 Routes
组件,且组件内的Routes
中,路径默认带上当前组件的路径作为前缀。
注意:此时定义父组件的路由时,要在后面加上 /*
,否则父组件将无法渲染。
<Routes>
<Route path="/" element={<Home />} />
<Route path="dashboard/*" element={<Dashboard />} />
</Routes>
function Dashboard() {
return (
<div>
<p>Look, more routes!</p>
<Routes>
<Route path="/" element={<DashboardGraphs />} />
<Route path="invoices" element={<InvoiceList />} />
</Routes>
</div>
);
}
四、默认路由
定义:在嵌套路由中,如果 URL 仅匹配了父级 URL,则Outlet
中会显示带有index
属性的子路由。可以使用在路由的任何层级。
<Routes>
<Route path="/foo" element={Foo}>
<Route index element={Default}></Route>
<Route path="bar" element={Bar}></Route>
</Route>
</Routes>
- 当 url 为
/foo
时:Foo 中的 Outlet 会显示 Default 组件 - 当 url 为
/foo/bar
时:Foo 中的 Outlet 会显示为 Bar 组件
五、全匹配路由
定义: path
属性取值为*
时,可以匹配任何(非空)路径,该匹配拥有最低的优先级。可以用于设置 404 页面。
<Routes>
<Route path="/foo" element={Foo}>
<Route path="bar" element={Bar}></Route>
<Route path="*" element={NotFound}></Route>
</Route>
</Routes>
六、多组路由
通常,一个应用中只有一个Routes组件。
但根据实际需要也可以定义多个路由出口(如:侧边栏和主页面都要随 URL 而变化)
<Router>
<SideBar>
<Routes>
<Route></Route>
</Routes>
</SideBar>
<Main>
<Routes>
<Route></Route>
</Routes>
</Main>
</Router>
七、路由重定向
当在某个路径/a
下,要重定向到路径/b
时,可以通过Navigate
组件进行重定向到其他路径
import { Navigate } from "react-router-dom";
function A() {
return <Navigate to="/b" />;
}
等价于以前版本中的Redirect
组件
<Redirect from="/a" to="/b" />
八、布局路由
当多个路由有共同的父级组件时,可以将父组件提取为一个没有 path
和index
属性的Route组件(Layout Route)
<Route element={<PageLayout />}>
<Route path="/privacy" element={<Privacy />} />
<Route path="/tos" element={<Tos />} />
</Route>
这种写法等价于:
<Route path="/privacy"
element={
<PageLayout>
<Privacy />
</PageLayout>
}
/>
<Route path="/tos"
element={
<PageLayout>
<Tos />
</PageLayout>
}
/>
九、订阅和操作 history stack的原理
浏览器会记录导航堆栈,以实现浏览器中的前进后退功能。在传统的前端项目中,URL的改变意味着向服务器重新请求数据。
在现在的客户端路由( client side routing )中,可以做到编程控制URL改变后的反应。如在点击a标签的回调函数中使用 event.preventDefault()
阻止默认事件,此时URL的改变不会带来任何UI上的更新。
<a
href="/contact"
onClick={(event) => {
// stop the browser from changing the URL and requesting the new document
event.preventDefault();
// push an entry into the browser history stack and change the URL
window.history.pushState({}, undefined, "/contact");
}}
/>
1. History对象
浏览器没有直接提供监听URL改变(push、pop、replace)的接口,因此 react-router
对原生的 history
对线进行了包装,提供了监听URL改变的API。
let history = createBrowserHistory();
history.listen(({ location, action }) => {
// this is called whenever new locations come in
// the action is POP, PUSH, or REPLACE
});
使用 react-router
时不需操作History对象(Routes
组件会进行操作)
2. Location对象
react-router
对 window.location
进行包装后,提供了一个形式简洁的Location对象,形如:
{
pathname: "/bbq/pig-pickins", // 主机名之后的URL地址
search: "?campaign=instagram", // 查询参数
hash: "#menu", // 哈希值,用于确定页面滚动的具体位置
state: null, // 对于 window.history.state 的包装
key: "aefz24ie" //
}
state
不显示在页面上,不会引起刷新,只由开发人员操作。
可用于记录用户的跳转详情(从哪跳到当前页面)或在跳转时携带信息。
可以用在 Link
组件或 navigate
方法中
<Link to="/pins/123" state={{ fromDashboard: true }} />
let navigate = useNavigate();
navigate("/users/123", { state: partialUser });
在目标的组件中,可以用 useLocation
方法获取该对象
let location = useLocation();
console.log(location.state);
state中的信息会进行序列化,因此如日期对象等信息会变为string
key
每个Location对象拥有一个唯一的key,可以据此来实现基于Location的滚动管理,或是数据缓存。
如:将 location.key
和 URL 作为键,每次请求数据前,先查找缓存是否存在来判断是否实际发送请求,来实现客户端数据缓存。
十、使用JS对象定义路由:useRoutes
使用 useRoutes hook
,可以使用一个JS对象而不是Routes组件与Route组件来定义路由。其功能类似于react-router-config
useRoutes
的返回是 React Element,或是 null。
对于传入的配置对象, 其类型定义如下:
interface RouteObject {
caseSensitive?: boolean;
children?: RouteObject[];
element?: React.ReactNode;
index?: boolean;
path?: string;
}