React-Router路由

路由

后端路由阶段

  • 页面中需要请求不同的路径内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户端.
  • 这种情况下渲染好的页面, 不需要单独加载任何的jscss, 可以直接交给浏览器展示, 这样也有利于**SEO的优化**.
  • 缺点:
    • 性能限制:后端路由需要将请求发送到服务器进行处理,然后再将响应返回给客户端。这涉及到网络延迟和服务器负载,可能会对应用程序的性能产生影响。特别是在大量请求同时到达时,后端路由可能面临性能瓶颈。
    • 扩展性限制:后端路由通常依赖于服务器端的处理和资源,这使得在面对高流量或大规模应用程序时扩展变得更加复杂。需要增加服务器资源或采用负载均衡等技术来应对流量增加,这可能导致高成本和复杂性。
    • 开发效率低下:后端路由要求在服务器端编写和维护路由逻辑,这意味着前后端之间需要进行更多的协作和开发工作。此外,由于请求需要经过网络传输,开发和调试的效率可能会受到一定影响。
    • 限制前端技术选择:后端路由通常会限制前端开发人员使用特定的服务器端技术栈。前端开发人员需要熟悉服务器端路由和服务器框架,这可能需要额外的学习成本和时间。
    • 不适合单页应用程序(SPA):后端路由通常是基于请求-响应模型的,不太适合用于构建单页应用程序(SPA),因为SPA通常需要在客户端进行动态的路由和页面切换。后端路由需要每次路由切换时都向服务器发送请求,这会增加不必要的网络开销和延迟。

前后端分离阶段;

  • 前端渲染的理解:

    • 每次请求涉及到的静态资源都会从静态资源服务器获取,这些资源包括HTML+CSS+JS,然后在前端对这些请求回来的资源进行渲染;
    • 需要注意的是,客户端的每一次请求,都会从静态资源服务器请求文件;
    • 同时可以看到,和之前的后端路由不同,这时后端只是负责提供API了;
  • 单页面富应用(SPA);

    • SPA最主要的特点就是在前后端分离的基础上加了一层前端路由.
    • 也就是前端来维护一套路由规则.
  • 前端路由的核心是什么呢?改变URL,但是页面不进行整体的刷新。

URL的hash

前端路由是如何做到URL和内容进行映射呢?监听URL的改变。

