实用指南:React(六):React-router详解

目录

一、react-router简介

二、路由配置

1、组件式路由

2、配置式路由

1、数据路由的配置

2、数据路由的相关Api

3、嵌套路由

三、路由跳转

1、声明式跳转

2、编程式跳转

四、路由传参

1、查询参数

2、动态路由

3、state

五、路由懒加载

1、数据路由中的懒加载

2、组件路由的懒加载

3、路由懒加载的好处

六、相关面试题

1、路由的两种模式

2、SPA与MPA的区别

3、MVC MVVM的区别


一、react-router简介

react-router包含react-router、react-router-dom、react-router-native。react-router可以应用于浏览器和原生应用的通用部分,react-router-dom可以用于浏览器端,react-router-native可以用于原生应用,我们做的是web项目,安装react-router-dom就可以了。我们在项目中目前使用react-router-dom v6.4+版本,它相较于v5及以下版本进行了大幅度的重构,简化了API、强化了路由的匹配逻辑还引入了嵌套路由的新实现方式

react-router-dom v6+ 与v5的区别

1、v6的Routes替代了v5的switch:Routes会自动匹配精确的路由,同一时间只渲染一个匹配的Route。v5需要手动处理exact来确保精准匹配

2、v6的element替代了v5的component或render:route组件通过element直接传入要渲染的组件就可以了,v6不支持component或render函数

3、v6简化了嵌套路由:v6通过Outlet组件实现了子路由的渲染,不用手动拆分路由配置,结构更清晰

4、编程式导航优化:v6使用了useNavigate钩子替代了useHistory,使用useNavigate(-1)可以实现后退,代码更简洁

5、v6的NavLink高亮优化:v6支持通过函数自定义active样式,不用额外处理匹配逻辑

二、路由配置

在配置路由之前,我们要先在项目中安装react-router-dom

npm i react-router-dom

1、组件式路由

组件式路由是react-router v6之前的主流用法,就是通过<Route/>、<Routes/>等组件标签来定义路由规则,不是集中式配置路由对象。v6.4版本之后组件式路由不是主流的用法,但它并没有被淘汰

我们可以使用react-router-dom提供的BrowserRouter或HashRouter包裹根组件<App/>

//main.tsx项目的入口组件
import { BrowserRouter } from 'react-router-dom'
createRoot(document.getElementById('root')).render(
,
)

我们在项目的根组件中使用Routes和Route来匹配和渲染路由

Routes:路由的根容器,管理所有的Route,主要用来匹配路由

Route:用于将不同的url路径映射到不同的react组件上,也就是当组件的url与route的path属性匹配时,会渲染相应的组件

Route提供了很多属性来定义路由的行为和渲染逻辑,我主要讲几个我在项目中常用的属性

path:它是一个字符串,用于匹配url路径模式,它支持动态参数(path:"/about/:id")和通配符(*)

element:匹配要渲染的react组件(element:{<Home/>})

index:布尔值,表示默认子路由,它的优先级最低,当其它所有路由都不匹配时才会来匹配这个默认路由,(<Route index element={<Home/>}/>)

//App.jsx项目的根组件
function App() {
}/>//默认路由
}/>
}/>
}
export default App

注:路由匹配时按顺序来进行的,如果我们想要项目一启动优先显示默认路由,我们要将默认路由写在第一个位置。通配符表示全局默认路由,一般放在路由配置的最后面

2、配置式路由

配置式路由支持数据处理也叫数据路由,它是react router v6.4+新增的,它可以集中式配置路由对象,支持loader数据预加载、action表单提交等数据处理,可以解决传统路由先跳转后加载数据导致的白屏或状态不一致的问题,适合需要统一管理复杂数据逻辑的应用

1、数据路由的配置

我们可以先在项目的src根目录下建一个router文件夹,专门用来存放与路由相关的文件

在router/index.js文件下通过createBrowserRouter创建路由配置

import { createBrowserRouter } from "react-router-dom"
import About from "../About"
import Home from "../Home"
const router=createBrowserRouter([
{
path:'/',
element:
},
{
path:"/about",
element:
},
{
path:"*",
element:404
}
])
export default router

