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. <BrowserRouter>
2. <HashRouter>
3. <Route>
4. <Redirect>
5. <Link>
6. <NavLink>
7. <Switch>
其他
  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 <Routes/><Route/>

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

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

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

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

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

  6. 写法区别:

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

1.2 <NavLink>

  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 <Navigate>

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

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: <App />
    // },
    {
        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属性可以书写子路由,但是子路由并不会直接展示在页面上,需要通过在展示子路由的页面上放置路由出口<Outlet />

路由表配置:

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 />
            }
        ]
    },
    // 直接是路由出口,就不需要在组件内写<Outlet/>
    {
        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 <Navigate to="/login" />
  // 写法2:编程式路由跳转
  const navigateTo = useNavigate()
  useEffect(() => {
    navigateTo('/login')
  }, [])
  return <></>
}

export default function App() {
  return (
    <>
      <AuthRouter />
    </>
  )
}

3. 总结:

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

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值