react+typescript+wbpack5项目搭建(二)之路由补充

继上一篇react+typescript+wbpack5项目搭建完成基本搭建后,路由补充
React Router Dom安装

npm install react-router-dom

官方推荐使用

BrowserRouter

1,基本使用:

(1)BrowserRouter
要想在 React 应用中使用 React Router,就需要在 React 项目的根文件(index.tsx)中导入 Router 组件:

import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'

import APP from './APP'

const container = document.getElementById('root') as HTMLElement
const root = ReactDOM.createRoot(container)
root.render(
  <BrowserRouter>
    <APP />
  </BrowserRouter>
)

在这个文件中,我们导入了 BrowserRouter 组件,然后使用该组件包裹了 App 组件。现在,在这个 BrowserRouter 组件中,来自 react-router-dom 的其他组件和 hooks 就可以正常工作了。

BrowserRouter 是最常用的路由方式,即浏览器路由。官方文档也建议将 BrowserRouter 组件用于 Web 应用程序。除了这种方式,React Router 还支持其他几种路由方式:

HashRouter:在路径前加入#成为一个哈希值,Hash 模式的好处是不会因为刷新页面而找不到对应路径;

MemoryRouter:不存储 history,路由过程保存在内存中,适用于 React Native 这种非浏览器环境;

NativeRouter:配合 React Native 使用,多用于移动端;

StaticRouter:主要用于服务端渲染时。

(2)NavLink

在App.tsx 创建三个导航链接,这些链接会指向App.tsx 中的一些路由。这时就需要导入 NavLink 组件,它是一个导航链接组件,类似于 HTML 中的标签。NavLink 组件使用 to 来指定需要跳转的链接:

import React from "react"
import { NavLink } from "react-router-dom"
import styles from './APP.scss'

const APP = () => {

  return (
    <div>
      <header>
        <h1>APP</h1>
      </header>
      <NavLink to="">首页</NavLink>
      <NavLink to="product">产品</NavLink>
      <NavLink to="contact">联系我们</NavLink>
    </div>
  )
}

export default APP

当点击这些导航链接时,网页的 URL 就会改变,跳转到对应的路由。

NavLink 是存在 active 状态的,所以可以为active 状态和非active 状态的导航链接添加样式:

.nav-active {
  color: red;
  font-weight: bold;
}

(3)Link
在 react-router-dom 中,可以使用 Link 组件来创建常规链接。Link 组件与 NavLink 组件非常相似,唯一的区别就是 NavLink 存在 active 状态,而 Link 没有。

(4)Routes
下面来看看如何将路由映射为对应的页面(组件)。首先需要从 react-router-dom 中导入一个名为 Routes 的组件,它将包含可以在页面特定部分显示的所有不同的路由。

我们需要在 Routes 组件中使用 Route 组件来定义所有路由。该组件接受两个 props:

path:页面 URL 应导航到的路径,类似于 NavLink 组件的 to;

element:页面导航到该路由时加载的元素。

Route 组件用于将应用的位置映射到不同的 React 组件。例如,当用户导航到 /product 路径时呈现 Product 组件,可以这样来写:

import React from "react"
import { NavLink, Routes, Route } from "react-router-dom"
import styles from './APP.scss'

import Home from './component/Home/Home'
import Product from "./component/Product/Product"
import Contact from "./component/Contact/Contact"

const APP = () => {

  return (
    <div>
      <header>
        <h1>APP</h1>
      </header>
      <nav>
        <NavLink to="">首页</NavLink>
        <NavLink to="product">产品</NavLink>
        <NavLink to="contact">联系我们</NavLink>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/product" element={<Product />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </div>
  )
}

export default APP

(5)路由顺序
在 React Router v6 以前,我们必须按照一定的顺序来定义路由,以获得准确的渲染。在 v6 及之后的版本中,路由定义的顺序无关紧要。
v6 中,将 组件替换为了 组件,会更具体的匹配

2,编程式导航:

React Router 提供了两种不同的编程式导航方式:

声明式导航组件: 组件

命令式导航方法:useNavigate Hook

我们可以使用这两种编程的方式来跳转到指定的路由,也可以实现路由的重定向,比如在浏览器的地址栏输入一个 URL 并进行跳转时,如果应用中没有定义该路由,就跳转到应用的首页。

(1)Navigate
此组件是一种声明式的导航方式,组件中通过 to props 来指定要跳转的路径。

import React from "react"
import { NavLink, Routes, Route, Navigate } from "react-router-dom"
import styles from './APP.scss'

import Home from './component/Home/Home'
import Product from "./component/Product/Product"
import Contact from "./component/Contact/Contact"