在项目的根组件App.jsx中通过RouterProvider来使用路由

import { RouterProvider} from 'react-router-dom';
import router from './router';
function App() {
return
11111
}
export default App;

2、数据路由的相关Api

数据路由提供了很多Api,但我在项目中常用的就是loader函数,所以我就详细介绍一下loader函数的具体用法

loader:我们可以使用loader函数直接在配置路由时发送请求,可以在进入路由前或路由参数变化时加载数据,可以避免白屏等待的问题,提高用户体验

//router/index.js路由配置文件
const router=createBrowserRouter([
{
path:'/',
element:,
loader:async ()=>{
const response=await fetch("https://jsonplaceholder.typicode.com/comments")
const res=await response.json()
return res
}
}
])
export default router

在组件中我们可以通过react-router-dom提供的hooks useLoaderData()来获取数据

//home.jsx组件
import { useLoaderData } from "react-router-dom"
function Home(){
const data=useLoaderData()
console.log(data)
return<>
我是首页
}
export default Home

3、嵌套路由

嵌套路由就是在一个路由组件中再定义其它的路由,形成父子关系的路由结构,嵌套路由很适合像后台管理系统这种具有共享布局的应用

数据路由的嵌套

在路由配置中使用路由对象提供的children属性来嵌套路由

import { createBrowserRouter } from "react-router-dom"
import About from "../page/About"
import Home from "../page/Home"
import Floor from "../page/Floor"
import Money from "../page/Money"
const router=createBrowserRouter([
{
path:'/home',
element:,
children:[
{
path:"floor",
element:
},
{
path:"Money",
element:
}
]
},
{
path:"/about",
element:
},
{
path:"*",
element:404
}
])
export default router

注:子路由默认在父路由的基础上进行叠加的,嵌套的子路由路径为绝对路径。默认路由是不能添加子路由的

在父路由组件中使用<Outlet>组件作为子路由的渲染出口

import {Outlet, useNavigate} from "react-router-dom"
import { useState } from "react"
function Home(){
const navigate=useNavigate()
const fn=()=>{
navigate("floor")
}
return<>
我是首页
}
export default Home

注:我们想要子组件显示在父组件的哪个位置,我们就可以将<Outlet>组件放在相应的位置上就可以了。

组件路由的嵌套

先在App.jsx组件配置要嵌套的路由

import NotFound from "./NotFound";
import { Routes, Route } from "react-router-dom";
//高阶组件
function App() {
return <>
}>
} />//嵌套的子路由
} />
} />
}
export default App

在路由trolley.jsx组件中使用<Outlet>组件作为子路由的渲染出口

import { Outlet,Link } from "react-router-dom";
function Trolley() {
return <>
跳到儿子组件
}
export default Trolley

注:我这里的例子只嵌套了一层子路由,要嵌套多层的话,方法是一样的,在子路由中再嵌套子路由就可以了

三、路由跳转

1、声明式跳转

声明式跳转也叫组件跳转,react-router-dom提供了<Link></Link><NavLink></NavLink>两种组件跳转方式。组件跳转的本质是渲染了一个<a></a>标签,适用于按钮,文本链接等一些点击跳转

<Link>:基础的组件跳转,没有高亮效果

import {Link} from "react-router-dom"
function Home(){
return<>
666
我是首页
}
export default Home

注:默认情况下,使用<Link>跳转会在浏览器的历史中添加一条新目录,我们可以使用replace属性来替换当前历史目录

<NavLink>:是<Link>的增强版,支持当前路由高亮

import { NavLink } from "react-router-dom"
function About(){
return <>
({color:isActive? "red":"blue"})}>跳到主页
我是about页
}
export default About

注:路由跳转组件不能写在非路由组件中,<Link></Link><NavLink></NavLink>是路由组件的一个单元,必须在路由组件中才能使用,写在非路由组件中会报错

2、编程式跳转

编程式跳转就是使用react-router-dom提供的hooks useNavigate来进行跳转,这种跳转方式适合点击按钮和需要js判断逻辑的场景,如登录成功后需要跳转到首页。

import {useNavigate} from "react-router-dom"
function About(){
const navigate=useNavigate()
const jump=()=>{
navigate("/",{replace:true})//替换历史记录
}
return <>
我是about页
}
export default About

注:编程式跳转要替换历史记录需要向useNavigate传入第二个参数{replace:true}。useNavigate(-1)

表示后退到上一页

四、路由传参

1、查询参数

查询参数是指url中?后面的部分,通常用于传递非敏感的数据,如id、页码等非敏感数据,不是路径的必须部分

组件跳转时通过Link或useNavigate拼接参数

//home.jsx组件
import {useNavigate} from "react-router-dom"
function Home(){
const navigate=useNavigate()
const fn=()=>{
navigate("/about?id=123&name=zhangsan")
}
return<>
我是首页
}
export default Home

目标组件通过react-router-dom提供的hooks useSearchParams读取参数。useSearchParams返回一个包含两个元素的数组,第一个元素为searchParams,第二个元素为setSearchParams

searchParams:它提供了多种方法,用于读取当前url的查询参数

注:useSearchParams返回的第二个元素,我在项目中没有用过,简单了解一下就可以了

//about.jsx组件
import {useSearchParams} from "react-router-dom"
function About(){
const [searchParams]=useSearchParams()
let searchValue=searchParams.get("name")
return <>
我是about页{searchValue}
}
export default About

注:我们调用searchParams提供的get方法就可以获取指定参数的值,参数不存在时返回null

2、动态路由

动态路由传参就是将路由路径中的可变部分用动态参数表示,跳转时携带要传的具体值,在目标页面通过路由提供的hooks来读取参数

传一个参数的情况

//router/index.js文件
const router=createBrowserRouter([
{
path:"/about/:id",
element:
}
])
export default router
//home.jsx携带参数的组件
import {useNavigate} from "react-router-dom"
import { useState } from "react"
function Home(){
const [data]=useState("123")
const navigate=useNavigate()
const fn=()=>{
navigate("/about/"+data)
}
return<>
我是首页
}
export default Home

目标组件使用react-router-dom提供的hooks useParams来接收参数,useParams返回的是一个对象

//about.jsx组件
import {useNavigate,useParams} from "react-router-dom"
function About(){
const navigate=useNavigate()
const id=useParams()
const jump=()=>{
navigate("/",{replace:true})
}
return <>
我是about页面
}
export default About

传多个参数的情况

const router=createBrowserRouter([
{
path:"/about/:id/:name",
element:
}
])
export default router
//home.jsx携带参数的组件
import {useNavigate} from "react-router-dom"
import { useState } from "react"
function Home(){
const [data]=useState("123")
const [name]=useState("zhangsan")
const navigate=useNavigate()
const fn=()=>{
navigate(`/about/${data}/${name}`)//模板字符串拼接参数
}
return<>
我是首页
}
export default Home
//home.jsx目标组件
import {useNavigate,useParams} from "react-router-dom"
function About(){
const navigate=useNavigate()
const {name}=useParams()//传了多个参数解构赋值获得需要的参数
const jump=()=>{
navigate("/",{replace:true})
}
return <>
我是about页面
}
export default About

3、state

state可以传递不希望显示在url中的敏感数据或复杂数据,它传递的参数会存储在浏览器的历史记录中,页面刷新后会丢失

//home.jsx组件传递数据的组件
import {useNavigate} from "react-router-dom"
import { useState } from "react"
function Home(){
const navigate=useNavigate()
const fn=()=>{
navigate("/about",{state:{arr:[1,2,3],userInfo:{id:1,name:"zhangsan"}}})
}
return<>
我是首页
}
export default Home

目标组件使用react-route-dom提供的useLocation来接收参数,它返回的是一个对象

import {useNavigate,useLocation} from "react-router-dom"
function About(){
const navigate=useNavigate()
const data=useLocation()
//获取home.jsx组件传来的数据
console.log(data.state)
const jump=()=>{
navigate("/",{replace:true})
}
return <>
我是about页面
}
export default About

五、路由懒加载

react路由懒加载是一种优化单页应用性能技术,它能让路由对应的组件在需要时才加载,不是让组件在初始化时全部加载,从而减少初始加载的体积,提升首屏渲染的速度

1、数据路由中的懒加载

在路由配置的组件中使用React.lazy()来接收一个动态导入的函数,并返回一个promise,将非默认导出的组件包装为react可识别的懒加载的组件

import { createBrowserRouter } from "react-router-dom"
import React, { lazy } from "react"
const Home = lazy(() => import("../page/Home"))
const About = lazy(() => import("../page/About"))
const Floor = lazy(() => import("../page/Floor"))
const Money = lazy(() => import("../page/Money"))
const router = createBrowserRouter([
{
path: '/',
element: ,
},
children: [
{
path: "floor",
element:
},
{
path: "Money",
element:
}
]
},
{
path: "/about",
element:
},
{
path: "*",
element: 404
}
])
export default router

在根组件中使用React.Suspense组件来包裹单个懒加载组件或多个路由,它可以在懒加载组件加载完成前,提供fallback占位内容(如加载动画,提示文本),避免页面空白

import './App.css';
import { RouterProvider} from 'react-router-dom';
import { Suspense } from 'react';
import router from './router';
function App() {
return
loading...}>
}
export default App;

2、组件路由的懒加载

import React, { lazy, Suspense } from "react";
import NotFound from "./NotFound";
import { Routes, Route } from "react-router-dom";
const Trolley = lazy(() => import("./trolley"))
const Child = lazy(() => import("./child"))
const TodoList = lazy(() => import("./todoList"))
function App() {
return <>
加载中}>
}>
} />
} />
} />
}
export default App

注:不能在配置路由中的element属性上直接写懒加载函数,因为element接收的是一个组件

3、路由懒加载的好处

路由懒加载就是通ES6的动态import语法,将每个路由组件分割成独立的小js文件,只有当用户访问该路由时,浏览器才会异步下载对应的js文件并渲染组件,可以缩短首屏加载的时间,提升用户体验

六、相关面试题

1、路由的两种模式

React Router主要支持两种路由模式,hash模式和history模式,它们的核心区别在于url的表现形式和底层实现的原理不同

1、url的区别

hash模式:创建路由的两种方式分别是createHashRouter和HashRouter,它们的url中包含#,#后面的内容为路由路径

history模式:创建路由的两种方式分别是createBrowserRouter和BrowserRouter,它们的url与常规路径一致,不包含#

2、实现原理不同

hash模式:它的实现原理是基于浏览器的onhashchange事件,通过监听url中#后内容的变化,来匹配对应的路由组件,不会向服务器发送请求

history模式:它的实现原理是基于h5新增的HistoryAPI pushState和replaceState方法,通过操作浏览器的历史记录来修改url,同时不会触发页面的刷新,但url变化时会向服务器发送请求

3、兼容性不同

hash模式:它的兼容性好,支持所有的现代浏览器及旧版浏览器,不需要服务器的额外配置

history模式:它的兼容性不好,它依赖于h5提供的API,不支持IE9以下的浏览器,需要服务器配置fallback规则,以返回所有可能路径的请求指向同一个index.html文件,否则刷新页面会出现404错误

2、SPA与MPA的区别

面试中的回答:

SPA(Single-Page-Application)和MPA(Multi-Page-Application)是两种不同的Web应用架构模式,它们核心的区别在于页面渲染和切换的方式。SPA通过前端js动态重写当前页面来与用户交互,而MPA的每个页面都需要从服务器重新加载整个页面

SPA:是只有一个主页面的应用,所有内容都包含在主页面上,对每一个功能模块组件化。单页应用跳转,切换相关组件,局部刷新资源。它的优点就是页面切换是无刷新的,所以用户的体验感好,而且是前后端分离的,开发效率高。但它需要先加载较大的js包,所以首屏加载的速度慢,而且早期的搜索引擎难以抓取js动态生成的内容,所以它不利于SEO优化

