ReactJS语法-组件的路由
路由的使用场景:有几个tabs,点击每个tab展示不同的页面布局内容,同时浏览器窗口显示对应页面的路径。
基本使用
- 安装React Router:
npm install react-router-dom
BrowserRouter或HashRoute
- Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件;
- BrowserRouter使用history模式;
- HashRouter使用hash模式;
-
举例:
//入口文件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> )- HashRouter 表示一个路由的跟容器,将来,所有的路由相关的东西,都要包裹在 HashRouter 里面,而且,一个网站中,只需要使用一次 HashRouter 就好了.
路由映射配置
- Routes:包裹所有的Route,在其中匹配一个路由
- Router5.x使用的是Switch组件
- 更早版本不需要任何包裹,直接使用
<Route path="/home" component={Home}></Route>
- Route:Route用于路径的匹配;
- path属性:用于设置匹配到的路径;
- element属性:设置匹配到路径后,渲染的组件
- Router5.x使用的是component属性
- exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件;
- Router6.x不再支持该属性
路由跳转
- 通常路径的跳转是使用Link组件,最终会被渲染成a元素;
- 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低版本使用方式)
-
入口文件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')) -
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> <Link to="/movie">电影</Link> <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> } } -
分别创建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> } }
Navigate导航
- Navigate用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中
- 案例:
- 用户跳转到Profile界面
- 但是在Profile界面有一个isLogin用于记录用户是否登录:
- true:那么显示用户的名称
- false:直接重定向到登录界面
举例
-
App.jsx新增Login组件
... <Link to="/login">登录</Link> ... <Route path='/login' element={<Login/>}/> -
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页面配置
- 如果用户随意输入一个地址,该地址无法匹配,那么在路由匹配的位置将什么内容都不显示。
- 很多时候,我们希望在这种情况下,让用户看到一个Not Found的页面。
- 这个过程非常简单:
- 开发一个Not Found页面;
- 配置对应的Route,并且设置path为*即可;
<Route path='*' element={<NotFound/>}/>
路由的嵌套
- 场景:Home组件页面有两个tabs点解切换展示不同的组件
- 解决方案:
- Router6之前,直接在Home组件内部配置Router路由规则
- Router6之后,APP.js中统一配置
-
举例使用
//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
手动路由的跳转
- 通过Link跳转路由非常有限制,因为会默认渲染为a标签,样式单一。
- 可以通过JavaScript代码进行跳转,比如点击button进行跳转,使用useNavigate进行跳转
函数式组件使用
-
将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
类组件使用
- 函数组件中通过useNavigate(),拿到navigate对象,通过该对象进行跳转,但是该方法不支持类组件
- 解决方案:创建一个通用的高阶组件,对类组件做一个增强,在高价组件内部拿到navigate对象,然后传递给累组件
-
举例:
//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)
路由参数传递
- 传递参数有二种方式:
- 动态路由的方式;
- search传递参数
- 动态路由的概念指的是路由中的路径并不会固定:
- 比如/detail的path对应一个组件Detail;
- 如果我们将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该Route,并且进行显示;
- 这个匹配规则,我们就称之为动态路由;
-
举例:
//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> ) -
无论是动态路由传参还是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
动态路由传参
-
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) -
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传参
-
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)
-
跳转到move时传参,App.jsx
... <Link to="/movie/top250/10">电影</Link> ... {/* 注意:默认情况下,路由中的规则,是模糊匹配的,如果 路由可以部分匹配成功,就会展示这个路由对应的组件,如果想让路由规则,进行精确匹配,可以为 Route,添加 exact 属性,表示启用精确匹配模式 */} {/* 如果要匹配参数,可以在 匹配规则中,使用 : 修饰符,表示这个位置匹配到的是参数 */} <Route path="/movie/:type/:id" component={Movie} exact></Route> -
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> } }
路由的配置文件
- 将所有的路由配置放到一个地方进行集中管理
- Router6之前需要借助于react-router-config完成;
- 在Router6.x中,为我们提供了useRoutes API可以完成相关的配置;
- 如果对某些组件进行了异步加载(懒加载),那么需要使用Suspense进行包裹
-
举例使用
//将所有的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> )