ReactJS语法-组件的路由

路由的使用场景:有几个tabs,点击每个tab展示不同的页面布局内容,同时浏览器窗口显示对应页面的路径。

基本使用

  1. 安装React Router: npm install react-router-dom

BrowserRouter或HashRoute

  1. Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件;
  2. BrowserRouter使用history模式;
  3. HashRouter使用hash模式;
  4. 举例:

     //入口文件index.js
     import ReactDOM from "react-dom/client"
     import App from "./App"
     import { HashRouter } from "react-router-dom"
        
     const root = ReactDOM.createRoot(document.querySelector("#root"))
     root.render(
         <HashRouter>
             <App/>
         </HashRouter>
     )
    
    1. HashRouter 表示一个路由的跟容器,将来,所有的路由相关的东西,都要包裹在 HashRouter 里面,而且,一个网站中,只需要使用一次 HashRouter 就好了.

路由映射配置

  1. Routes:包裹所有的Route,在其中匹配一个路由
    1. Router5.x使用的是Switch组件
    2. 更早版本不需要任何包裹,直接使用<Route path="/home" component={Home}></Route>
  2. Route:Route用于路径的匹配;
    1. path属性:用于设置匹配到的路径;
    2. element属性:设置匹配到路径后,渲染的组件
      1. Router5.x使用的是component属性
  3. exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件;
    1. Router6.x不再支持该属性

路由跳转

  1. 通常路径的跳转是使用Link组件,最终会被渲染成a元素;
  2. to属性:Link中最重要的属性,用于设置跳转到的路径

举例使用

import React from 'react'
import { Link, Route, Routes } from 'react-router-dom'
import Home from './pages/Home'
import About from "./pages/About"
import routes from './router'
import "./style.css"

export function App(props) {
  return (
    <div className='app'>
      <div className='header'>
        <span>header</span>
        <div className='nav'>
          <Link to="/home">首页</Link>
          <Link to="/about">关于</Link>
        </div>
        <hr />
      </div>
      <div className='content'>
        {/* 映射关系: path => Component */}
        <Routes>
          <Route path='/home' element={<Home/>}>
          <Route path='/about' element={<About/>}/>
        </Routes> 
      </div>
      <div className='footer'>
        <hr />
        Footer
      </div>
    </div>
  )
}
export default App

基础知识(Router低版本使用方式)

  1. 入口文件main.js

     // JS打包入口文件
     // 1. 导入包
     import React from 'react'
     import ReactDOM from 'react-dom'
     import App from './App.jsx'
        
     // 使用 render 函数渲染 虚拟DOM
     ReactDOM.render(<App></App>, document.getElementById('app'))
    
  2. App.jsx

     //1. 安装react-router-dom: npm install react-router-dom
     import React from 'react'
     // 2. 导入 路由模块HashRouter、Route、Link
     import { HashRouter, Route, Link } from 'react-router-dom'
     import Home from './components/Home.jsx'
     import Movie from './components/Movie.jsx'
     import About from './components/About.jsx'
        
        
     export default class App extends React.Component {
       constructor(props) {
         super(props)
         this.state = {}
       }
        
       render() {
         {/* 
             1. 当 使用 HashRouter 把 App 根组件的元素包裹起来之后,网站就已经启用路由了
             2. 在一个 HashRouter 中,只能有唯一的一个根元素
             3. 在一个网站中,只需要使用 唯一的一次 <HashRouter></HashRouter> 就行了
         */}
         return <HashRouter>
           <div>
             <h1>这是网站的APP根组件</h1>
             <hr />
             {/*Link 表示一个路由的链接 */}
             <Link to="/home">首页</Link>&nbsp;&nbsp;
             <Link to="/movie">电影</Link>&nbsp;&nbsp;
             <Link to="/about">关于</Link>
             <hr />
                
             {/* 
             1. Route 表示一个路由规则, 在 Route 上,有两个比较重要的属性, path   component,其中 path 表示要匹配的路由,component 表示要展示的组件 
             2. Route 具有两种身份:1. 它是一个路由匹配规则; 2. 它是 一个占位符,表示将来匹配到的组件都放到这个位置
             3. 如果想让路由规则,进行精确匹配,可以为 Route,添加 exact 属性,表示启用精确匹配模式 
             */}
             <Route path="/home" component={Home}></Route>
             <hr />
             <Route path="/movie" component={Movie} exact></Route>
             <hr />
             <Route path="/about" component={About}></Route>
           </div>
         </HashRouter>
       }
     }
    
  3. 分别创建Home.jsx、Movie.jsx、About.jsx

     //Home.jsx
     import React from 'react'
     export default class Home extends React.Component {
       constructor(props) {
         super(props)
         this.state = {}
       }
        
       render() {
         return <div>
           Home
         </div>
       }
     }
        
     //Movie.jsx
     import React from 'react'
     export default class Movie extends React.Component {
       constructor(props) {
         super(props)
         this.state = {}
       }
        
       render() {
         return <div>
         move
         </div>
       }
     }
        
     //About.jsx
     import React from 'react'
     export default class About extends React.Component {
       constructor(props) {
         super(props)
         this.state = {}
       }
        
       render() {
         return <div>
           About
         </div>
       }
     }
    
  1. Navigate用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中
  2. 案例:
    1. 用户跳转到Profile界面
    2. 但是在Profile界面有一个isLogin用于记录用户是否登录:
      1. true:那么显示用户的名称
      2. false:直接重定向到登录界面

