React路由该如何使用?react-router-dom详解

闾丘京
2023-12-01

一、基本使用

  1. 安装依赖
    npm i react-router-dom
    
  2. 引入实现路由所需要的组件及组件页面
    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)

区别:

HashRouterBrowseRouter
只会修改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组件的styleclassName可以接收一个函数,函数接收一个含有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:决定路由匹配规则

  1. 路径参数

    • 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>
      );
    }
    
  2. 路径匹配规则

    当URL同时匹配到含有路径参数的路径和无参数路径时,有限匹配没有参数的”具体的“(specific)路径。

    <Route path="teams/:teamId" element={<Team />} />
    <Route path="teams/new" element={<NewTeamForm />} />
    

    如上的两个路径,将会匹配 teams/new

  3. 兼容类组件

    在以前版本中,组件的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" />

八、布局路由

当多个路由有共同的父级组件时,可以将父组件提取为一个没有 pathindex属性的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-routerwindow.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;
}
 类似资料: