• react笔记-03react-router篇


    本文章是react的路由笔记

    一、react路由(v5版本)

    1. 什么是路由(前端)?

    1. 一个路由就算一个映射关系(key: value)
    2. key为路径,value为组件

    2. 前端路由的工作原理

    根据浏览器历史记录:history(BOM)

    3. 路由的基本使用(react-router-dom)

    3.1 react-router:(有三种库)

    • web (react-router-dom)
    • react-native
    • any(react-router)

    3.2 react-router-dom 的api(版本5/v5)

    安装
    npm i react-router-dom@5
    
    内置组件
    1. 
    2. 
    3. 
    4. 
    5. 
    6. 
    7. 
    
    其他
    1. history 对象
    2. match 对象
    3. withRouter 函数

    3.3 基本使用

    App.js中

    import { Component } from 'react'
    import { Link, Route } from 'react-router-dom'
    import Home from './pages/Home'
    import About from './pages/About'
    
    export default class App extends Component {
    	render() {
    		return {
    			<div>
    				<header>
    					{/* 编写路由链接 */}
    					<Link to="/home">首页</Link>
    					<Link to="/about">关于</Link>
    				</header>
    				<main>
    					{/* 注册路由 */}
    					<Route path="/home" component={Home} />
    					<Route path="/about" component={About} />
    				</main>
    			</div>
    		}
    	}
    }
    

    index.js中

    import React from 'react'
    import ReactDOM from 'react-dom'
    import { BrowserRouter, HashRouter } from 'react-router-dom'
    import App from './App'
    
    ReactDOM.render(
    	{/* history路由 */}
    	<BrowserRouter>
    		<App />
    	</BrowserRouter>
    	{/* hash路由 */}
    	<HashRouter></HashRouter>
    	, document.querySelect('#root'))
    

    3.4 路由组件与一般组件

    一般组件的props信息需要通过父组件传递,而路由组件的props信息是路由信息,不需要传递。

    3.5 NavLink的使用

    1. NavLink的基本使用

    匹配上的路由会默认加上active这个类名

    <NavLink to="/home">首页</NavLink>
    <NavLink to="/about">关于</NavLink>
    

    通过activeClassName可以自定义选中的类名

    <NavLink to="/home" activeClassName="header-active">首页</NavLink>
    <NavLink to="/about" activeClassName="header-active">关于</NavLink>
    
    1. NavLink的封装

    组件的使用:

    {/* 「首页」标题:组件标签标签体内容(特殊的标签属性,组件内可以通过this.props.children获取) */}
    <MyNavLink to="/home">首页</MyNavLink>
    <MyNavLink to="/about">关于</MyNavLink>
    

    组件的封装:

    {/* 一组props */}
    <NavLink to={to} activeClassName="header-active">首页</NavLink>
    {/* 多组props */}
    <NavLink {...this.props} activeClassName="header-active"/>
    

    3.6 Switch的使用

    Switch:当匹配到第一个路由的时候就停止

    <Switch>
      <Route path="/home" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/about" component={Demo} />
    </Switch>
    

    3.7 路由的模糊匹配与精准匹配

    模糊匹配:

    <header>
      <NavLink to="/about/a/b">关于</NavLink>
    </header>
    <main>
      <Switch>
        <Route path="/about" component={About} />
      </Switch>
    </main>
    

    精准匹配:exact={true}

    <Route exact={true} path="/about" component={About} />
    

    3.8 Redirect的使用

    Redirect(重定向):谁都匹配不上,就走Redirect

    <Switch>
      <Route path="/home" component={Home} />
      <Route path="/about" component={About} />
      <Redirect to="/about" />
    </Switch>
    

    3.9 嵌套路由

    嵌套路由要写完整路径

    <div>
      <NavLink to="/home/news">News</NavLink>
      <NavLink to="/home/message">Message</NavLink>
    </div>
    <div>
      <Switch>
        <Route path="/home/news" component={News} />
        <Route path="/home/message" component={Message} />
      </Switch>
    </div>
    

    3.10 路由组件传参

    1. 向路由组件传递params参数

    传递的参数:

    <NavLink to={`/home/news/${title}/${page}`}>News</NavLink>
    <Route path="/home/news/:title/:page" component={News} />
    

    News组件可以通过this.props.match.params获取到params参数

    2. 向路由组件传递search(query)参数(显式传参)

    使用:

    <NavLink to={`/home/news?title=${title}&page=${page}`}>News</NavLink>
    
    {/* search参数无需声明接收,正常注册路由即可 */}
    <Route path="/home/news" component={News} />
    

    接收:

    接收到的search参数是urlencoded编码的字符串,需要借助querystring解析

    key=value&key=value => urlencoded编码

    安装query-string

    npm i query-string
    
    import qs from 'query-string'
    
    qs.parse(this.props.location.search)
    
    3. 向路由组件传递state参数(隐式传参)

    使用:

    <NavLink to={{pathname: '/home/news', state: { title, page }}}>News</NavLink>
    
    {/* state参数无需声明接收,正常注册路由即可 */}
    <Route path="/home/news" component={News} />
    

    接收:

    通过this.props.location.state接收,BrowserRouter刷新参数不丢失,HashRouter刷新参数丢失

    3.11 push与replace模式

    • push:会留下痕迹,可以通过回退键,回到上一页
    • replace:不会留下痕迹,不能回到上一页

    3.12 编程式路由导航

    // 路由跳转
    this.props.history.replace('/xxx', [state])
    this.props.history.replace('/xxx', { id, title })
    this.props.history.push('/xxx', [state])
    this.props.history.push('/xxx', { id, title })
    // 路由前进
    this.props.history.goForward()
    // 路由后退
    this.props.history.goBack()
    // 与vue中的go类似,传入number,正数表示前进,负数表示后退
    this.props.history.go(number)
    

    3.13 withRouter的使用

    在一般组件内,不能像路由组件一样获取到路由信息,需要在组件上包裹一个withRouterwithRouter可以加工一般组件,让一般组件具备路由组件所特有的api,withRouter的返回值是一个新组件。

    import React, { Component } from 'react'
    import { withRouter } from 'react-router-dom'
    
    class App extends Component {
      handleClick = () => {
        console.log(this.props)
      }
    
      render() {
        return (
          <div onClick={this.handleClick}>关于</div>
        )
      }
    }
    
    export default withRouter(App)
    

    二、react路由(v6版本)

    1. Component

    1.1

    1. v6版本中移除了之前的,用代替

    2. 要配合使用,且必须要用包裹

    3. 属性用于指定:匹配时是否区分大小写(默认为false)

    4. 也可以嵌套使用,且可配合useRoutes()配置路由表,但需要通过组件来渲染其子路由

    5. end属性:如果他的子路由匹配,父亲路由失去高亮

    6. 写法区别:

      import { Routes, Route, Navigate } from 'react-router-dom'
      
      {/* Switch组件 替换成 Routes */}
      <Routes>
          {/* Route的component属性变成element属性 */}
          {/* v5写法: */}
          {/* v6写法:} /> 写成标签 */}
          <Route caseSensitive path='/home' element={<Home />} />
          <Route path='/about' element={<About />} />
          {/* 重定向,不用Redirect,而是Navigate */}
          {/* 用法: */}
          {/* replace跳转模式:push/replace模式,默认false(push) */}
          <Route path='/' element={<Navigate to="/home" />} />
        	{/* end属性:如果他的子路由匹配,父亲路由失去高亮 */}	
        	<Route path='/' end element={<Navigate to="/home" />} />
      </Routes>
      

    1.2

    1. 自定义选中的高亮样式

      {/* v5写法 */}
      <NavLink activeClassName="xxx">
      {/* v6写法:在className属性中写入函数,函数返回类名 */}
      <NavLink to='/home' className={({isActive}) => { return isActive ? 'item active' : 'item' }}>Home</NavLink>
      
      <NavLink to='/home' className={(e) => {console.log(e);}}>Home</NavLink>
      

      下面是的参数e:

      在这里插入图片描述

    1.3

    1. :重定向,不用Redirect,而是Navigate。渲染到页面上,会让路由进行跳转。
    2. 用法:
    3. replace属性:路由跳转模式,push/replace模式,默认false(push)

    2. Hooks

    2.1 useRoutes()

    注册路由:

    • 写法1:
    {/* 注册路由 */}
    <Routes>
      <Route path='/home' element={<Home />} />
      <Route path='/about' element={<About />} />
      <Route path='/' element={<Navigate to="/home" />} />
    </Routes>
    
    • 写法2:由useRoutes生成路由表
    import React from 'react'
    import { NavLink, Navigate, useRoutes } from 'react-router-dom'
    import About from './pages/About'
    import Home from './pages/Home'
    
    export default function App() {
    
      // 通过路由表生成路由
      const routeElement = useRoutes([
        {
          path: '/home',
          element: <Home />
        },
        {
          path: '/about',
          element: <About />
        },
        {
          path: '/',
          element: <Navigate to="/home" />
        }
      ])
    
      return (
        <div>
          <NavLink to='/home'>Home</NavLink> | <NavLink to='/about'>About</NavLink>
          <hr />
          {/* 注册路由 */}
          { routeElement }
        </div>
      )
    }
    

    2.2 嵌套路由

    在路由表添加children属性即可

    const routeElement = useRoutes([
      {
        path: '/home',
        element: <Home />
      },
      {
        path: '/about',
        element: <About />,
        children: [
          {
            path: 'news',
            element: <div>news</div>
          },
          {
            path: 'message',
            element: <div>message</div>
          }
        ]
      },
      {
        path: '/',
        element: <Navigate to="/home" />
      }
    ])
    

    About组件中,需要一个路由出口,用Outlet来展示二级子路由

    import React from 'react'
    import { NavLink, Outlet } from 'react-router-dom'
    
    export default function index() {
      return (
        <div>About
          <br />
          {/* v5写法 */}
          <NavLink to="/about/news">news</NavLink> | <NavLink to="/about/message">message</NavLink>
          {/* v6中:同v5,也可以只写子路由路径,但是不能带斜杠,类似于vue */}
          <NavLink to="news">news</NavLink> | <NavLink to="message">message</NavLink>
          {/* 路由出口 */}
          <Outlet />
        </div>
      )
    }
    

    2.3 useParams()(获取params参数)

    useParams()获取传递的params参数

    import { useParams } from 'react-router-dom'
    

    2.4 useMatch()(获取params参数)

    useMatchuseParams都可以用来读取params参数

    使用:useMatch('/home/:id/:title')

    2.5 useSearchParams()(获取search参数)

    useSearchParams用来获取search(query)参数,返回的是一个数组

    1. searchParams:获取到search参数,但是需要通过get()方法获取
    // 我的url为http://localhost:5173/about/news?id=1&title=haha
    const [searchParams, setSearchParams] = useSearchParams()
    // 需要通过get方法,传入key,获取到value
    let id = searchParams.get('id'))
    
    1. setSearchParams:更改search参数
    setSearchParams('id=2&title=hello')
    

    2.6 useLocation()(获取search、state参数)

    1. 获取search参数:

    useLocationuseSearchParams都可以获取search参数,不用像useMatch那样传递路径。

    1. 获取state参数:

    传递state参数(隐式传参):

    {/* v5写法 */}
    <NavLink to={{ pathname: '/home', state: {name: 'xiaotian', age: 20} }}>Home</NavLink>
    {/* v6写法 */}
    <NavLink to='/home' state={{ name: 'xiaotian', age: 20 }}>Home</NavLink>
    
    1. 使用useLocation()
    // 对象的连续解构
    const { state: {name, age} } = useLocation()
    

    2.7 路由传参总结

    params => useParams、useMatch

    search(query) => useSearchParams、useLocation

    state => useLocation

    2.8 编程式路由导航

    let navigate = useNavigate()
    // 1. 基本使用
    navigate('/about')
    // 2. 子路由
    navigate('news')
    // 3. 传递state参数
    navigate('news', {
      replace: false,
      state: {
        name: 'xiaotian',
        age: 20
      },
    })
    // 4. 前进
    navigate(1)
    // 5. 后退
    navigate(-1)
    

    2.9 useInRouterContext()

    1. 作用:用于判断组件是否被路由包裹着
    2. 返回值:boolean

    2.10 useNavigationType()

    1. 作用:返回当前的导航类型(用户是如何来到当前页面的)
    2. 返回值:poppushreplace
    3. 备注:pop是指直接在浏览器中打开了这个路由组件(复制链接、刷新进入)

    2.11 useOutlet()

    1. 作用:用来呈现当前组件中渲染的嵌套路由
    2. 返回值:如果嵌套路由没有挂载,则res返回null;如果嵌套路由已经挂载,则展示嵌套路由对象。
    let res = useOutlet()
    

    2.12 useResolvedPath

    作用:给定一个url值,解析其中的pathsearchhash值。

    三、项目中如何配置路由表(v6版本)

    1. 标签形式

    1. src下创建router目录,新建index.jsx

    这里用到了嵌套路由的写法

    import { BrowserRouter, Routes, Route } from 'react-router-dom'
    import App from '../App'
    import Home from '../views/Home/Home'
    import About from '../views/About/About'
    
    const baseRouter = () => {
        return (
            <BrowserRouter>
                <Routes>
                	{/* 嵌套路由写法 */}
                    <Route path='/' element={<App />}>
                        <Route path='/home' element={<Home />}></Route>
                        <Route path='/about' element={<About />}></Route>
                    </Route>
                </Routes>
            </BrowserRouter>
        )
    }
    
    export default baseRouter
    
    1. main.jsx中将原有的App根组件替换成路由表组件
    import ReactDOM from 'react-dom/client'
    import BaseRouter from './router/index.jsx'
    
    ReactDOM.createRoot(document.getElementById('root')).render(
      <BaseRouter />,
    )
    
    1. App.jsx中配置路由出口
    import { Outlet } from 'react-router-dom'
    
    export default function App() {
      return (
        <div>
          {/* 占位符,用于展示组件,类似于vue的router-view(路由出口) */}
          <Outlet />
        </div>
      )
    }
    

    2. Hook形式

    2.1 基本使用

    1. src下创建router目录,新建index.jsx
    import { Navigate } from 'react-router-dom'
    import Home from '../views/Home/Home'
    import About from '../views/About/About'
    
    const routes = [
        // 当App组件使用了useRoutes渲染路由时,就不允许在路由表中配置App组件有关的路由
        // 错误写法:会导致页面不停渲染,最终卡死
        // {
        //     path: '/',
        //     element: 
        // },
        {
            path: '/',
            element: <Navigate to="/home" />
        },
        {
            path: '/home',
            element: <Home />
        },
        {
            path: '/about',
            element: <About />
        },
        // 错误页面,重定向到404
        {
            path: '*',
            element: <Navigate to="/404" />
        }
    ]
    
    export default routes
    
    1. main.jsx中用BrowserRouterHashRouter包裹一下根组件
    import React from 'react'
    import ReactDOM from 'react-dom/client'
    import { BrowserRouter } from 'react-router-dom'
    import App from './App.jsx'
    
    ReactDOM.createRoot(document.getElementById('root')).render(
      <BrowserRouter>
        <App />
      </BrowserRouter>,
    )
    
    1. App.jsx中渲染路由
    import { useRoutes } from 'react-router-dom'
    import router from './router'
    
    export default function App() {
      const outlet = useRoutes(router)
      return (
        <div>
        	// 渲染路由
          	{ outlet }
        </div>
      )
    }
    

    2.2 路由懒加载

    router/index.jsx中,使用lazy即可实现路由的懒加载

    import { lazy } from 'react'
    
    const Home = lazy(() => import('../views/Home/Home'))
    

    但是通常情况下,lazySuspense是结合使用的,不然会出现报错(但是我目前写lazy没有出现报错),可以通过如下代码书写我们的懒加载路由:

    import { lazy, Suspense } from 'react'
    import { Navigate } from 'react-router-dom'
    
    const Home = lazy(() => import('../views/Home/Home'))
    const About = lazy(() => import('../views/About/About'))
    
    const routes = [
        {
            path: '/',
            element: <Navigate to="/home" />
        },
        {
            path: '/home',
            element: (
                // 当路由很多时,书写这段代码会显得非常繁琐,可以封装一个withLoadingComponent方法
                <Suspense fallback={<div>loading...</div>}>
                    <Home />
                </Suspense>
            )
        },
        {
            path: '/about',
            element: withLoadingComponent(<About />),
        }
    ]
    
    // comp的ts类型为JSX.Element
    const withLoadingComponent = (comp) => {
        return (
            <Suspense fallback={<div>loading...</div>}>
                { comp }
            </Suspense>
        )
    }
    
    export default routes
    

    2.3 嵌套路由

    路由表通过children属性可以书写子路由,但是子路由并不会直接展示在页面上,需要通过在展示子路由的页面上放置路由出口

    路由表配置:

    const routes = [
        {
            path: '/',
            element: <Navigate to="/home" />
        },
        {
            path: '/home',
            element: <Home />
        },
        // 该写法需要在About组件写路由出口
        {
            path: '/about',
            element: <About />,
            // 子路由
            children: [
                {
                	// 子路由:可以写全路径
                    path: '/about/news',
                    element: <News />
                },
                {
                	// 子路由:写二级路径,但不要前面的'\'
                    path: 'message',
                    element: <Message />
                },
                {
                	// 子路由:默认展示Hello组件
                    index: true,
                    element: <Hello />
                }
            ]
        },
        // 直接是路由出口,就不需要在组件内写
        {
            path: '/layout',
            element: <Outlet />,
            // 子路由
            children: [
                {
                    path: '/layout/news',
                    element: <News />
                }
            ]
        }
    ]
    

    路由出口:(在About组件内)

    import React from 'react'
    import { Outlet } from 'react-router-dom'
    
    export default function about() {
      return (
        <div>
          about
          <Outlet />
        </div>
      )
    }
    

    3. 使用内置方法形式

    3.1 在router/index.jsx中:

    使用createBrowserRouter创建BrowserRouter路由:

    import { createBrowserRouter } from 'react-router-dom'
    import Layout from '../views/layout'
    import Login from '../views/login'
    
    const router = createBrowserRouter([
        {
            path: '/',
            element: <Layout />
        },
        {
            path: '/login',
            element: <Login />
        }
    ])
    
    export default router
    

    3.2 在main.jsx中:

    引入内置组件RouterProvider,替换用BrowserRouter包裹App组件的写法

    import React from 'react'
    import ReactDOM from 'react-dom/client'
    // 引入RouterProvider组件
    import { RouterProvider } from 'react-router-dom'
    import router from './router/index.jsx'
    
    ReactDOM.createRoot(document.getElementById('root')).render(
      <RouterProvider router={router} />
    )
    

    四、实现路由守卫

    1. 实现思路1:

    注册路由是三、3使用内置组件的方法

    1. router/index.jsx中:通过组件的children属性,将需要展示的组件包裹在封装的路由守卫组件中。
    import { createBrowserRouter } from 'react-router-dom'
    import Layout from '../views/layout'
    import Login from '../views/login'
    // 路由守卫组件
    import AuthRouter from './AuthRouter'
    
    const router = createBrowserRouter([
        {
            path: '/',
            // 因为组件中的内容会通过props的children属性传递给子组件
            element: <AuthRouter><Layout /></AuthRouter>
        },
        {
            path: '/login',
            element: <Login />
        }
    ])
    
    export default router
    
    1. AuthRouter.jsx中:判断是否有token,有则加载Layout组件内容,没有则跳转到Login页面
    // 封装高阶组件 路由鉴权组件
    import { Navigate } from "react-router-dom"
    
    const AuthRouter = ({ children }) => {
        const token = localStorage.getItem('token')
        if (token) {
            return <>{ children }</>
        } else {
            return <Navigate to='/login' replace />
        }
    }
    
    export default AuthRouter
    

    2. 实现思路2:

    1. main.jsx
    import React from 'react'
    import ReactDOM from 'react-dom/client'
    import { BrowserRouter } from 'react-router-dom'
    import App from './App.jsx'
    
    ReactDOM.createRoot(document.getElementById('root')).render(
      <BrowserRouter>
        <App />
      </BrowserRouter>
    )
    
    1. App.jsx
    import { useRoutes, useLocation, useNavigate, Navigate } from 'react-router-dom'
    import { useEffect } from 'react'
    import Layout from './views/layout'
    import Login from './views/login'
    
    const router = [
      {
        path: '/',
        element: <Layout />
      },
      {
        path: '/login',
        element: <Login />
      }
    ]
    
    const AuthRouter = () => {
      const outlet = useRoutes(router)
      const location = useLocation()
    
      let token = localStorage.getItem('token')
      if (location.pathname !== '/login' && !token) {
        // 这里不能直接使用useNavigate进行跳转,因为需要的是一个jsx组件
        return <ToLogin />
      } else {
        return outlet
      }
    }
    
    const ToLogin = () => {
      // 写法1:可以直接用Navigate组件
      // return 
      // 写法2:编程式路由跳转
      const navigateTo = useNavigate()
      useEffect(() => {
        navigateTo('/login')
      }, [])
      return <></>
    }
    
    export default function App() {
      return (
        <>
          <AuthRouter />
        </>
      )
    }
    

    3. 总结:

    思路1和思路2的核心都是判断是否有token,没有token就跳转登陆页,区别是用编程式导航调整和用组件进行跳转。

  • 相关阅读:
    企业想过等保,其中2FA双因素认证手段必不可少
    小马哥的CSS驿站
    【vSphere 8 自签名证书】企业 CA 签名证书替换 vSphere Machine SSL 证书Ⅰ—— 生成 CSR
    达之云BI平台助力中国融通集团陕西军民服务社有限公司实现数字化运营
    C++实现的动态规划求解分解为若干素数之和的方案总数
    数据库——集群与读写分离 <--->设计优化【补】
    利用正则表达式限制网页表单里的文本框输入内容
    rsync+inotify远程同步
    漏洞复现----43、Spring Cloud Function SpEL 代码注入 (CVE-2022-22963)
    【Python】10 自动选课
  • 原文地址:https://blog.csdn.net/m0_65519288/article/details/139758947