Skip to content

ReactRouter6

React Router 6 快速上手

安装:

bash
npm install react-router-dom@6

1.概述

  1. React Router 以三个不同的包发布到 npm 上,它们分别为:

    1. react-router: 路由的核心库,提供了很多的:组件、钩子。
    2. react-router-dom: 包含 react-router 所有内容,并添加一些专门用于 DOM 的组件,例如 <BrowserRouter>
    3. react-router-native: 包括 react-router 所有内容,并添加一些专门用于 ReactNative 的 API,例如:<NativeRouter>等。
  2. 与 React Router 5.x 版本相比,改变了什么?

    1. 内置组件的变化:移除<Switch/> ,新增 <Routes/>等。

    2. 语法的变化:component={About} 变为 element={<About/>}等。

    3. 新增多个 hook:useParamsuseNavigateuseMatch等。

    4. 官方明确推荐函数式组件了!!!

      ......

2.Component

1. <BrowserRouter>

history API

  1. 说明:<BrowserRouter> 用于包裹整个应用。

  2. 示例代码:

    jsx
    import React from "react";
    import ReactDOM from "react-dom";
    import { BrowserRouter } from "react-router-dom";
    
    ReactDOM.render(
      <BrowserRouter>{/* 整体结构(通常为App组件) */}</BrowserRouter>,
      root
    );

BrowserRouter 潜在问题,即造成 404 的情况说明 ——> 主要的是针对生产环境,并把 html 文件放到了后端的情况下(其他的大多数情况都不会出现任何的问题)

如果在使用 React Router 中的 BrowserRouter 时出现 404 错误,可能有以下几个原因和解决方法:

  1. 服务器配置:BrowserRouter 使用了 HTML5 History API,它需要服务器正确地配置以处理路由请求。在开发环境中,通常由 webpack-dev-server 等工具自动处理。

    但在生产环境中,你需要确保服务器能够正确地处理所有路由请求并返回你的应用程序的主要 HTML 文件。(当把 html 放到了后端的公共资源目录下的时候)这可以通过在服务器上设置通配符路由来实现。

    例如,在使用 Express.js 作为服务器框架的情况下,你可以添加以下路由配置来处理所有的路由请求:

    js
    app.get("*", (req, res) => {
      res.sendFile(path.resolve(__dirname, "path/to/your/index.html"));
    });

    这将确保所有路由请求都返回你的应用程序的主要 HTML 文件。

  2. basepath 设置:如果你在 BrowserRouter 中设置了 basepath 属性,你需要确保服务器配置中也包含相应的 basepath。

    例如,如果你在 BrowserRouter 中设置了 <BrowserRouter basename="/myapp">,那么服务器配置中的路由处理也应该包含 /myapp

  3. 404 页面的处理:如果服务器无法找到匹配的路由路径,它可能会返回 404 错误页面。你可以为你的应用程序添加一个自定义的 404 页面,以提供更好的用户体验。

    在你的应用程序中,可以添加一个 Route 组件来处理 404 页面:

    jsx
    import { Route, Switch } from "react-router-dom";
    
    // ...
    
    <Switch>
      {/* 其他路由 */}
      <Route component={NotFound} />
    </Switch>;

    在上述示例中,NotFound 组件将作为默认的 404 页面组件。

通过确保服务器正确配置和处理所有路由请求,以及设置正确的 basepath,你应该能够解决 BrowserRouter 导致的 404 错误。如果问题仍然存在,请提供更多详细信息,例如服务器和路由配置的代码片段,以便我可以提供更具体的帮助。

造成 404 的本质原因?

BrowserRouter 在 React Router 中是一种用于处理前端路由的组件。它使用 HTML5 History API 来实现无刷新的页面导航,从而创建单页应用程序(SPA)。

当你使用 BrowserRouter 时,它会使用浏览器的 history.pushState 方法将路由路径添加到浏览器的历史记录中,同时更新 URL 地址栏的内容。这允许你在应用程序中使用自定义的路由路径,而不会触发完整的页面刷新。

然而,BrowserRouter 的工作原理也导致了潜在的 404 问题。默认情况下,**当用户在浏览器中直接输入或刷新一个由 BrowserRouter 管理的路由路径时,例如 http://example.com/myroute,浏览器会向服务器(这里)发出请求(browser 和 hash 模式都会发请求),**但服务器并不知道如何处理该路由路径,因此返回 404 错误。