举例

  1. App.jsx新增Login组件

     ...
     <Link to="/login">登录</Link>
     ...
     <Route path='/login' element={<Login/>}/>
    
  2. Login.jsx

     import React, { PureComponent } from 'react'
     import { Navigate } from 'react-router-dom'
        
     export class Login extends PureComponent {
       constructor(props) {
         super(props)
        
         this.state = {
           isLogin: false
         }
       }
          
       login() {
         this.setState({ isLogin: true })
       }
        
       render() {
         const { isLogin } = this.state
        
         return (
           <div>
             <h1>Login Page</h1>
             {/* 当前用户已经登录跳转到Home,反之,展示登录按钮*/}
             {!isLogin ? <button onClick={e => this.login()}>登录</button>: <Navigate to="/home"/>}
           </div>
         )
       }
     }
        
     export default Login
    

Not Found页面配置

  1. 如果用户随意输入一个地址,该地址无法匹配,那么在路由匹配的位置将什么内容都不显示。
  2. 很多时候,我们希望在这种情况下,让用户看到一个Not Found的页面。
  3. 这个过程非常简单:
    1. 开发一个Not Found页面;
    2. 配置对应的Route,并且设置path为*即可;
     <Route path='*' element={<NotFound/>}/>
    

路由的嵌套

  1. 场景:Home组件页面有两个tabs点解切换展示不同的组件
  2. 解决方案:
    1. Router6之前,直接在Home组件内部配置Router路由规则
    2. Router6之后,APP.js中统一配置
  3. 举例使用

     //App.jsx统一配置路由规则
     <Routes>
       <Route path='/' element={<Navigate to="/home"/>}/>
       {/* 当home下有2个子路由*/}
       <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>
       <Route path='/about' element={<About/>}/>
       <Route path='/login' element={<Login/>}/>
       <Route path='*' element={<NotFound/>}/>
     </Routes> 
        
     //Home.jsx
     import React, { PureComponent } from 'react'
     import { Link, Outlet } from 'react-router-dom'
        
     export class Home extends PureComponent {
        
       render() {
         return (
           <div>
             <h1>Home Page</h1>
             <div className='home-nav'>
               <Link to="/home/recommend">推荐</Link>
               <Link to="/home/ranking">排行榜</Link>
             </div>
             {/* Router6之前直接在这里配置路由规则Router,且直接站位 */}
             {/* Router6,必须统一配置,这里需要加一个占位的组件Outlet,用来表示子组件在这里展示 */}
             <Outlet/>
           </div>
         )
       }
     }
        
     export default Home
    

手动路由的跳转

  1. 通过Link跳转路由非常有限制,因为会默认渲染为a标签,样式单一。
  2. 可以通过JavaScript代码进行跳转,比如点击button进行跳转,使用useNavigate进行跳转

函数式组件使用

  1. 将App.jsx改为类组件

     import React from 'react'
     // 1. 引入useNavigate
     import { Link, Navigate, Route, Routes, useNavigate, useRoutes } from 'react-router-dom'
     import Home from './pages/Home'
     import HomeRecommend from "./pages/HomeRecommend"
     import HomeRanking from "./pages/HomeRanking"
     import About from "./pages/About"
     import Login from "./pages/Login"
     import Category from "./pages/Category"
     import Order from "./pages/Order"
     import NotFound from './pages/NotFound'
     import routes from './router'
     import "./style.css"
        
     export function App(props) {
       //获取navigate对象
       const navigate = useNavigate()
          
       //实现跳转方法
       function navigateTo(path) {
         //使用navigate进行跳转
         navigate(path)
       }
        
       return (
         <div className='app'>
           <div className='header'>
             <span>header</span>
             <div className='nav'>
               <Link to="/home">首页</Link>
               <Link to="/about">关于</Link>
               <Link to="/login">登录</Link>
               {/* 实现按钮跳转 */}
               <button onClick={e => navigateTo("/category")}>分类</button>
               <span onClick={e => navigateTo("/order")}>订单</span>
             </div>
             <hr />
           </div>
           <div className='content'>
             {/* 映射关系: path => Component */}
             <Routes>
               <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>
               <Route path='/about' element={<About/>}/>
               <Route path='/login' element={<Login/>}/>
               <Route path='/category' element={<Category/>}/>
               <Route path='/order' element={<Order/>}/>
               <Route path='*' element={<NotFound/>}/>
             </Routes> 
           </div>
           <div className='footer'>
             <hr />
             Footer
           </div>
         </div>
       )
     }
     export default App
    