MPA:是有多个独立页面的应用,每个页面都需要服务器返回一个完整的HTML,多页应用跳转,需要整页资源刷新。它的优点就是服务器返回了直接渲染的HTML,所以首屏加载的速度非常快,有利于搜索引擎优化。但它每次跳转都要刷新页面,用户的体验感较差,而且服务器会有较大的压力

我之前做过的后台管理系统之类的项目对SEO没有太多要求,这类项目非常注重操作的流畅度,所以我会首选SPA来开发项目,但对于项目中有些页面需要SEO进行优化(如认证授权页、支付相关的页面),我会使用MPA或SSR来渲染页面,然后再进入SPA主应用

面试中还可能会问

客户端渲染(CSR)、服务端渲染(SSR)、静态站点生成(SSG)的区别

CSR、SSR和SSG三者的核心区别是最终生成HTML的内容和位置

CSR:就是渲染页面的过程是在客户端进行的,用户的体验感好,后端仅提供API,服务器的压力小,它适合后台管理系统等对SEO无要求的应用型网站

SSR:就是服务端收到请求后,只针对首页是在服务端渲染完成的,渲染完成后直接将html发送到客户端,其它的页面都是在客户端渲染完成的,服务端渲染首屏加载的速度快,有利于SEO优化,但它每次渲染都要消耗CPU,服务器的压力大,简化了服务端渲染的复杂度。它适合既需要SEO优化又包含动态数据的内容型网站如电商商品页,新闻资讯页等等

SSG:就是项目里所有的页面都是在服务端渲染完成的,用户访问时直接返回这个文件,它的性能是最好的,安全性高,它适合公司官网,文档等内容相对固定的网站

想要高薪的可以回答这两段话:

像传统的客户端渲染可以选择React,Vue或Angular框架来进行开发,当项目需要服务端渲染时可以选择React提供的全栈框架Next.js或Vue提供的全栈框架Nuxt.js来进行开发,对于静态站点生成,react项目可以选择使用Next.js或Gatsby,vue项目可以选择Nuxt.js或VitePress

现在的全栈框架像Next.js、Nuxt.js已经能统一这些渲染模式了,我们在同一个项目中针对不同的页面,可以自由地选择是使用SSR,SSG还是CSR,可以让我们为每个页面选择最合适的技术,让我们的项目在性能、SEO优化等方面都得到最大提升

3、MVC MVVM的区别

面试中我们可以这样回答:

MVC和MVVM都是用于实现关注点分离的架构模式,将业务逻辑和数据管理分开,提高代码的可测试性、可维护性和可扩展性,但它们的核心区别在于层级间的数据方式不同

MVC(Model-View-Controller)Model:用于定义数据和业务逻辑,View:是UI界面,用于用户界面展示,Controller:接收用户输入,协调Model和view。MVC是单向通信的流程,用户操作View,View将事件传给Controller,Controller去修改Model,Model变更后再通知View更新。这个过程中Controller是中枢,需要手动或通过观察者模式来控制更新

MVVM(Model-View-ViewModel)Model:用于定义业务逻辑,View:代表UI视图,负责数据的展示,ViewModel:负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作。MVVM的核心是双向数据绑定,ViewModel是View的抽象,它包含了视图的状态和行为。View通过声明式绑定与ViewModel连接,两者自动同步。当Model变化,ViewModel自动更新,进而View自动更新,用户在View上的操作也会自动映射到ViewModel和Model。这使得View和Model完全解耦,开发者无需关心dom操作,只需要管理数据,提高了开发效率和可测试性

MVC在后端和传统前端中应用广泛,可以说MVVM是为了应对现代复杂前端交互而从MVC演进过来的模式,像Vue、Angular这类框架是非常经典和直接的MVVM模式的实现,React函数组件与Hooks的组合,思想也很接近MVVM

posted @ 2025-09-04 11:42  yfceshi  阅读(5)  评论(0)    收藏  举报