这是因为在使用 BrowserRouter 时,所有路由路径都在前端处理,而服务器只负责返回应用程序的主要 HTML 文件。服务器并不知道如何处理特定的路由路径,因此返回 404 错误。

为什么 HashRouter 不会有问题?

HashRouter 使用 URL 中的哈希部分(即 # 后面的内容)来表示路由路径。例如,http://example.com/#/myroute。哈希部分不会被发送到服务器,因此对于后端服务器来说,无论哈希部分是什么,它都会返回相同的 HTML 文件(因为请求地址都是/)。

这意味着当使用 HashRouter 时,后端服务器无需进行特殊的配置来处理路由请求。它只需要返回前端应用程序的主要 HTML 文件,而不会根据哈希部分返回不同的响应。

如果 html 没有放到后端,而是部署在 nginx 服务器上呢?

如果你将 index.html 文件放在 Nginx 中而不是后端的公共目录,你需要配置 Nginx 以正确处理前端应用程序的路由。

注意:当我们把 html 放到了前端服务器 nginx 上的时候,浏览器就不需要直接请求后端,后端也就不需要处理前端的请求,不用配置防止 404,只需要在 nginx 里面防止 404 即可。

你可以在 Nginx 配置中使用 try_files 指令,将所有路由请求都重定向到 index.html 文件。这样,Nginx 将返回 index.html 文件,并由前端应用程序来处理路由渲染。

以下是一个示例 Nginx 配置文件的部分内容,展示了如何配置路由重定向:

nginx
server {
  listen 80;
  server_name example.com;

  root /path/to/your/frontend;  # 前端应用程序的根目录

  location / {
    try_files $uri $uri/ /index.html;
  }
}

在上述示例中,try_files $uri $uri/ /index.html; 配置了一个通配符路由,将所有路由请求都重定向到 index.html 文件。

总结

在生产实践上:

(1)HashRouter

HashRouter 相当于锚点定位,因此不论#后面的路径怎么变化,请求的都相当于是#之前的那个页面。可以很容易的进行前后端不分离的部署(也就是把前端打包后的文件放到服务器端的 public 或 static 里),因为请求的链接都是 ip 地址:端口/#/xxxx,因此请求的资源路径永远为/,相当于 index.html,而其他的后端 API 接口都可以正常请求,不会和/冲突,由于前后端不分离也不会产生跨域问题。 缺点就是丑,路径里总有个#

(2)BrowserRouter

因为 BrowserRouter 模式下请求的链接都是 ip 地址:端口/xxxx/xxxx,因此相当于每个 URL 都会访问一个不同的后端地址,如果后端没有覆盖到路由就会产生 404 错误。

可以通过加入中间件解决,放在服务器端路由匹配的最后,如果前面的 API 接口都不匹配,则返回 index.html 页面。但这样也有一些小问题,因为要求前端路由和后端路由的 URL 不能重复。

比如商品列表组件叫/product/list,而请求商品列表的 API 也是/product/list,那么就会访问不到页面,而是被 API 接口匹配到。

(3)完美解决方法:

进行前后端分离的部署,比如前端地址 ip1:端口 1,后端接口地址 ip2:端口 2,使用 Nginx 反向代理服务器进行请求分发。

前端向后端发起请求的 URL 为 nginx 所在的服务器+/api/xxx,通过 NGINX 的配置文件判断,如果 URL 以 api 开头则转发至后端接口,否则转发至前端的地址,访问项目只需访问 Nginx 服务器即可。

2.<HashRouter>

URL 的哈希值

  1. 说明:作用与<BrowserRouter>一样,但<HashRouter>修改的是地址栏的 hash 值。
  2. 备注:6.x 版本中<HashRouter><BrowserRouter> 的用法与 5.x 相同。

3. <Routes/> 与 <Route/>

  1. v6 版本中移出了先前的<Switch>,引入了新的替代者:<Routes>

  2. <Routes><Route>要配合使用,且必须要用<Routes>包裹<Route>

  3. <Route> 相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。——> 功能和 switch 一样

  4. <Route caseSensitive> 属性用于指定:匹配时是否区分大小写(默认为 false)。

  5. 当 URL 发生变化时,<Routes> 都会查看其所有子 <Route> 元素以找到最佳匹配并呈现组件 。

  6. <Route> 也可以嵌套使用,且可配合useRoutes()配置 “路由表” ,但需要通过 <Outlet> 组件来渲染其子路由。

  7. 示例代码:

    jsx
    <Routes>
      /* path属性用于定义路径,element属性用于定义当前路径所对应的组件 */
      <Route path='/login' element={<Login />}></Route>
      /* 用于定义嵌套路由,home是一级路由,对应的路径/home */
      <Route path='home' element={<Home />}>
        /* test1 和 test2 是二级路由, 对应的路径是/home/test1 或 /home/test2 */
        <Route path='test1' element={<Test />}></Route>
        <Route path='test2' element={<Test2 />}></Route>
      </Route>
      //Route也可以不写element属性, Demo组件对应的路径是/users/xxx
      <Route path='users'>
        <Route path='xxx' element={<Demo />} />
      </Route>
    </Routes>

path 写/和不写/的区别:

  • / 开头的 path 属性表示绝对路径,相对于根路径。
  • 不以 / 开头的 path 属性表示相对路径,相对于父级路由路径。
    • 在不以 / 开头的情况下,path 属性的值实际上是在父级路径后面追加的路径片段。
  1. 作用: 修改 URL,且不发送网络请求(路由链接)。

  2. 注意: 外侧需要用<BrowserRouter><HashRouter>包裹。

  3. 示例代码:

    jsx
    import { Link } from "react-router-dom";
    
    function Test() {
      return (
        <div>
          <Link to='/路径'>按钮</Link>
        </div>
      );
    }
  1. 作用: 与<Link>组件类似,且可实现导航的“高亮”效果。

  2. 示例代码:

    jsx
    // 注意: NavLink默认类名是active,下面是指定自定义的class
    
    //自定义样式
    <NavLink
        to="login"
        className={({ isActive }) => {
            console.log('home', isActive)
            return isActive ? 'base one' : 'base'
        }}
    >login</NavLink>
    
    /*
    	默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,
    	当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
    */
    <NavLink to="home" end >home</NavLink>

6. <Navigate>

  1. 作用:只要<Navigate>组件被渲染,就会修改路径,切换视图。——> 相当于 编程式路由导航

  2. replace属性用于控制跳转模式(push 或 replace,默认是 push)。

  3. 示例代码:

    jsx
    import React, { useState } from "react";
    import { Navigate } from "react-router-dom";
    
    export default function Home() {
      const [sum, setSum] = useState(1);
      return (
        <div>
          <h3>我是Home的内容</h3>
          {/* 根据sum的值决定是否切换视图:在 router5 中需要用 redirect 来实现 */}
          {sum === 1 ? (
            <h4>sum的值为{sum}</h4>
          ) : (
            <Navigate to='/about' replace={true} />
          )}
          <button onClick={() => setSum(2)}>点我将sum变为2</button>
        </div>
      );
    }

Navigate 和 router5 中的 Redirect 的区别:

Navigate 本身就是用来代替 Redirect 的!

  • Redirect 用于在路由渲染期间进行页面重定向,而 Navigate 则是用于编程式导航的钩子函数。
  • Redirect 会进行页面的完全重定向和加载,而 Navigate 则可以在不刷新整个页面的情况下进行导航(只是更改路由而已)。
  • Redirect 在 React Router v5 中是一个组件,在 React Router v6 中是一个重定向操作的函数。
  • Navigate 是 React Router v6 中用于编程式导航的钩子函数,通过调用 navigate 函数实现导航操作。

注意:

jsx
{
  /*router@5 对于首页的重定向兜底写法:*/
}
<Redirect from='/' to='/firm' />;
{
  /*router@6 的写法:因为 Navigate 没有 from 属性,是直接的跳转过去*/
}
<Route path='/' element={<Navigate to='/firm' />}></Route>;

匹配任何路径:

jsx
<Route path='*' element={<Navigate to='/firm' />}></Route>

7. <Outlet>

  1. <Route>产生嵌套时,渲染其对应的后续子路由。——> 相当于 router-view 标签,需要和 useRoutes 配合一起使用

  2. 示例代码:

    jsx
    //App.jsx:
    //根据路由表生成对应的路由规则 ——> 类似 vue-router 的配置路由表
    const element = useRoutes([
      {
        path: "/about",
        element: <About />,
      },
      {
        path: "/home",
        element: <Home />,
        children: [
          {
            path: "news", //不能加/
            element: <News />,
          },
          {
            path: "message",
            element: <Message />,
          },
        ],
      },
    ]);
    
    //Home.jsx:
    import React from "react";
    import { NavLink, Outlet } from "react-router-dom";
    
    export default function Home() {
      return (
        <div>
          <h2>Home组件内容</h2> {/* 此时已经在/home这个路径下了 */}
          <div>
            <ul className='nav nav-tabs'>
              <li>
                <NavLink className='list-group-item' to='news'>
                  News
                </NavLink>
              </li>
              <li>
                <NavLink className='list-group-item' to='message'>
                  Message
                </NavLink>
              </li>
            </ul>
            {/* 指定路由组件呈现的位置 */}
            <Outlet />
          </div>
        </div>
      );
    }

3.Hooks

1. useRoutes()

  1. 作用:根据路由表,动态创建<Routes><Route>。在组件中使用 Outlet 标签以展示子组件。

  2. 示例代码:

    jsx
    //路由表配置:src/routes/index.js
    import About from '../pages/About'
    import Home from '../pages/Home'
    import {Navigate} from 'react-router-dom'
    
    export default [
    	{
    		path:'/about',
    		element:<About/>
    	},
    	{
    		path:'/home',
    		element:<Home/>,
        children:[
          {
            path:'news', //不能加/
            element:<News/>
          },
          {
            path:'message',
            element:<Message/>,
          }
        ]
    	},
    	{
    		path:'/',
    		element:<Navigate to="/about"/> //重定向到/about,相当于 redirect 标签的功能
    	}
    ]
    
    //App.jsx
    import React from 'react'
    import {NavLink,useRoutes} from 'react-router-dom'
    import routes from './routes'
    
    export default function App() {
    	//根据路由表生成对应的路由规则
    	const element = useRoutes(routes)
      //或者:
      // const SomeComponent = () => {
      	// const element = useRoutes(routes);
      	// return element;
    	// };
    
    	return (
    		<div>
    			......
          {/* 注册路由,这块有点像 router-view */}
          {element}
          {/* <SomeComponent/> */}
    		  ......
    		</div>
    	)
    }

注意:useRoutes() 钩子函数不仅可以在 App 组件中使用,还可以在任何组件中使用,只要这些组件在 React Router 的上下文中。

React Router 的上下文是通过 BrowserRouterHashRouter 或其他支持路由上下文的组件提供的。只要你的组件在这些组件的子组件树中,就可以使用 useRoutes() 钩子函数。

2. useNavigate() —>push/replace

  1. 作用:返回一个函数用来实现编程式导航。——> 代替了 5.x 中 history 属性中的 push 和 replace 方法

  2. 示例代码:

    jsx
    import React from "react";
    import { useNavigate } from "react-router-dom";
    
    export default function Demo() {
      const navigate = useNavigate();
      const handle = () => {
        //第一种使用方式:指定具体的路径
        navigate("/login", {
          replace: false,
          state: { a: 1, b: 2 },
        });
        //第二种使用方式:传入数值进行前进或后退,类似于5.x中的 history.go()方法
        navigate(-1);
      };
    
      return (
        <div>
          <button onClick={handle}>按钮</button>
        </div>
      );
    }

3. useParams()

  1. 作用:取到当前匹配路由的params参数,类似于 5.x 中的match.params.参数

  2. 示例代码:

    jsx
    import React from "react";
    import { Routes, Route, useParams } from "react-router-dom";
    import User from "./pages/User.jsx";
    
    function ProfilePage() {
      // 获取URL中携带过来的params参数
      let { id } = useParams();
    }
    
    function App() {
      return (
        <Routes>
          <Route path='users/:id' element={<User />} />
        </Routes>
      );
    }

4. useSearchParams()

  1. 作用:用于读取和修改当前位置的 URL 中的查询字符串,类似于 5.x 中的location.search.参数(但是避免了繁杂的自己切分的方式)。

  2. 返回一个包含两个值的数组,内容分别为:当前的 seaech 参数、更新 search 的函数。

  3. 示例代码:

    jsx
    import React from "react";
    import { useSearchParams } from "react-router-dom";
    
    export default function Detail() {
      const [search, setSearch] = useSearchParams(); //search 的内容为 a&b&c 的形式
      const id = search.get("id");
      const title = search.get("title");
      const content = search.get("content");
      return (
        <ul>
          <li>
            <button onClick={() => setSearch("id=008&title=哈哈&content=嘻嘻")}>
              点我更新一下收到的search参数
            </button>
          </li>
          <li>消息编号:{id}</li>
          <li>消息标题:{title}</li>
          <li>消息内容:{content}</li>
        </ul>
      );
    }

5. useLocation()

  1. 作用:获取当前 location 信息,对标 5.x 中的路由组件的location属性。——> 这个方法可以获取 state 数据,state 没有专门的 API 来直接获取

  2. 示例代码:

    jsx
    import React from "react";
    import { useLocation } from "react-router-dom";
    
    export default function Detail() {
      const x = useLocation();
      console.log("@", x);
      // x就是location对象内容:
      /*
    		{
          hash: "",
          key: "ah9nv6sz",
          pathname: "/login",
          search: "?name=zs&age=18",
          state: {a: 1, b: 2}
        }
    	*/
      return (
        <ul>
          <li>消息编号:{id}</li>
          <li>消息标题:{title}</li>
          <li>消息内容:{content}</li>
        </ul>
      );
    }

6. useMatch()

  1. 作用:返回当前匹配信息,对标 5.x 中的路由组件的match属性。

  2. 示例代码:

    jsx
    <Route path="/login/:page/:pageSize" element={<Login />}/>
    <NavLink to="/login/1/10">登录</NavLink>
    
    export default function Login() {
      const match = useMatch('/login/:x/:y')
      console.log(match) //输出match对象
      //match对象内容如下:
      /*
      	{
          params: {x: '1', y: '10'}
          pathname: "/LoGin/1/10"
          pathnameBase: "/LoGin/1/10"
          pattern: {
          	path: '/login/:x/:y',
          	caseSensitive: false,
          	end: false
          }
        }
      */
      return (
      	<div>
          <h1>Login</h1>
        </div>
      )
    }

7. useInRouterContext()

说明:用于判断当前组件是否处于路由上下文中,并返回一个布尔值。

作用:如果组件在 <Router> 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。

8. useNavigationType()

  1. 作用:返回当前的导航类型(用户是以如何方式来到当前页面的)。
  2. 返回值:POPPUSHREPLACE
  3. 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)。