URL的hash

  • URL的hash也就是锚点(#),本质上是改变window.locationhref属性
  • 可以通过直接赋值location.hash来改变href, 但是页面不发生刷新;
  • hash的优势就是兼容性更好,在老版IE中都可以运行,但是缺陷是有一个#,显得不像一个真实的路径。
<div id="app">
    <a href="#/home">home</a>
    <a href="#/about">about</a>
    <div class="router-view"></div>
</div>

以下是给提供的代码添加了注释:

// 1. 获取 router-view 元素
const routerViewEl = document.querySelector(".router-view");

// 2. 监听 hashchange 事件
window.addEventListener("hashchange", () => {
    switch (location.hash) { // 根据当前 hash 值设置页面内容
        case "#/home":
            routerViewEl.innerHTML = "home";
            break;
        case "#/about":
            routerViewEl.innerHTML = "about";
            break;
        default:
            routerViewEl.innerHTML = "default";
    }
});
  1. 获取名为 “router-view” 的元素,用于显示路由视图。
  2. 监听浏览器的 hashchange 事件,当 hash 值发生变化时,执行回调函数。根据当前的 hash 值,设置 routerViewEl 元素的内容,以显示对应的页面内容。如果 hash"#/home",显示 "home";如果 hash 为 “#/about”,显示 “about”;否则显示 “default”。

注意:该代码是使用 hash 值来实现简单的路由功能,根据不同的 hash 值显示不同的页面内容。然而,现代的前端路由通常使用 HTML5 History API 中的 pushStatereplaceState 方法来修改 URL,而不是使用 hash。使用 hash 的路由在 URL 上会带有 # 符号,并且可能不支持一些现代的前端路由功能和服务器配置。

HTML5的History

  • history接口是HTML5新增的, 它有六种模式改变URL而不刷新页面:
    • replaceState :替换原来的路径;
    • pushState :使用新的路径;
    • popState :路径的回退;
    • go :向前或向后改变路径;
    • forward :向前改变路径;
    • back :向后改变路径;
<div id="app">
    <a href="#/home">home</a>
    <a href="#/about">about</a>
    <div class="router-view"></div>
</div>
// 1. 获取 router-view 元素
const routerViewEl = document.querySelector(".router-view");

// 2. 监听所有的 a 元素
const aEls = document.getElementsByTagName("a"); 
for (let aEl of aEls) {
    aEl.addEventListener("click", (e) => { 
        e.preventDefault(); // 阻止默认行为,不进行页面跳转
        const href = aEl.getAttribute("href"); // 获取链接的 href 属性
        console.log(href); // 打印 href
        history.pushState({}, "", href); // 将链接添加到浏览器历史记录中
        historyChange(); // 执行页面设置操作
    })
}

// 3. 监听 popstate 和 go 操作
window.addEventListener("popstate", historyChange); 
window.addEventListener("go", historyChange);

// 4. 执行设置页面操作
function historyChange() { 
    switch(location.pathname) { // 根据当前路径设置页面内容
        case "/home":
            routerViewEl.innerHTML = "home"; 
            break;
        case "/about":
            routerViewEl.innerHTML = "about"; 
            break;
        default:
            routerViewEl.innerHTML = "default"; 
    }
}
  1. 获取名为 “router-view” 的元素,用于显示路由视图。
  2. 遍历所有的 <a> 元素,并为每个元素添加点击事件监听器。当点击链接时,阻止默认行为,获取链接的 href 属性,并将其添加到浏览器历史记录中。然后执行 historyChange 函数,用于根据当前路径设置页面内容。
  3. 监听浏览器的 popstate 和自定义的 go 事件。当浏览器的前进/后退按钮被点击或通过 JavaScript 的 go 方法调用时,执行 historyChange 函数,用于根据当前路径设置页面内容。
  4. historyChange 函数根据当前路径的不同,设置 routerViewEl 元素的内容,以显示对应的页面内容。如果路径为 "/home",显示 "home";如果路径为 “/about”,显示 “about”;否则显示 “default”。

React-Router

  • 安装react-router-dom — npm install react-router-dom

Router的基本使用

  • 选择路由模式 BrowserRouter使用history模式 / HashRouter使用hash模式
<HashRouter>
    <App />
</HashRouter>

路由映射配置

  • Routes:包裹所有的Route,在其中匹配一个路由
    • Router5.x使用的是Switch组件
  • Route:Route用于路径的匹配;
    • path 属性:用于设置匹配到的路径;
    • element 属性:设置匹配到路径后,渲染的组件;
      • Router5.x使用的是 component 属性
    • exact :精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件;
      • Router6.x不再支持该属性
   {/* 映射关系: path => Component */}
        <Routes>
          <Route path='/' element={<Home/>}/>
          <Route path='/about' element={<About/>}/>
          <Route path='/login' element={<Login/>}/>
          <Route path='*' element={<NotFound/>}/>
        </Routes>

路由配置和跳转

  • 路由跳转 — Link组件 / NavLink组件
    • 通常路径的跳转是使用Link组件,最终会被渲染成 a 元素;
    • NavLink是在Link基础之上增加了一些样式属性
    • to 属性 — 用于设置跳转到的路径
     <div className='nav'>
          <Link to="/home">首页</Link>
          <Link to="/about">关于</Link>
          <Link to="/login">登录</Link>
          <Link to="/user?name=why&age=18">用户</Link>
     </div>
  • NavLink的使用
    • 需求:路径选中时,对应的a元素变为红色
    • 这个时候,我们要使用NavLink组件来替代Link组件:
      • style:传入函数,函数接受一个对象,包含isActive属性
      • className:传入函数,函数接受一个对象,包含isActive属性
      • 事实上在默认匹配成功时,NavLink就会添加上一个动态的active class;
        • 可能出现样式的层叠,也可以自定义class
  <NavLink to="/home" 
  style={({isActive}) => ({color: isActive ? "red": ""})}>首页</NavLink>
  <NavLink to="/about" 
  style={({isActive}) => ({color:  isActive ? "red": ""})}>关于</NavLink>
  //或者
  <NavLink to="home" className={this.getActiveClass}>首页</NavLink>
  getActiveClass({isActive}){
      return classNames({"link-active": isActive})
  }
  • 路由重定向 — Navigate
    • Navigate用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中:
 <Route path='/' element={<Navigate to="/home"/>}/>
  • Not Found页面配置 — path="\*"
    • 用户随意输入一个地址,该地址无法匹配,那么在路由匹配的位置显示Not Found页面
 <Route path='*' element={<NotFound/>}/>

路由的嵌套

  • 路由之间是存在嵌套关系的。
  • <Outlet> 组件用于在父路由元素中作为子路由的占位元素。
  <Route path='/' element={<Navigate to="/home"/>}/>
    <Route path='/home' element={<Home/>}>
      <Route path='/home' element={<Navigate to="/home/recommend"/>}/>
      <Route path='/home/recommend' element={<HomeRecommend/>}/>
      <Route path='/home/ranking' element={<HomeRanking/>}/>
    </Route>
  </Routes>
  
   <div>
        <h1>Home Page</h1>
        <div className='home-nav'>
          <Link to="/home/recommend">推荐</Link>
          <Link to="/home/ranking">排行榜</Link>
          <button onClick={e => this.navigateTo("/home/songmenu")}>歌单</button>
        </div>

        {/* 占位的组件 */}
        <Outlet/>
  </div>

手动路由的跳转

  • 通过JavaScript代码逻辑进行跳转(比如点击了一个button),那么就需要获取到navigate对象。
  • Router6.x版本之后,代码类的API都迁移到了hooks的写法:
    • 代码跳转,需要通过useNavigate的Hook获取到navigate对象进行操作
 const navigate = useNavigate()
  
  function navigateTo(path) {
    navigate(path)
  }

  return (
    <div className='app'>
      <div className='header'>
        <span>header</span>
        <div className='nav'>
          {/* 点击按钮时,导航到 "/category" 路径 */}
          <button onClick={e => navigateTo("/category")}>分类</button>
          {/* 点击按钮时,导航到 "/order" 路径 */}
          <span onClick={e => navigateTo("/order")}>订单</span>

          <Link to="/user?name=why&age=18">用户</Link>
        </div>
        <hr />
      </div>
      <div className='content'>
        {/* 映射关系: path => Component */}
        <Routes>
          <Route path='/category' element={<Category/>}/>
          <Route path='/order' element={<Order/>}/>
          <Route path='/detail/:id' element={<Detail/>}/>
          <Route path='/user' element={<User/>}/>
          <Route path='*' element={<NotFound/>}/>
        </Routes>
        {useRoutes(routes)}
      </div>
    </div>
  )
  • 类组件
// 定义一个高阶组件 (HOC),它接收一个组件(WrapperComponent)作为参数
function withRouter(WrapperComponent) {
  // 返回一个新的函数组件,接收原始组件的 props 作为参数
  return function(props) {
    // 使用 React Router 的 useNavigate hook 获取导航函数,
    // 它可以用来进行页面跳转
    const navigate = useNavigate();

    // 使用 React Router 的 useParams hook 获取动态路由参数,
    // 例如,在路由 /detail/:id 中,id 就是一个动态路由参数
    const params = useParams();

    // 使用 React Router 的 useLocation hook 获取 location 对象,
    // 它包含了当前URL的信息
    const location = useLocation();

    // 使用 React Router 的 useSearchParams hook 获取 URL 查询参数,
    // 返回一个包含所有查询参数的 URLSearchParams 实例
    const [searchParams] = useSearchParams();

    // 将查询参数的 entries 转化为一个对象,方便使用
    const query = Object.fromEntries(searchParams);

    // 将获取到的路由相关的数据组合到一个对象中
    const router = { navigate, params, location, query };

    // 返回一个新的组件,传递原始的 props 并添加 router prop
    return <WrapperComponent {...props} router={router}/>;
  }
}
这个代码是一个使用类组件的React Router例子。`Home` 是一个 `PureComponent`,它包含了一些导航链接和一个 `Outlet` 组件。

下面是对代码的解释:

```jsx
// 定义一个类组件 Home,它继承自 React.PureComponent
export class Home extends PureComponent {
  // 定义一个方法 navigateTo,用于导航到指定路径
  navigateTo(path) {
    // 从 props 中获取 router 对象,然后获取其中的 navigate 方法
    const { navigate } = this.props.router;
    // 调用 navigate 方法,导航到指定路径
    navigate(path);
  }

  // 定义渲染方法
  render() {
    // 渲染一些导航链接和一个 Outlet 组件
    return (
      <div>
        <h1>Home Page</h1>
        <div className='home-nav'>
          {/* 使用 Link 组件导航到 "/home/recommend" 路径 */}
          <Link to="/home/recommend">推荐</Link>

          {/* 使用 Link 组件导航到 "/home/ranking" 路径 */}
          <Link to="/home/ranking">排行榜</Link>

          {/* 点击按钮时,使用 navigateTo 方法导航到 "/home/songmenu" 路径 */}
          <button onClick={e => this.navigateTo("/home/songmenu")}>歌单</button>
        </div>

        {/* Outlet 组件,它用于显示当前路径对应的子路由组件 */}
        <Outlet/>
      </div>
    )
  }
}

在这个组件中,Link 组件用于创建导航链接,Outlet 组件用于渲染子路由。点击按钮时,会调用 navigateTo 方法,该方法会使用 navigate 函数导航到指定路径。

路由参数传递

  • 动态路由的方式;
    <Link to="/user/9978">用户</Link>
    <Route path="/user/:id" element={<User />}></Route>
    
    // 获取动态路由参数 -- 需要通过 useParams 只能在函数式组件中使用
    import { useParams } from "react-router-dom";
    export function User() {
      const params = useParams()
      return (
        <div>
          <h4>User Page</h4>
          <h4>id: {params.id}</h4>
        </div>
      )
    }
    
  • search传递参数;
 {/* 创建一个链接,点击后会导航到 "/user" 路径,并带有查询参数 "name=coder" 和 "age=19" */}
    <Link to="/user?name=coder&age=19">用户</Link>
    
function MyComponent() {
  // 获取 URL 查询参数
  const [searchParams] = useSearchParams();

  // 将查询参数转换为 JavaScript 对象
  const query = Object.fromEntries(searchParams);

  // 现在你可以使用 query.name 和 query.age 获取查询参数了

  return (
    <div>
      {/* 显示查询参数 */}
      <p>名字: {query.name}</p>
      <p>年龄: {query.age}</p>
    </div>
  );
}

路由的配置文件

  • 早期,Router并且没有提供相关的API,我们需要借助于react-router-config完成;
  • Router6.x中,为我们提供了useRoutes API可以完成相关的配置

React-Router路由

    <div>  {useRoutes(routes)} </div>
  • 对某些组件进行了异步加载懒加载),那么需要使用Suspense进行包裹:
// 在单独的router/index.js文件中

// 路由懒加载/按需加载/异步加载 ? 
// 这样暂时不会显示,因为是异步的要单独下载,需要加载一个loading动画React提供的Suspense组件
const Order = React.lazy(() => import("../page/Order"));
const User = React.lazy(() => import("../page/User"));

const router = [
    {
        path: '/',
        element: <Navigate to="/home" />,
    },
    {
        path: "/home",
        element: <Home />,
        children: []
    }
]
export default router
<HashRouter>
  <Suspense fallback={<h4>Loading~~~~</h4>}>
    <App />
  </Suspense>
</HashRouter>

类组件无法直接使用navigate、location等参数,应该如何进行操作?

  • 封装

这是一个React中的高阶组件(HOC),它包装另一个组件,并为其提供与路由相关的属性。

import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
// 这是一个接收组件(WrapperComponent)作为参数的函数
function withRouter(WrapperComponent) {
  // 它返回另一个接收props作为参数的函数
  return function(props){
    // 这个钩子函数允许你以编程的方式进行导航。它相当于react-router v5中的history.push。
    const navigate = useNavigate()

    // 这个钩子函数允许你访问当前动态路由的参数
    const params = useParams()

    // 这个钩子函数返回当前的位置对象,该对象包含了当前URL的信息
    const location = useLocation();

    // 这个钩子函数返回一个新的URLSearchParams对象,该对象表示当前URL的查询参数
    const [searchParams] = useSearchParams();

    // 将URLSearchParams对象转换成普通的JavaScript对象,方便访问
    const query = Object.fromEntries(searchParams);

    // 将所有这些与路由相关的对象聚合到一个对象中
    const router = {navigate, params, location, query}

    // 返回WrapperComponent,扩展原始的props,并添加新创建的router对象作为一个属性
    return <WrapperComponent {...props} router={router} />
  }
}
export default withRouter

这个函数旨在为组件提供方便访问路由相关功能和数据的方式,如导航、URL参数和查询参数。你可以使用这个函数来包装任何需要使用这些功能的组件。

  • 使用

这个代码首先从 ‘react’ 中导入了 ReactPureComponent,以及从一个名为 “../hoc/with_router” 的文件中导入了 withRouter 高阶组件。

然后定义了一个名为 About 的组件,它继承自 PureComponent。这个组件有一个名为 navigateTo 的方法,它使用了 router 属性中的 navigate 方法来进行导航。在 render 方法中,它从 router 属性中获取了 query 对象,并在页面上展示了 query.namequery.age 的值。

最后,它使用 withRouter 高阶组件包装了 About 组件,并将结果作为默认导出。这样,About 组件就可以使用 router 属性中的 navigate 方法和 query 对象了。

// 从'react'中导入React和PureComponent
import React, { PureComponent } from 'react'
// 从"../hoc/with_router"中导入withRouter高阶组件
import withRouter from "../hoc/with_router"

// 定义一个名为About的PureComponent组件
export class About extends PureComponent {
  // 定义一个导航方法,接收一个路径作为参数
  navigateTo(path) {
    // 从props中解构出router对象,然后从router中解构出navigate方法
    const { navigate } = this.props.router
    // 调用navigate方法,进行页面导航
    navigate(path)
  }
  // 定义render方法,返回组件的JSX结构
  render() {
    // 从props中解构出router对象,然后从router中解构出query对象
    const { query } = this.props.router
    // 返回一个div元素,其中包含一个标题和一个显示query.name和query.age的文本
    return (
      <div>
        <h3>About Page</h3>
        <h4>name: {query.name}-age: {query.age}</h4>
      </div>
    )
  }
}
// 使用withRouter高阶组件包装About组件,并导出结果
export default withRouter(About)

这个 About 组件通过 withRouter 高阶组件得到了 router 属性,可以通过它进行导航以及获取查询参数。

原文链接:https://juejin.cn/post/7236713712628121655 作者:星_墨

(0)
上一篇 2023年5月24日 上午10:16
下一篇 2023年5月25日 上午10:07

相关推荐

发表评论

登录后才能评论