likes
comments
collection
share

React-Router V6全方位讲解

作者站长头像
站长
· 阅读数 38

前言

React-Router v6 发布于2021年底,相较于v5有很大的更新和升级,变化主要体现在下面几点:

  1. 不再依赖React,可以用于任何UI库中。
  2. 使用新声明式语法,不再使用静态的组件。
  3. 移除了一些API如withRouter,改为使用新Hooks。
  4. 支持自动生成路径。
  5. 更好的动态路由匹配。
  6. 支持嵌套路由。
  7. 更好的代码拆分和懒加载。
  8. 移除了一些不常用的API。
  9. 性能和包体积都有提升。
  10. 增加了一些对TypeScript的支持。

总体来说,React Router v6是一个遍地重构的大版本升级,使用全新的声明式API改进了路由的开发体验,并且提升了性能,虽然v6版本可以不依赖React,但是普遍还是在React中使用,下面我们也是在React项目中介绍它的基本使用方式和核心API使用场景。

路由的History模式和Hash模式

前端路由基本分成两种模式,History模式和Hash模式,这两种模式有各自的特点和使用场景

History模式

特点

  1. 使用HTML5 History API(pushState、replaceState和popstate事件)来管理浏览器历史记录和导航。
  2. 路由信息显示在URL的路径部分(例如:/about)。
  3. 不使用哈希符号(#),URL看起来更加干净美观。
  4. 可以通过服务器配置来支持直接在不同路径下加载应用,因为服务器会正确地返回主页面并在前端处理路由。
  5. 适合现代浏览器,支持HTML5 History API,并且需要漂亮的URL。

适用场景

当你有一个现代浏览器环境,并且你希望URL看起来更加干净,并且能够通过服务器配置来处理不同路径下的应用加载。

Hash模式

特点

  1. 使用URL的哈希部分(#)来模拟路由状态,即称为哈希路由。
  2. 路由信息保存在URL的哈希部分(例如:/#/about)。
  3. 适用于不支持HTML5 History API的老旧浏览器,因为哈希的变化不会发送到服务器,无需特殊服务器配置。
  4. 可能在URL中带有哈希,看起来可能不太美观。

适用场景

当你需要兼容老旧浏览器,或者不方便在服务器上进行配置时,可以选择哈希模式。也常用于单页应用程序(SPA)的静态文件服务器。

总结

  1. 使用"history"模式时,URL看起来干净,适合现代浏览器和有服务器配置的情况。
  2. 使用"hash"模式时,适合老旧浏览器,不需要特殊服务器配置,但URL可能会看起来较为杂乱。
  3. 选择合适的模式取决于你的项目需求、浏览器兼容性和服务器配置等因素。

React-Router在React项目中的使用

我们下面使用History路由模式讲解在React项目中如何使用

安装依赖包

React-Router在React项目中使用需要安装两个依赖包,分别是react-router-dom和history,安装时需要指定版本

npm install react-router-dom@6 history@5

项目入口文件链接路由

由于我们使用的是history模式,这里我们需要导入BrowserRouter,并在render根组件时将根组件包裹

import React from 'react'
import ReactDOM from 'react-dom/client'
import RouterApp from '@app/routerApp'
import { BrowserRouter } from 'react-router-dom'

const rootElement = document.getElementById('root') as HTMLElement
const root = ReactDOM.createRoot(rootElement)

root.render(
    <BrowserRouter>
        <RouterApp />
    </BrowserRouter>
)

routerApp.tsx引入路由配置组件

我们这里将路由配置抽离成独立的组件,并在routerApp.tsx根组件中引入路由配置组件

import React from 'react'
import Router from '@src/router'

function RouterApp() {
    return <Router />
}

export default RouterApp

使用Routes和Route组件配置路由

在v6版本中,配置路由有两种方式,使用路由组件和使用useRoutes,这里先介绍使用路由组件的形式

import React from 'react'
import { Routes, Route } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'

function Router() {

    return (
        <Routes>
            <Route path="/" element={<Login />} />
            <Route path="forgetPassword" element={<ForgetPassword />} />
            <Route path="main" element={<Main />} />
            <Route path="*" element={<div> This Page Not Exist</div>} />
        </Routes>
    )
}

export default Router

在配置路由时,所有路由Route都需要写在Routes内部。Route中传入path即为路由的路径,例如上述path="forgetPassword"即当路由为http://localhost:8000/forgetPassword时会匹配ForgetPassword组件。当path为/时为默认匹配,即直接访问http://localhost:8000时会进入登录页面。当path为*时为通配符,这个通配符路由会匹配所有未匹配到其他路由的路径,也就是说,它是一个“捕获一切”的路由。例如我们访问http://localhost:8000/abc此时没有匹配任何路由,页面就会显示This Page Not ExistRoute中传入element即为路由所匹配的组件,意味着当我们访问http://localhost:8000/forgetPassword时,将会加载ForgetPassword组件

使用useRoutes配置路由

import React from 'react'
import { useRoutes } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'

function Router() {

    const Routes = useRoutes([
        { path: '/', element: <Login /> },
        { path: 'forgetPassword', element: <ForgetPassword /> },
        { path: 'main', element: <Main /> },
        { path: '*', element: <div> This Page Not Exist</div> },
    ])

    return Routes
}

export default Router

使用useRoutes创建路由配置时,useRoutes接收一个数组,数组的每一项是一个路由对象,其中的pathelement与组件路由作用相同。useRoutes返回一个react.element元素

创建路由跳转链接

我们项目中默认匹配组件时Login组件,我们可以在login组件中添加一些链接,用于跳转到其他路由

import React, { memo } from 'react'
import { Link } from 'react-router-dom'
  
function Login() {

    return (
        <div>
            Login Page
            <br />
            <Link to="forgetPassword">forgetPassword</Link>
            <br />
            <Link to="main">login in</Link>
        </div>
    )
}

export default memo(Login)

路由跳转链接使用组件Link,组件的to属性的值就是会跳转到的路由路径。注意:link的to属性值,会自动拼接当前的路径,并以/分割。例如当前路径为http://localhost:8000,点击forgetPassword时会将跳转到http://localhost:8000/forgetPasswordlogin页面如下所示React-Router V6全方位讲解当点击forgetPasswordlogin in时会对应跳转到ForgetPasswordMain页面

配置嵌套路由

我们上面配置的路由都是一级路由,日常项目中我们常会遇到多级路由,此时我们就需要使用到路由嵌套,路由嵌套同样有两种写法。例如我们上述案例登录后,在Main组件中配置两个子路由userListaccountCenter

Main组件中配置链接

import React, { memo } from 'react'
import { Link, Outlet } from 'react-router-dom'
import './index.less'

function Main() {

    return (
        <div className="main">
            <div className="sidebar">
                <Link to="userList">userList</Link>
                <br />
                <Link to="accountCenter">accountCenter</Link>
            </div>
            <div className="content">
                <Outlet />
            </div>
        </div>
    )
}

export default memo(Main)

这里配置路由链接与一级路由类似,只是需要注意的是,这里点击userList时,跳转的地址是拼接了父路由地址的,即会跳转到地址http://localhost:8000/main/userList这里最关键的是Outlet组件,该组件类似一个占位符,即当匹配到的子路由后,子路由的内容将显示在Outlet区域

使用Routes和Route组件

import React from 'react'
import { Routes, Route } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'
import UserList from '@pages/routerDemo/userList'
import AccountCenter from '@pages/routerDemo/accountCenter'

function Router() {

    return (
        <Routes>
            <Route path="/" element={<Login />} />
            <Route path="forgetPassword" element={<ForgetPassword />} />
            <Route path="main" element={<Main />}>
                <Route index element={<UserList />} />
                <Route path="userList" element={<UserList />} />
                <Route path="accountCenter" element={<AccountCenter />} />
            </Route>
            <Route path="*" element={<div> This Page Not Exist</div>} />
        </Routes>
    )
}

export default Router

嵌套路由写法在使用Routes和Route组件配置嵌套路由时,直接使用父路由包裹子路由即可,子路由匹配路径需要拼接父路由的路径,例如访问http://localhost:8000/main/userList将在Main组件的Outlet处显示userList组件内容索引路由解释在上面Main的子路由中,第一个路由没有具体的path值,而是直接传入一个index,此时该路由称为Main的索引路由。索引路由的作用是,当我们访问http://localhost:8000/main时,并没有指定二级路由的时候,Outlet处显示的就是该索引路由对应的内容。如果没有配置索引路由,那当我们访问http://localhost:8000/main时,Outlet处显示的是空白,没有任何内容。

使用useRoutes

import React from 'react'
import { useRoutes } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'
import UserList from '@pages/routerDemo/userList'
import AccountCenter from '@pages/routerDemo/accountCenter'

function Router() {

    const Routes = useRoutes([
        { path: '/', element: <Login /> },
        { path: 'forgetPassword', element: <ForgetPassword /> },
        { 
            path: 'main', 
            element: <Main />,
            children: [
                { index: true, element: <UserList /> },
                { path: 'userList', element: <UserList /> },
                { path: 'accountCenter', element: <AccountCenter /> },
            ]
        },
        { path: '*', element: <div> This Page Not Exist</div> },
    ])
    
    return Routes
}

export default Router

在使用useRoutes时,嵌套路由配置在父路由对象的children属性中,children属性值也是一个数组,数组的每一项都是一个子路由,其中不设置path,而是设置index: true的,就是对应的索引路由,其作用和上面介绍的一样。

使用动态链接NavLink

动态链接NavLink和普通链接Link的主要区别就是NavLink可以获取到当前链接的路由是否是活动状态,可以根据该状态调整链接的样式。NavLink可以根据状态传入不同的className,style,和children,在传入这些值时,需要传入一个函数,该函数接收两个参数,分别是isActive, isPending,其中isActive是活动状态,指当前路由是匹配状态。isPending是进行中的状态,指路由被匹配,还在加载中,一般是懒加载的路由在加载的过程中。根据不同状态设置className

<NavLink
    to="userList"
    className={({ isActive, isPending }) => {
        return isPending ? 'pending' : isActive ? 'active' : ''
    }}
    >
    userList
</NavLink>

根据不同状态设置style

<NavLink
    to="accountCenter"
    style={({ isActive, isPending }) => {
        return isPending ? { color: 'red' } : isActive ? { color: 'green' } : {}
    }}
    >
    accountCenter
</NavLink>

根据不同状态设置children

<NavLink
    to="accountCenter"
    style={({ isActive, isPending }) => {
        return isPending ? { color: 'red' } : isActive ? { color: 'green' } : {}
    }}
    >
    {({ isActive, isPending }) => { return isPending ? 'pending' : isActive ? 'accountCenter' : '' }}
</NavLink>

使用参数路由,并使用useParams获取参数

参数路由路由配置

所谓参数路由,就是不使用具体的路径名,而是使用参数占位,使用Route配置参数路由写法为<Route path=":userId" element={<UserInfo />} />,path的值是个: + 参数名的形式,例如我们在上面例子,在userlist路由下再加入userInfo的参数子路由,参数为userId

import React from 'react'
import { Routes, Route } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'
import UserList from '@pages/routerDemo/userList'
import AccountCenter from '@pages/routerDemo/accountCenter'
import UserInfo from '@pages/routerDemo/userInfo'

function Router() {

    return (
        <Routes>
            <Route path="/" element={<Login />} />
            <Route path="forgetPassword" element={<ForgetPassword />} />
            <Route path="main" element={<Main />}>
                <Route index element={<UserList />} />
                <Route path="userList" element={<UserList />}>
                    <Route path=":userId" element={<UserInfo />} />
                </Route>
                <Route path="accountCenter" element={<AccountCenter />} />
            </Route>
            <Route path="*" element={<div> This Page Not Exist</div>} />
        </Routes>
    )
}

export default Router

此时我们不管访问http://localhost:8000/main/userList下面的任何路由,最后一级都会被解析为路由参数,例如http://localhost:8000/main/userList/1001此时最后一级路由1001就是路由参数。此时我们就可以使用useParams获取该参数。配置参数路由在useRoutes中的写法如下

import React from 'react'
import { useRoutes } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'
import UserList from '@pages/routerDemo/userList'
import AccountCenter from '@pages/routerDemo/accountCenter'
import UserInfo from '@pages/routerDemo/userInfo'

function Router() {

    const Routes = useRoutes([
        { path: '/', element: <Login /> },
        { path: 'forgetPassword', element: <ForgetPassword /> },
        {
            path: 'main',
            element: <Main />,
            children: [
                { index: true, element: <UserList /> },
                {
                    path: 'userList',
                    element: <UserList />,
                    children: [
                        { path: ':userId', element: <UserInfo /> }
                    ]
                },
                { path: 'accountCenter', element: <AccountCenter /> },
            ]
        },
        { path: '*', element: <div> This Page Not Exist</div> },
    ])

    return Routes
}

export default Router

参数路由跳转链接配置

import React, { memo } from 'react'
import { Link, Outlet } from 'react-router-dom'

function UserList() {

    const userList = [
        {
            id: 1001,
            name: '张三',
            age: 18
        },
        {
            id: 1002,
            name: '李四',
            age: 19
        },
        {
            id: 1003,
            name: '王五',
            age: 20
        }
    ]

    return (
        <div>
            UserList Page
            <br />
            <ul>
                {
                    userList.map((user) => (
                        <React.Fragment key={user.id}>
                            <Link to={`${user.id}`}>{user.name}</Link>
                            <br />
                        </React.Fragment>
                    ))
                }
            </ul>
            <br />
            <Outlet />
        </div>
    )
}

export default memo(UserList)

使用useParams获取url参数

我们可以在链接跳转到对应参数路由后,在任意已经渲染的组件中获取到该路由参数,我们此时在UserInfo组件中获取为例,获取后在页面中显示出来

import React, { memo } from 'react'
import { useParams } from 'react-router-dom'
  
function UserInfo() {

    const { userId } = useParams()

    return (
        <div>
            UserList Page {userId}
        </div>
    )
}

export default memo(UserInfo)

此时当我们点击任意一个参数链接后,都可以对应获取到参数值React-Router V6全方位讲解

路由优先匹配具名路由

这里有个需要特别注意的点,就是当我们子路由中有参数路由具名路由时,会优先匹配具名路由,例如我在上面案例中,在userList路由下新增一个test路由

<Route path="userList" element={<UserList />}>
    <Route path=":userId" element={<UserInfo />} />
    <Route path="test" element={<div> This is Test Page </div>} />
</Route>

此时如果我们访问http://localhost:8000/main/userList/test,此时匹配的就是第二个子路由,页面显示的也是This is Test Page,此时在任何组件中使用useParams是无法获取到参数的,这是路由匹配优先级决定的。若我们没有<Route path="test" element={<div> This is Test Page </div>} />这个子路由,访问http://localhost:8000/main/userList/test时最后的test就会解析成路由参数,也就可以使用useParams获取到userId的值,值为test

使用useSearchParams设置、获取搜索参数

搜索参数和url参数不同,搜索参数不会改变当前的路由路径,而是直接在路由后面添加参数,与get请求传参写法一致,都是在路由路径的末尾加上?key=value的形式,多个参数之间使用&符号连接。在react-router中,我们使用useSearchParams返回两个值,[searchParams, setSearchParams]其中searchParams.get('参数名')用来获取参数值,setSearchParams({key1: value1, key2: value2})用来设置参数值,当我们调用setSearchParams之后,当前路由上就会拼接我们对象中的所有参数。例如我们在UserInfo组件中使用

import React, { memo } from 'react'
import { useParams, useSearchParams } from 'react-router-dom'
  
function UserInfo() {

    const { userId } = useParams()
    const [searchParams, setSearchParams] = useSearchParams()

    return (
        <div>
            UserList Page {userId} {searchParams.get('name')}
            <br />
            <button onClick={() => setSearchParams({ name: '张三', age: '18' })}>张三</button>
        </div>
    )
}

export default memo(UserInfo)

当我们点击按钮后,页面呈现如下React-Router V6全方位讲解

路由传参,使用useLocation获取路由信息

在我们使用Link跳转路由时,除了使用上面说的url传参和搜索参数传参之外,还可以直接传入state参数,我们先来看一下Link组件源码中定义的props有哪些内容React-Router V6全方位讲解我们能看到Link可以接收一个state的props,该props类型任意,代码中我们实现如下例如我们在UserList组件中每个跳转连接中,都将用户信息对象通过state传入

import React, { memo } from 'react'
import { Link, Outlet } from 'react-router-dom'

function UserList() {

    const userList = [
        {
            id: 1001,
            name: '张三',
            age: 18
        },
        {
            id: 1002,
            name: '李四',
            age: 19
        },
        {
            id: 1003,
            name: '王五',
            age: 20
        }
    ]

    return (
        <div>
            UserList Page
            <br />
            <ul>
                {
                    userList.map((user) => (
                        <React.Fragment key={user.id}>
                            <Link to={`${user.id}`} state={{ ...user }}>{user.name}</Link>
                            <br />
                        </React.Fragment>
                    ))
                }
                <Link to="test">Test</Link>
            </ul>
            <br />
            <Outlet />
        </div>
    )
}

export default memo(UserList)

此时我们便可以在UserInfo组件中使用useLocation获取当前的路由信息,当前的路由信息中就包含state

import React, { memo, useEffect } from 'react'
import { useParams, useSearchParams, useLocation } from 'react-router-dom'
  
function UserInfo() {

    const { userId } = useParams()
    const [searchParams, setSearchParams] = useSearchParams()

    const location = useLocation()

    useEffect(() => {
        console.log('location', location)
    }, [location])

    return (
        <div>
            UserList Page {userId} {searchParams.get('name')}
            <br />
            <button onClick={() => setSearchParams({ name: '张三', age: '18' })}>张三</button>
        </div>
    )
}

export default memo(UserInfo)

我们打印出的location值如下React-Router V6全方位讲解此时我们使用location.state就可以获取到路由传过来的参数值。

使用useNavigate实现编程式导航

我们上面所用的导航形式都是使用Link或者NavLink标签实现,我们也可以在代码中实现导航,此时我们需要用到useNavigate钩子函数,该钩子函数返回一个函数navigatenavigate可以接收两个参数,第一个参数是路由路径,第二个是一个对象,对象中可以传入一个state属性,该属性就是useLocation获取路由信息中的state,这种方式也成为编程式导航传参例如我们将Main中的Link替换成编程式导航

import React, { memo } from 'react'
import { Outlet, useNavigate } from 'react-router-dom'
import './index.less'

function Main() {

    const navigate = useNavigate()

    // 跳转到指定页面
    const toPage = (path: string, state: { [key: string]: any }) => {
        navigate(path, { state })
    }

    return (
        <div className="main">
            <div className="sidebar">
                <button onClick={() => toPage('userList', { path: 'userList' })}>userList</button>
                <br />
                <button onClick={() => toPage('accountCenter', { path: 'accountCenter' })}>accountCenter</button>
            </div>
            <div className="content">
                <Outlet />
            </div>
        </div>
    )
}

export default memo(Main)

此时我们点击这两个按钮时,同样会跳转到对应路由地址,并且在对应的路由下使用useLocation也能获取到state的值。

转载自:https://juejin.cn/post/7268011328940933155
评论
请登录