类组件使用

  1. 函数组件中通过useNavigate(),拿到navigate对象,通过该对象进行跳转,但是该方法不支持类组件
  2. 解决方案:创建一个通用的高阶组件,对类组件做一个增强,在高价组件内部拿到navigate对象,然后传递给累组件
  3. 举例:

     //hoc/with_router.js 封装一个通用的高阶组件
     import { useState } from "react"
     import {useNavigate} from "react-router-dom"
        
     // 高阶组件: 函数
     function withRouter(WrapperComponent) {
       return function(props) {
         // 1.函数内部拿到导航navigate
         const navigate = useNavigate()
         //2. 将原来组件进行增强
         return <WrapperComponent {...props} router={router}/>
       }
     }
     export default withRouter
        
     //Home.jsx
     import React, { PureComponent } from 'react'
     import { Link, Outlet } from 'react-router-dom'
     import { withRouter } from "../hoc/with_router"
     export class Home extends PureComponent {
       navigateTo(path) {
         //增强过的在这可以拿到navigate
         const { navigate } = this.props.router
         navigate(path)
       }
        
       render() {
         return (
           <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>
         )
       }
     }
     //导出增强后的Home组件
     export default withRouter(Home)
    

路由参数传递

  1. 传递参数有二种方式:
    1. 动态路由的方式;
    2. search传递参数
  2. 动态路由的概念指的是路由中的路径并不会固定:
    1. 比如/detail的path对应一个组件Detail;
    2. 如果我们将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该Route,并且进行显示;
    3. 这个匹配规则,我们就称之为动态路由;
  3. 举例:

     //App.jsx
     return (
         <div className='app'>
           <div className='header'>
             <span>header</span>
             <div className='nav'>
               ...
               {/* search传参 */}
               <Link to="/user?name=why&age=18">用户</Link>
             </div>
             <hr />
           </div>
           <div className='content'>
             {/* 映射关系: path => Component */}
            <Routes>
              ...
              {/* 动态路由传参 */}
               <Route path='/detail/:id' element={<Detail/>}/>
               <Route path='/user' element={<User/>}/>
               ...
             </Routes> 
           </div>
           <div className='footer'>
             <hr />
             Footer
           </div>
         </div>
       )
    
  4. 无论是动态路由传参还是search传参获取参数的方法都无法在类组件中使用,因此需要增强

     import { useState } from "react"
     import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom"
        
     // 高阶组件: 函数
     function withRouter(WrapperComponent) {
       return function(props) {
         // 1.导航
         const navigate = useNavigate()
        
         // 2.动态路由的参数: /detail/:id
         const params = useParams()
        
         // 3.查询字符串的参数: /user?name=why&age=18
         //?name=why&age=18
         const location = useLocation()
         //直接获取到对象
         const [searchParams] = useSearchParams()
         const query = Object.fromEntries(searchParams)
         const router = { navigate, params, location, query }
         //将参数传递给组件
         return <WrapperComponent {...props} router={router}/>
       }
     }
        
     export default withRouter
    

动态路由传参

  1. HomeSongMenu.jsx中点击进入详情页面

     import React, { PureComponent } from 'react'
     import { withRouter } from "../hoc"
        
     export class HomeSongMenu extends PureComponent {
       constructor(props) {
         super(props)
        
         this.state = {
           songMenus: [
             { id: 111, name: "华语流行" },
             { id: 112, name: "古典音乐" },
             { id: 113, name: "民谣歌曲" },
           ]
         }
       }
        
       NavigateToDetail(id) {
         const { navigate } = this.props.router
         navigate("/detail/" + id)
       }
        
       render() {
         const { songMenus } = this.state
        
         return (
           <div>
             <h1>Home Song Menu</h1>
             <ul>
               {
                 songMenus.map(item => {
                   return <li key={item.id} onClick={e => this.NavigateToDetail(item.id)}>{item.name}</li>
                 })
               }
             </ul>
           </div>
         )
       }
     }
        
     export default withRouter(HomeSongMenu)
    
  2. Detail.jsx详情页面

     import React, { PureComponent } from 'react'
     import { withRouter } from '../hoc/with_router'
        
     export class Detail extends PureComponent {
       render() {
         const { router } = this.props
         const { params } = router
        
         return (
           <div>
             <h1>Detail Page</h1>
             <h2>id: {params.id}</h2>
           </div>
         )
       }
     }
        
     export default withRouter(Detail)
    