const APP = () => {

  return (
    <div>
      <header>
        <h1>APP</h1>
      </header>
      <nav>
        <NavLink to="">首页</NavLink>
        <NavLink to="product">产品</NavLink>
        <NavLink to="contact">联系我们</NavLink>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/product" element={<Product />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="*" element={<Navigate to="/" />} />
      </Routes>
    </div>
  )
}

export default APP

(2)useNavigate
useNavigate Hook是一种命令式导航方式,使用方法如下

import React from "react"
import styles from './Home.scss'
import { useNavigate } from "react-router-dom"

const Home = () => {
  const navgite = useNavigate()
  return (
    <div className={styles.title}>
      主页面
      <button onClick={() => { navgite('/contact') }}>跳转到联系页面</button>
    </div>
  )
}


export default Home

3,通过路由传递状态

在 react-router-dom 中可以通过以下三种方式来传递状态:

使用Link 组件
使用Navigate 组件
使用useNavigate 钩子

(1)Link
通过state传递

import React from "react"
import { Link } from "react-router-dom"
import styles from './Product.scss'

const Product = () => {
  return (
    <div className={styles.title}>
      产品页面
      <Link to='/' state={"产品页面的返回"}>返回主页面</Link>
    </div>
  )
}


export default Product

通过useLocation接收

import React from "react"
import styles from './Home.scss'
import { useNavigate, useLocation } from "react-router-dom"

const Home = () => {
  const navgite = useNavigate()
  const location = useLocation()
  return (
    <div className={styles.title}>
      主页面
      <button onClick={() => { navgite('/contact') }}>跳转到联系页面</button>
      <p>{location.state}</p>
    </div>
  )
}


export default Home

(2)Navigate
使用方式和 Link 组件类似state传递useLocation接收,多用于重定向

(3)useNavigate
上面讲过使用方法,一样state传递useLocation接收

4,动态路由

<Route path="/product/:keyword" element={<Product />} />

keyword为不固定参数,比如是你的搜索内容,只需要声明一个带有 keyword 占位符的路由,就会加载一个同一个组件

import React from "react"
import { NavLink, Routes, Route, Navigate } from "react-router-dom"
import styles from './APP.scss'

import Home from './component/Home/Home'
import Product from "./component/Product/Product"
import Contact from "./component/Contact/Contact"

const APP = () => {

  return (
    <div>
      <header>
        <h1>APP</h1>
      </header>
      <nav>
        <NavLink to="">首页</NavLink>
        <NavLink to="product/123">产品</NavLink>
        <NavLink to="contact">联系我们</NavLink>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/product/:keyword" element={<Product />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="*" element={<Navigate to="/" />} />
      </Routes>
    </div>
  )
}

export default APP

React Router 就提供了一个 useParams Hook,它返回一个对象,该对象具有 URL 参数及其值之间的映射。使用方式如下:

import React from "react"
import { Link, useParams } from "react-router-dom"
import styles from './Product.scss'

const Product = () => {
  const { keyword } = useParams()
  return (
    <div className={styles.title}>
      产品页面
      <Link to='/' state={"产品页面的返回"}>返回主页面</Link>
      <p>{keyword}</p>
    </div>
  )
}


export default Product

5,嵌套路由

嵌套路由允许父路由充当包装器并控制子路由的渲染。比如,在应用中点击消息时,会跳转到 /messages 路由,并显示所有的通知列表。当点击某一条消息时,就会跳转到 /messages/:id 路由,这时就能看到指定 id 的消息详情,同时消息列表是显示在左侧的。这个场景就要依赖嵌套路由来实现。下面来看看如何使用 React Router 实现这种嵌套路由模式。

import React from "react"
import { NavLink, Routes, Route, Navigate } from "react-router-dom"
import styles from './APP.scss'

import Home from './component/Home/Home'
import Product from "./component/Product/Product"
import Contact from "./component/Contact/Contact"
import Messages from "./component/Messages/Messages"
import MessagesDetails from "./component/MessagesDetails/MessagesDetails"

const APP = () => {

  return (
    <div>
      <header>
        <h1>APP</h1>
      </header>
      <nav>
        <NavLink to="">首页</NavLink>
        <NavLink to="product/123">产品</NavLink>
        <NavLink to="contact">联系我们</NavLink>
        <NavLink to="messages/1">messages</NavLink>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/product/:keyword" element={<Product />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/messages" element={<Messages />}>
          <Route path=":id" element={<MessagesDetails />} />
        </Route>
        <Route path="*" element={<Navigate to="/" />} />
      </Routes>
    </div>
  )
}

export default APP