9. useOutlet()

  1. 作用:用来呈现当前组件中渲染的嵌套路由内容。

  2. 示例代码:

    jsx
    const result = useOutlet();
    console.log(result);
    // 如果嵌套路由没有挂载,则result为null
    // 如果嵌套路由已经挂载,则展示嵌套的路由对象

10.useResolvedPath(url)

  1. 作用:给定一个 URL 值,解析其中的:path、search、hash 值。

11.useHistory()

useHistory() 是一个自定义钩子函数,用于获取路由历史对象。路由历史对象用于在 React Router 中进行导航、跳转和管理历史记录。——> 对标 5.x 中的路由组件的history属性。

通过 useHistory() 钩子函数可以获取一个路由历史对象,该对象提供了以下属性和方法:

  • location:表示当前路由的位置信息,包括 pathnamesearchhash 等。
  • navigate:是一个函数,用于进行编程式导航,即在组件内部进行页面跳转。
  • go, back, forward:用于在路由历史记录中进行导航操作,类似于浏览器的后退、前进、跳转功能。
  • push, replace:用于将新的路由添加到历史记录中,或替换当前的路由。

12.useRouter()

useRouter() 是一个自定义钩子函数,用于获取当前路由相关信息的路由对象。它可以用于在组件中访问和操作当前路由的状态、路径、参数等。

使用 useRouter() 钩子函数可以获取一个路由对象,该对象包含以下属性和方法:

  • location:表示当前路由的位置信息,包括 pathnamesearchhash 等。
  • params:包含当前路由的参数信息,如果路径中包含参数,则可以通过该属性访问参数的值。
  • navigate:是一个函数,用于进行编程式导航,即在组件内部进行页面跳转。
  • basePath:表示当前路由的基本路径,通常用于嵌套路由中指定的父级路径。
  • isMatch:一个函数,用于判断给定的路径是否与当前路由匹配。

以下是 useRouter() 的示例用法:

jsx
import { useRouter } from "react-router-dom";

const MyComponent = () => {
  const router = useRouter();
  const { location, params, navigate, basePath, isMatch } = router;

  // 使用路由对象中的属性和方法
  console.log(location);
  console.log(params);

  // 在事件处理程序中进行页面导航
  const handleClick = () => {
    navigate("/about");
  };

  return (
    <div>
      <button onClick={handleClick}>跳转到 About 页面</button>
    </div>
  );
};

这个钩子可以替代 useHistory()useLocation()useParams() 等钩子函数的功能。