search传参

  1. User.jsx

     import React, { PureComponent } from 'react'
     import { withRouter } from '../hoc/with_router'
        
     export class User extends PureComponent {
       render() {
         const { router } = this.props
         const { query } = router
        
         return (
           <div>
             <h1>User: {query.name}-{query.age}</h1>
           </div>
         )
       }
     }
     export default withRouter(User)
    

路由传参(router<6)

  1. 跳转到move时传参,App.jsx

     ...
     <Link to="/movie/top250/10">电影</Link>&nbsp;&nbsp;
     ...
        
     {/* 注意:默认情况下,路由中的规则,是模糊匹配的,如果 路由可以部分匹配成功,就会展示这个路由对应的组件,如果想让路由规则,进行精确匹配,可以为 Route,添加 exact 属性,表示启用精确匹配模式 */}
     {/* 如果要匹配参数,可以在 匹配规则中,使用 : 修饰符,表示这个位置匹配到的是参数 */}
     <Route path="/movie/:type/:id" component={Movie} exact></Route>
    
  2. Movie.jsx组件内部接受参数

     export default class Movie extends React.Component {
         constructor(props) {
             super(props)
             this.state = {
               routeParams: props.match.params
             }
         }
            
         render() {
             console.log(this);
             // 如果想要从路由规则中,提取匹配到的参数,进行使用,可以使用 this.props.match.params.*** 来访问
             return <div>
               {/* Movie --- {this.props.match.params.type} --- {this.props.match.params.id} */}
                
               Movie --- {this.state.routeParams.type} --- {this.state.routeParams.id}
                
             </div>
         }
     }
    

路由的配置文件

  1. 将所有的路由配置放到一个地方进行集中管理
  2. Router6之前需要借助于react-router-config完成;
  3. 在Router6.x中,为我们提供了useRoutes API可以完成相关的配置;
  4. 如果对某些组件进行了异步加载(懒加载),那么需要使用Suspense进行包裹
  5. 举例使用

     //将所有的Router配置抽取到router/index.js中
     import Home from '../pages/Home'
     import HomeRecommend from "../pages/HomeRecommend"
     import HomeRanking from "../pages/HomeRanking"
     import HomeSongMenu from '../pages/HomeSongMenu'
     // import About from "../pages/About"
     // import Login from "../pages/Login"
     import Category from "../pages/Category"
     import Order from "../pages/Order"
     import NotFound from '../pages/NotFound'
     import Detail from '../pages/Detail'
     import User from '../pages/User'
     import { Navigate } from 'react-router-dom'
     import React from 'react'
        
     //懒加载
     const About = React.lazy(() => import("../pages/About"))
     const Login = React.lazy(() => import("../pages/Login"))
        
     const routes = [
       {
         path: "/",
         element: <Navigate to="/home"/>
       },
       {
         path: "/home",
         element: <Home/>,
         children: [
           {
             path: "/home",
             element: <Navigate to="/home/recommend"/>
           },
           {
             path: "/home/recommend",
             element: <HomeRecommend/>
           },
           {
             path: "/home/ranking",
             element: <HomeRanking/>
           },
           {
             path: "/home/songmenu",
             element: <HomeSongMenu/>
           }
         ]
       },
       {
         path: "/about",
         element: <About/>
       },
       {
         path: "/login",
         element: <Login/>
       },
       {
         path: "/category",
         element: <Category/>
       },
       {
         path: "/order",
         element: <Order/>
       },
       {
         path: "/detail/:id",
         element: <Detail/>
       },
       {
         path: "/user",
         element: <User/>
       },
       {
         path: "*",
         element: <NotFound/>
       }
     ]
     export default routes
        
     //App.jsx中直接引入使用
     import routes from './router'
     ...
     <div className='content'>
         {/* 映射关系: path => Component */}
         {/*使用useRoutes引入配置*/}
         {useRoutes(routes)}
     </div>
        
     //项目入口文件index.js,顶部组件用Suspense包裹
     import ReactDOM from "react-dom/client"
     import App from "./App"
     import { HashRouter } from "react-router-dom"
     import { Suspense } from "react"
        
     const root = ReactDOM.createRoot(document.querySelector("#root"))
     root.render(
         <HashRouter>
           <Suspense fallback={<h3>Loading...</h3>}>
             <App/>
           </Suspense>
         </HashRouter>
     )
    
Table of Contents