React Router

130 阅读3分钟

1. 路由用法

1.1 基本用法

  • 模式
    • 历史模式
    • 哈希模式
// index.js
import React from "react"
import ReactDOM from "react-dom/client"
import App from "./App"
import { /* BrowserHistory, */ HashRouter } from "react-router-dom"

const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
  <React.StrictMode>
    <HashRouter>
      <App />
    </HashRouter>
  </React.StrictMode>
)
  • 核心:
    • 映射关系: path => Component
  • Routes组价中包含 Route
import React, { PureComponent } from "react"
import { Route } from "react-router-dom"
import { Link } from "react-router-dom"
import { Routes } from "react-router-dom"
import About from "./pages/About"
import Home from "./pages/Home"

export 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>
    )
  }
}

export default App

1.2 Link 与 NavLink 的区别

  • NavLink 与 Link 的区别在于前者可以添加类属性,以此来添加样式
    • NavLink 会自动添加一个叫 active 的类名
    • NavLink 可以自定义类名
// 方式一
<NavLink to="/home">
  首页
</NavLink>
<NavLink to="/about">
  关于
</NavLink>

// style.css
.nav .active {
  color: red;
}


// 方式二
<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>

1.3 Navigate 导航

1.3.1 判断是否登录

import React, { PureComponent } from "react"
import { Navigate } from "react-router-dom"

export class Login extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      isLogin: true
    }
  }
  render() {
    const { isLogin } = this.state
    return (
      <div>
        <button onClick={e => this.setState({ isLogin: !isLogin })}>登录</button>
        {isLogin ? <h1>Login Page</h1> : <Navigate to="/home" />}
      </div>
    )
  }
}

export default Login

1.3.2 重定向

  • 当初始路径为 '/' 的时候此时要重定向到首页
<Route path="/" element={<Navigate to="/home" />} />

1.3.3 错误页面

<Route path="*" element={<NotFound />} />

1.4 路由嵌套

  • 直接在对应的 Route 路由里面添加对应的子路由就可以,而在之前的版本中路由很分散,不易于管理
// home.jsx
import React, { PureComponent } from "react"
import { Outlet } from "react-router-dom"
import { Link } from "react-router-dom"

export class Home extends PureComponent {
  render() {
    return (
      <div>
        <h1>Home Page</h1>

        <Link to="/home/recommend">推荐</Link>
        <Link to="/home/ranking">排行榜</Link>

        <Outlet />
      </div>
    )
  }
}

export default Home

// App.jsx
// 二级路由也可以重定向
<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>

1.5 手动路由

1.5.1 函数组件

  • 在函数组件中使用 useNavigate()
export function App(props) {
  // 必选写到顶部,否则会报错
  const navigate = useNavigate()

  function navigateTo(path) {
    console.log(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("/profile")}>个人信息</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="/profile" element={<Profile />} />
          <Route path="/order" element={<Order />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </div>
      <div className="footer">
        <hr />
        Footer
      </div>
    </div>
  )
}

1.5.2 类组件

  • 需要使用高阶组件封装
// hoc/withRouter.js
import { useNavigate } from "react-router-dom"

export function withRouter(WrapperComponent) {
  return function (props) {
    const navigate = useNavigate()
    const router = { navigate }
    return <WrapperComponent {...props} router={router} />
  }
}

// Home.jsx
import React, { PureComponent } from "react"
import { Outlet } from "react-router-dom"
import { Link } from "react-router-dom"
import { withRouter } from "../hoc"

export class Home extends PureComponent {
  navigate(path) {
    const { navigate } = this.props.router
    navigate(path)
  }

  render() {
    return (
      <div>
        <h1>Home Page</h1>

        <Link to="/home/recommend">推荐</Link>
        <Link to="/home/ranking">排行榜</Link>
        <button onClick={e => this.navigate("/home/songmenu")}>歌单信息</button>

        <Outlet />
      </div>
    )
  }
}

export default withRouter(Home)

1.6 使用路由获取参数

1.6.1 useParams

// App.jsx
<Route path="/detail/:id" element={<Detail />} />

// List.jsx
...
navigateToDetail(id) {
  const { navigate } = this.props.router
  navigate("/detail/" + id)
  console.log(id)
}
render() {
  const { menuList } = this.state
  return (
    <div>
      <h1>HomeSongMenu Page</h1>
      <ul>
        {menuList.map(list => (
          <li key={list.id} onClick={e => this.navigateToDetail(list.id)}>
            {list.name}
          </li>
        ))}
      </ul>
    </div>
  )
}
export default withRouter(HomeSongMenu)

// withRouter.js
import { useParams } from "react-router-dom"
import { useNavigate } from "react-router-dom"

export function withRouter(WrapperComponent) {
  return function (props) {
    const navigate = useNavigate()
    const params = useParams()
    const router = { navigate }
    return <WrapperComponent {...props} router={router} params={params} />
  }
}

// Detail.jsx
import React, { PureComponent } from "react"
import { withRouter } from "../hoc"

export class Detail extends PureComponent {
  render() {
    const { params } = this.props
    return (
      <div>
        <h1>Detail Page</h1>
        <div>id: {params.id}</div>
      </div>
    )
  }
}

export default withRouter(Detail)

1.6.2 查询字符串的参数

// withRouter.js
import { useLocation } from "react-router-dom"
import { useSearchParams } from "react-router-dom"
import { useParams } from "react-router-dom"
import { useNavigate } from "react-router-dom"

export function withRouter(WrapperComponent) {
  return function (props) {
    // 1. 导航
    const navigate = useNavigate()

    // 2. 动态路由的参数 /detail/:id
    const params = useParams()

    // 3. 查询字符串的参数 /user?name=lwz&age=18
    const location = useLocation()

    const [searchParams] = useSearchParams()
    const query = Object.fromEntries(searchParams)

    const router = { navigate, params, query }
    return <WrapperComponent {...props} router={router} />
  }
}

// App.jsx
<Link to="/user?name=lwz&age=18">用户</Link>
<Route path="/user" element={<User />} />

// User.jsx
import React, { PureComponent } from "react"
import { withRouter } from "../hoc"

export class User extends PureComponent {
  render() {
    const { query } = this.props.router
    return (
      <div>
        <h1>
          User Page: {query.name} - {query.age}
        </h1>
      </div>
    )
  }
}

export default withRouter(User)

1.7 路由配置文件

// router/index.js
const routes = [
  {
    path: "/",
    element: <Navigate to="/home" />
  },
  {
    path: "/home",
    element: <Home />,
    children: [
      {
        path: "/home/recommend",
        element: <HomeRecommend />
      },
      {
        path: "/home/ranking",
        element: <HomeRanking />
      }
    ]
  },
  {
    path: "*",
    element: <NotFound />
  }
]

export default routes

// App.jsx
import { useRoutes } from "react-router-dom"
import routes from "./router"
{useRoutes(routes)}

1.8 Router-页面的懒加载和Suspence

// router/index.js
const Detail = React.lazy(() => import("../pages/Detail"))
const User = React.lazy(() => import("../pages/User"))

// src/index.js
...
root.render(
  <React.StrictMode>
    <HashRouter>
      <Suspense fallback={<h3>Loading...</h3>}>
        <App />
      </Suspense>
    </HashRouter>
  </React.StrictMode>
)