然后告诉 React Router 应该在父路由(Messges)中的哪个位置渲染子路由(MessagesDetails)。这就就需要使用 React Router 的 Outlet 组件

import React from "react"
import { Outlet } from "react-router-dom"
// import MessagesDetails from "../MessagesDetails/MessagesDetails"
import Conversations from "../Conversations/Conversations"
import styles from './Messages.scss'

const Messages = () => {
  return (
    <div className={styles.title}>
      <Conversations />
      <Outlet />
    </div>
  )
}


export default Messages

6,查询参数

在 React Router 中,如何从 URL 中获取参数呢?例如以下 URL:
/search?q=react&src=typed_query&f=live

从 v6 开始,React Router 使用 URLSearchParams API 来处理查询字符串,URLSearchParams 内置于所有浏览器(IE 除外)中,并提供了处理查询字符串的实用方法。当创建 URLSearchParams 实例时,需要向它传递一个查询字符串:

const queryString = "?q=react&src=typed_query&f=live";
const sp = new URLSearchParams(queryString);

sp.has("q"); // true
sp.get("q"); // react
sp.getAll("src"); // ["typed_query"]
sp.get("nope"); // null

sp.append("sort", "ascending");
sp.toString(); // "?q=react&src=typed_query&f=live&sort=ascending"

sp.set("q", "bytes.dev");
sp.toString(); // "?q=bytes.dev&src=typed_query&f=live&sort=ascending"

sp.delete("sort");
sp.toString(); // "?q=bytes.dev&src=typed_query&f=live"

React Router 提供了一个自定义的 useSearchParams Hook,它是基于 URLSearchParams 进行的封装。useSearchParams 返回一个数组,该数组第一个元素是 URLSearchParams 的实例,第二个元素是更新查询参数的一个方法。

import { useSearchParams } from 'react-router-dom'

const Results = () => {
  const [searchParams, setSearchParams] = useSearchParams();

  const q = searchParams.get('q')
  const src = searchParams.get('src')
  const f = searchParams.get('f')

  return (
    // ...
  )
}

如果需要更新查询字符串,可以使用 setSearchParams,向它传递一个对象,该对象的key/value 对将作为 &key=value 添加到 url:

const Results = () => {
  const [searchParams, setSearchParams] = useSearchParams();

  const q = searchParams.get('q')
  const src = searchParams.get('src')
  const f = searchParams.get('f')

  const updateOrder = (sort) => {
    setSearchParams({ sort })
  }

  return (
    ...
  )
}

7,Route 配置

React Router v6 内置了一个 useRoutes Hook,它在功能上等同于 ,但它是使用 JavaScript 对象而不是 元素来定义路由。这个对象具有与普通 元素相同的属性,但它们不需要使用 JSX 来编写。

useRoutes 的返回值要么是一个有效的 React 元素(可以使用它来渲染路由树),如果没有匹配项,则返回 null。

import { useRoutes } from "react-router-dom";

const routes = useRoutes([
  { path: "/", element: <Home /> },
  {
    path: "/messages",
    element: <Messages />,
    children: [
      { path: ":id", element: <MessagesDetails /> },
    ],
  },
]);

export default function App() {
  return (
    <div>
      <Navbar />
      {routes}
    </div>
  );
}

注意:使用BrowserRouter刷新当前页会出问题,需要配置historyApiFallback和publicPath,用来解决此问题

// webpack.dev.js
const { merge } = require('webpack-merge')
const path = require('path')
const webpack = require('webpack')

const common = require('./webpack.common')
const { PROJECT_PATH, SERVER_HOST, SERVER_PORT } = require('../constant')

module.exports = merge(common, {
  target: 'web', // 防止配置browserslist 热更新失效
  mode: 'development',
  devtool: 'cheap-module-source-map',
  output: {
  	publicPath: '/', // 本地开发
    filename: 'js/[name].js',
    path: path.resolve(PROJECT_PATH, './dist')
  },
  devServer: {
    host: SERVER_HOST, // 服务ip
    port: SERVER_PORT, // 服务端口
    static: {
      directory: path.join(__dirname, 'public'),
    },
    // contentBase: path.join(__dirname, 'build'),已经被废弃,请使用static
    // stats: 'errors-only', // 设为errors-only表示终端只打印错误类型的日志,不会打印warning以及其他信息影响阅读
    // clientLogLevel: 'none', // 设为none表示去除多余网页console信息
    compress: true, // 设为true表示启用gzip压缩,加快网站打开速度
    open: true, // 设为true表示第一次启动项目时自动打开默认浏览器
    hot: true, // 设为true表示启用服务热替换配置
    // noInfo: true, // 设为true表示去除启动项目时显示的打包信息
    https: true, // 启动项目的时候,默认是https协议
    historyApiFallback: true, // 解决BrowserRouter路由跳转之后刷新浏览器按钮报404的情况
  },
  plugins: [
    // 实际上只开启 hot:true 就会自动识别有无声明该插件,没有则自动引入,但是怕有隐藏问题这里还是手动加上了
    new webpack.HotModuleReplacementPlugin()
  ]
})

