React Router路由详解

154 阅读4分钟

1. Router的基本使用

1.1 安装

  • react-router包含一些react-native的内容,web开发并不需要,所以安装 react-router-dom
  • npm install react-router-dom

1.2 选择路由组件

  • BrowserRouter/HashRouter => App

    • Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件

    • BrowserRouter使用history模式

    • HashRouter使用hash模式

    root.render(
      <React.StrictMode>
        <HashRouter>
          <App />
        </HashRouter>
      </React.StrictMode>
    );
    

1.3 配置映射关系和跳转

  • Routes:包裹所有的Route,在其中匹配一个路由

    • Router5.x使用的是Switch组件
  • Route:Route用于路径的匹配

    • path属性:用于设置匹配到的路径

    • element属性:设置匹配到路径后,渲染的组件

      • Router5.x使用的是component属性
    • exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件

      • Router6.x不再支持该属性
  • Link:

    • 通常路径跳转使用Link组件,最终被渲染成 a 元素
    • to属性:用于设置跳转到的路径
    • replace:替换当前路径
import React, { PureComponent } from 'react'
import { Link, Route, Routes } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'

export default class App extends PureComponent {
  render() {
    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>
    )
  }
}

2. NavLink使用

  • Link 的不同

    • style:传入函数,函数接受一个对象,包含isActive属性
    • className:传入函数,函数接受一个对象,包含isActive属性
  • 默认的activeClassName:

    • 事实上在默认匹配成功时,NavLink就会添加上一个动态的active class
import React, { PureComponent } from 'react'
import { NavLink, Route, Routes } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'

export default class App extends PureComponent {
  render() {
    return (
      <div className='app'>
        <div className="header">
          <span>Header</span>
          <div className="nav">
            {/* <NavLink to='/home' style={({isActive}) => ({color: isActive ? 'blue' : ''})}>首页</NavLink>
            <NavLink to='/about' style={({isActive}) => ({color: isActive ? 'blue' : ''})}>关于</NavLink> */}
            <NavLink to='/home' className={({isActive}) => isActive ? 'link-active' : ''}>首页</NavLink>
            <NavLink to='/about' className={({isActive}) => isActive ? 'link-active' : ''}>关于</NavLink>
          </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>
    )
  }
}

image.png

3. Navigate使用

  • Navigate用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中

  • 登录的自动跳转

    • {!isLogin ? <button onClick={e => this.login()}>登录</button> : <Navigate to='/home' />}
  • 根路径的重定向

    • <Route path='/' element={<Navigate to='/home' />} />

4. NotFound页面配置

  • 开发一个Not Found页面

  • 配置对应的Route,并且设置path为*即可

    • <Route path='*' element={<NotFound/>} />

5. 路由嵌套的使用

  • 父路由包含子路由

    <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="*" element={<NotFound/>} />
    </Routes>
    
  • Outlet占位

    import React, { PureComponent } from 'react'
    import { Link, Outlet } from 'react-router-dom'
    
    export default 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>
    
            {/* 占位的组件 */}
            <Outlet/>
          </div>
        )
      }
    }
    

6. 代码进行跳转

  • 如果希望通过 JavaScript 代码进行跳转,需要获得 navigate 对象

  • Router6.x 之后,代码类的 API 迁移到了hooks中

    • 如果用代码跳转,需要通过 useNavigate 的Hook获取到navigte对象进行操作
    • 这种 Hook 只能在函数式组件中调用
  • 将 App 更改为函数式组件

    import React from 'react'
    import { Link, Navigate, Route, Routes, useNavigate } from 'react-router-dom'
    import About from './pages/About'
    import Home from './pages/Home'
    import HomeRanking from './pages/HomeRanking'
    import HomeRecommend from './pages/HomeRecommend'
    import Login from './pages/Login'
    import Category from './pages/Category'
    import Order from './pages/Order'
    import NotFound from './pages/NotFound'
    
    export default function App(props) {
    
      // 必须放在顶层使用
      const navigate = useNavigate()
    
      function navigateTo(path) {
        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>
      )
    }
    

    image.png

  • 在类组件如何通过 代码进行跳转呢

    • 封装一个高阶组件,传入 navigate
    // with_router.js
    import { useNavigate } from 'react-router-dom'
    
    function withRouter(WrapperComponent) {
      return function(props) {
        const navigate = useNavigate()
        const router = { navigate }
    
        return <WrapperComponent {...props} router={router}/>
      }
    }
    
    export default withRouter
    
  • 类组件中使用

    import React, { PureComponent } from 'react'
    import { Link, Outlet } from 'react-router-dom'
    import { withRouter } from '../hoc'
    
    export class Home extends PureComponent {
    
      navigateTo(path) {
        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>
        )
      }
    }
    
    export default withRouter(Home)
    

7. 路由传递参数

  • 动态路由

    • useParams
    • <Route path='/detail/:id' element={<Detail/>}/>
  • search传递参数

    • useSearchParams
    • <Link to="/user?name=zhangsan&age=20">用户</Link>
  • withRouter 高阶组件升级

    // with_router.js
    import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom'
    
    function withRouter(WrapperComponent) {
      return function(props) {
        // 1.导航
        const navigate = useNavigate()
        // 2.动态路由参数
        const params = useParams()
        // 3.查询字符的参数
        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
    

8. 路由的配置方式

  • 在 Router6.x 中,提供了useRoutes API可以完成相关的配置

    • <div>{useRoutes(routes)}</div>
  • routes 配置

    • path:路径
    • element:组件
    • children:子级路由
    import { Navigate } from 'react-router-dom'
    
    import Home from '../pages/Home'
    import HomeRanking from '../pages/HomeRanking'
    import HomeRecommend from '../pages/HomeRecommend'
    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 Detail from '../pages/Detail'
    import User from '../pages/User'
    import NotFound from '../pages/NotFound'
    
    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
    

9. 路由的懒加载

  • 分析打包后的文件

    • 未使用懒加载

      • main.eb3f0b10.js main.js(哈希值) 中存放的是所有的js代码
    • 使用懒加载

      • 对于懒加载的组件进行单独打包

        image.png

  • 如何使用懒加载?

    • 导入:React.lazy(() => import())
    const About = React.lazy(() => import('../pages/About'))
    const Login = React.lazy(() => import('../pages/Login'))
    
    • Suspense fallback
    // index.js
    import React, { Suspense } from 'react';
    import ReactDOM from 'react-dom/client';
    import App from './App';
    import { HashRouter } from 'react-router-dom';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
        <HashRouter>
          <Suspense fallback={<h3>Loading...</h3>}>
            <App />
          </Suspense>
        </HashRouter>
      </React.StrictMode>
    );