注意:contentBase已经废弃,请使用static

HashRouter

使用 URL 的 hash 部分(即 window.location.hash )的 使您的 UI 与 URL 保持同步。

注意:HashRouter不支持 location.key 或 location.state

HashRouter 是通过 hash 值来对路由进行控制。使用 HashRouter,路由会默认有个#
举例来说,用户访问http://www.example.com/,实际会看到的是http://www.example.com/#/

1,basename: string
这个代表所有位置的基本url,格式正确的基本名称基本都有一个前导斜杠"/",但没有尾部斜杠。

<HashRouter basename="/calendar"/>

2,getUserConfirmation: func
用于确认导航的功能。默认使用 window.confirm。

const getConfirmation = (message, callback) => {
  const allowTransition = window.confirm(message)
  callback(allowTransition)
}

<HashRouter getUserConfirmation={getConfirmation}/>

3,hashType: string
用于window.location.hash的编码类型,取值范围为

slash: #/
noslash: #
hashbang: #!(已经被Google弃用)
默认为slash。

4,children: node
一个用于渲染的 single child element

MemoryRouter

能在内存中保存你的 “URL” 的历史记录(并不会对地址栏进行读写)。很适合在测试环境和非浏览器环境中使用,例如 React Native

1,initialEntries: array
history 栈中的一个 location 数组。这些可能是具有 { pathname, search, hash, state } 或简单的 URL 字符串的完整地址对象

<MemoryRouter
  initialEntries={[ '/one', '/two', { pathname: '/three' } ]}
  initialIndex={1}
>
  <App/>
</MemoryRouter>

2,initialIndex: number
在 initialEntries 数组中的初始化地址索引

3,getUserConfirmation: func
用于确认导航的函数

4,keyLength: number
location.key 的长度。默认为 6

5,children: node
渲染一个 单独子元素

Redirect

渲染 将使导航到一个新的地址。这个新的地址会覆盖 history 栈中的当前地址,类似服务器端(HTTP 3xx)的重定向

import { Route, Redirect } from 'react-router'

<Route exact path="/" render={() => (
  loggedIn ? (
    <Redirect to="/dashboard"/>
  ) : (
    <PublicHomePage/>
  )
)}/>

HashRouter和BrowserRouter的区别

1,原理上

HashRouter:在路径中包含了#,相当于HTML的锚点定位。(# 符号的英文叫hash,所以叫HashRouter)
BrowserRouter:使用的是HTML5的新特性History,没有HashRouter(锚点定位)那样通用,低版本浏览器可能不支持。

2,用法上

BrowserRouter进行组件跳转时可以传递任意参数实现组件间的通信
HashRouter不能(除非手动拼接URL字符串),因此一般配合Redux使用,实现组件间的数据通信。

3.生产实践上
HashRouter:

HashRouter相当于锚点定位,因此不论#后面的路径怎么变化,请求的都相当于是#之前的那个页面。可以很容易的进行前后端不分离的部署(也就是把前端打包后的文件放到服务器端的public或static里),因为请求的链接都是ip地址:端口/#/xxxx,因此请求的资源路径永远为/,相当于index.html,而其他的后端API接口都可以正常请求,不会和/冲突,由于前后端不分离也不会产生跨域问题。
缺点就是丑,路径里总有个#

BrowserRouter:

因为BrowserRouter模式下请求的链接都是ip地址:端口/xxxx/xxxx,因此相当于每个URL都会访问一个不同的后端地址,如果后端没有覆盖到路由就会产生404错误。
可以通过加入中间件解决,放在服务器端路由匹配的最后,如果前面的API接口都不匹配,则返回index.html页面。但这样也有一些小问题,因为要求前端路由和后端路由的URL不能重复。
比如商品列表组件叫/product/list,而请求商品列表的API也是/product/list,那么就会访问不到页面,而是被API接口匹配到。
解决方法:
进行前后端分离的部署,比如前端地址ip1:端口1,后端接口地址ip2:端口2,使用Nginx反向代理服务器进行请求分发。前端向后端发起请求的URL为nginx所在的服务器+/api/xxx,通过NGINX的配置文件判断,如果URL以api开头则转发至后端接口,否则转发至前端的地址,访问项目只需访问Nginx服务器即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值