8.8 React路由 🎉
8.8 React路由 🎉
ReactRouter 路由,让用 react.js 构建单页面应用变得轻而易举
单页面应用也称为SPA(Single Page Application),它主要是网页的界面渲染在一个静态的页面上,当用户要从当前界面跳到另一个界面的时候,在这个过程中,不需要重新加载整个页面,所以页面之间的切换十分快速
多页面在进行页面切换时十分缓慢,在路由进行跳转的时候会加载所有的资源,而且页面重复代码多
对比之下,单页面原理是JavaScript动态修改内容而已,资源只需要局部的刷新,因此SPA具有极高的流畅度,有利于提升用户体验
页面与页面之前的切换,是通过不同的地址访问的,地址对应着要显示的组件。而显示这个页面切换显示的功能就是路由的作用
单页面VS多页面
单页面(SPA) | 多页面(MPA) | |
---|---|---|
组成 | 外壳页面和多个页面片段 | 多个完整页面构成 |
资源共用 | 共用、只需在外壳部分加载 | 不共用,每个页面都需要加载 |
刷新方式 | 页面局部刷新或更改 | 整页刷新 |
URL模式 | a.com/#/one 或者 a.com/one | a.com/one.html |
用户体验 | 页面切换快,体验好 | 页面切换慢,体验差 |
数据传递 | 容易(路由或者组件之间传递) | 依赖URL传参、本地存储传参 |
SEO | 不利于SEO(后续有其他解决方案) | 利于SEO |
维护成本 | 相对容易 | 相对复杂 |
React Router 是一个基于 React之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步
我们的React应用是单页面应用(一般只有一个html文件),路由可以让我们实现页面内容的切换。
react-router
为 React Router 提供核心路由功能,但是你不需要直接安装 react-router
react-router-dom
react-router-dom是基于react-router的,适用于浏览器端应用,react-router-dom包含react-router的所有功能,并且又增加了适合在浏览器运行环境下的一些功能
提示
初步体验 💎
- 安装react-router-dom
npm install react-router-dom --save
提示
如果你写 React Native 应用,你应该安装 react-router-native
而不是 react-router-dom
; 当你安装 react-router-dom
或 react-router-native
时,都会将 react-router
作为依赖安装。
先用一个示例来演示一下,下面是我的目录结构
import React from 'react';
import { Routes, Route } from "react-router-dom"
const App = React.lazy(() => import("../App"))
const Home = React.lazy(() => import("../views/home/home"))
const News = React.lazy(() => import("../views/news/news"))
const About = React.lazy(() => import("../views/about/about"))
const About1 = React.lazy(() => import("../views/about/about1/about1"))
const About2 = React.lazy(() => import("../views/about/about2/about2"))
const Error = React.lazy(() => import("../views/error/error"))
const Routers: any = () => {
return (
<Routes>
<Route path='/' element={<App />} >
<Route index element={<Home />} />
<Route path='home' index element={<Home />} />
<Route path='news' element={<News />} />
<Route path='about' element={<About />} >
<Route index element={<About1 />} />
<Route path='about1' index element={<About1 />} />
<Route path='about2' element={<About2 />} />
</Route>
</Route>
<Route path='*' element={<Error />} />
</Routes>
)
}
export default Routers
import React from 'react';
import ReactDOM from 'react-dom/client';
//antd国际化处理
import zhCN from 'antd/locale/zh_CN';
import { ConfigProvider } from 'antd'
import 'antd/dist/reset.css';
import "./assets/scss/public.scss"
//路由
import Routers from "./router/router"
import { BrowserRouter } from "react-router-dom"
import { Suspense } from 'react'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<ConfigProvider locale={zhCN} theme={{ token: { colorPrimary: "#00b96b" } }}>
<Suspense fallback={<div>页面跳转中...</div>}>
<BrowserRouter>
<Routers />
</BrowserRouter>
</Suspense>
</ConfigProvider>
</React.StrictMode >
);
import React from 'react'
import { Outlet } from 'react-router-dom'
const App = () => {
return (
<>
<h1>Header</h1>
<Outlet></Outlet>
<h1>Footer</h1>
</>
)
}
export default App
import React from 'react'
const Home = () => {
return (
<div>
<p>Home</p>
</div>
)
}
export default Home
测试
BrowserRouter和HashRouter 💎
<BrowserRouter>
使用HTML5 History API保持页面 和 URL 的同步,使用干净的URL将当前位置存储在浏览器的地址栏中。
<HashRouter>
使用 Hash 模式路由保持页面 和 URL 的同步。
BrowserRouter与HashRouter区别
BrowseRouter使用HTML5的history API,HashRouter使用哈希
地址表现形式不同
BrowserRouter : http://localhost:3000/app
HashRouter : http://localhost:3000/#/app
BrowseRouter地址的请求会发送到服务器,需要服务器的支持,而HashRouter的不会。
官方推荐使用BrowserRouter。
import React from 'react';
import ReactDOM from 'react-dom/client';
//antd国际化处理
import zhCN from 'antd/locale/zh_CN';
import { ConfigProvider } from 'antd'
import 'antd/dist/reset.css';
import "./assets/scss/public.scss"
//路由
import Routers from "./router/router"
import { BrowserRouter } from "react-router-dom"
import { Suspense } from 'react'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<ConfigProvider locale={zhCN} theme={{ token: { colorPrimary: "#00b96b" } }}>
<Suspense fallback={<div>页面跳转中...</div>}>
<BrowserRouter>
<Routers />
</BrowserRouter>
</Suspense>
</ConfigProvider>
</React.StrictMode >
);
Route和Routes 💎
<Route>
用来配置URL与UI,如果其路径与当前地址栏中URL匹配,则会呈现其元素。
属性
path
:设置访问路径element
:设置对应URL要渲染的组件caseSensitive
:设置路径是否区分大小写,默认为falseindex
:确定路由是否为索引路由
<Routes>
包裹一组<Route>
,每当地址发生变化时,<Routes>
都会查看其所有子<Route>元素
,以找到最佳路径匹配并呈现对应的UI
属性
path
:设置访问路径element
:设置对应URL要渲染的组件
import React from 'react';
import { Routes, Route } from "react-router-dom"
import App from "../App"
import Home from "../views/home/home"
import News from "../views/news/news"
import About from "../views/about/about"
import About1 from "../views/about/about1/about1"
import About2 from "../views/about/about2/about2"
import Error from "../views/error/error"
const Routers = () => {
return (
<Routes>
<Route path='/' element={<App />} >
<Route index element={<Home />} />
<Route path='home' index element={<Home />} />
<Route path='news' element={<News />} />
<Route path='about' element={<About />} >
<Route index element={<About1 />} />
<Route path='about1' index element={<About1 />} />
<Route path='about2' element={<About2 />} />
</Route>
</Route>
<Route path='*' element={<Error />} />
</Routes>
)
}
export default Routers
Outlet 💎
父路由元素中应使用<Outlet>
来渲染其子路由元素。这允许在呈现子路由时显示嵌套UI。
如果URL跟父路由的路径完全匹配,则会默认渲染设置了index属性的子路由,如果子路由中没有设置index的,则不渲染任何子路由。
import React from 'react';
import { Routes, Route } from "react-router-dom"
import App from "../App"
import Home from "../views/home/home"
import News from "../views/news/news"
import About from "../views/about/about"
import About1 from "../views/about/about1/about1"
import About2 from "../views/about/about2/about2"
import Error from "../views/error/error"
const Routers = () => {
return (
<Routes>
<Route path='/' element={<App />} >
<Route index element={<Home />} />
<Route path='home' index element={<Home />} />
<Route path='news' element={<News />} />
<Route path='about' element={<About />} >
<Route index element={<About1 />} />
<Route path='about1' index element={<About1 />} />
<Route path='about2' element={<About2 />} />
</Route>
</Route>
<Route path='*' element={<Error />} />
</Routes>
)
}
export default Routers
Link和NavLink 💎
<Link>
用户可以通过点击它来导航跳转到另一个页面。
属性
to
:设置点击后要跳转的页面地址string
:to的值为字符串,代表要跳转的路径object
: to的值为对象,该对象可以包含下列属性:- pathname:
<string>
表示要链接到的路径 - search:
<string>
查询参数 - hash: 一个放在URL中的Hash,例如 #a-hash.
- pathname:
reloadDocument
:跳过路由,则直接实现<a href/>
的功能(刷新浏览器)state
:属性可用于为存储在history state
中的新位置设置有状态值。随后可以通过useLocation()
访问该值
<NavLink>
是一个特殊的<Link>
,它会知道当前被选中的项是哪个。被选中选会添加.active
class。
属性
- end:精准匹配,使用
<NavLink>
进行跳转时,默认会给选中的元素添加active的类属性,但是,当点击/about
时,首页也会高亮,因为首页的路由地址为/
,而/about
字符中开头字符也是/
,这就造成了模糊匹配,影响精确度。解决办法:开启精准匹配end
<NavLink to="/" end>home</NavLink> <NavLink to="/about" >about1</NavLink> <NavLink to="/login">login</NavLink>
- className:修改默认的class名字.active
增加公共样式
.active{
color:red;
}
点击跳转,查看Link和NavLink的区别
import React from 'react'
import { Outlet } from 'react-router-dom'
import { Link, NavLink } from 'react-router-dom'
const App = () => {
return (
<>
<h1>Header</h1>
<div>
<Link to="/about/about1">about1页</Link>
<Link to={{ pathname: '/about/about2' }} >about2页</Link>
<br />
<NavLink to="/about/about1">about1页</NavLink>
<NavLink to={{ pathname: '/about/about2' }}>about2页</NavLink>
<NavLink to={{ pathname: '/home' }}>home页</NavLink>
<NavLink to={{ pathname: '/news' }}>news页</NavLink>
</div>
<Outlet></Outlet>
<h1>Footer</h1>
</>
)
}
export default App
Navigate 💎
当渲染一个 <Navigate>
元素的时候,就会执行它指向的路由跳转(重定向)
一般逻辑发生变化后,想要跳转到其他页面(非手动点击方式)使用。
import { Navigate } from 'react-router-dom'
const [isLogin, setIsLogin] = useState(false)
// 如果输入了用户名跟密码。则登录成功,跳转到首页
if (nameRef.current.value && pwdRef.current.value) {
setIsLogin(true)
}
return (
<div>
<h2>登录</h2>
<form onSubmit={onLogin}>
用户名:<input ref={nameRef} required/>
密码:<input type='password' ref={pwdRef} required/>
<button type='submit'>登录</button>
</form>
{/* isLogin为ture,则跳转到首页 */}
{isLogin && <Navigate to='/' />}
</div>
)
import { Navigate } from 'react-router-dom'
<Router>
<Routes>
<Route path='/login' element={<Login />}></Route>
<Route path='/' element={<Home />}></Route>
<Route path='/home' element={<Navigate to="/" />}></Route>
</Routes>
</Router>
路由传参 💎
params传参 👻
在要跳转的路由路径中使用:params
这种语法传递参数,然后在跳转后的页面中使用useParams
读取传递过来的参数
路由中定义参数名称
const Routers = () => {
return (
<Router>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/news/:city' element={<News />} />
{/* 设置参数的key值,这里意味着我就可以传递一个名字为city的参数了 */}
<Route path='/about' element={<About />} >
<Route path='about1' element={<About1 />} />
<Route path='about2' element={<About2 />} />
</Route>
</Routes>
</Router>
)
}
页面跳转时传递参数值
import React from 'react'
import { NavLink } from 'react-router-dom'
export default function Home() {
return (
<div>
<NavLink to="/news/beijing">用户页面</NavLink>
</div>
)
}
跳转到的页面接收参数值
import React from 'react'
import { useParams } from 'react-router-dom'
export default function News() {
const params = useParams()
console.log(params)
return (
<div>News</div>
)
}
search传参 👻
如果to
接收一个对象,则可以通过对象的属性search传递参数。
search只支持传递字符串
页面跳转时传递参数值
import React from 'react'
import { NavLink } from 'react-router-dom'
export default function Home() {
return (
<div>
<NavLink to={{ pathname: '/news', search: `city=beijing` }} >用户页面</NavLink>
{/* 或者 */}
<NavLink to={{ pathname: '/news?name=beijing' }} >用户页面</NavLink>
{/* 或者 */}
<NavLink to= '/news?name=beijing'>用户页面</NavLink>
</div>
)
}
跳转到的页面接收参数值
import React from 'react'
import { useSearchParams } from 'react-router-dom'
export default function News() {
const [searchParams, setSearchParams] = useSearchParams()
console.log(searchParams.get("city"))
return (
<div>News</div>
)
}
reateSearchParams使用
params传递参数传递参数需要在路由中提前定义字段,search传递参数只能传递字符串,那么我想传递复杂数据,就可以使用createSearchParams传递参数
使用createSearchParams
支持传递对象或者数组
import React from 'react'
import { NavLink, createSearchParams } from 'react-router-dom'
export default function Home() {
const arr: any = [1, 2, 3, "lcl"]
const obj: any = {
name: "lcl",
age: 30
}
return (
<div>
<NavLink to={{ pathname: '/news', search: `${createSearchParams({ value1: JSON.stringify(arr), value2: JSON.stringify(obj) })}` }} >用户页面</NavLink>
</div>
)
}
跳转到的页面接收参数值
import React from 'react'
import { useSearchParams } from 'react-router-dom'
export default function News() {
const [searchParams, setSearchParams] = useSearchParams()
const arr = JSON.parse(searchParams.get("value1") as any)
const obj = JSON.parse(searchParams.get("value2") as any)
console.log(arr)
console.log(obj)
return (
<div>News</div>
)
}
state传参 👻
使用上述三种方式传递的参数都会显示在地址栏,那么如何不让他显示进行参数传递呢?
通过state
属性传递参数
使用useLocation
来接收state传递的参数,useLocation会返回一个location对象,只要浏览器地址发生改变,则这个location就会随着更新。
通过state
属性传递参数
import React from 'react'
import { NavLink } from 'react-router-dom'
export default function Home() {
return (
<div>
<NavLink to='/news' state={{ city: 'beijing', arr: [1, 2, 3] }}>用户页面</NavLink>
</div>
)
}
跳转到的页面接收参数值
import React from 'react'
import { useLocation } from 'react-router-dom'
export default function News() {
const location = useLocation()
console.log(location.state)//访问location的state属性即可
return (
<div>News</div>
)
}
编程式导航及传参 💎
这个hook,这个函数可以让我们进行编程式路由跳转
import React from 'react'
import { useNavigate } from 'react-router-dom'
export default function Home() {
const navigate = useNavigate()
const jump = () => {
navigate("/news") //不传递参数
//navigate("/news/beijing") //params传参(需要提前在路由中定义)
//navigate({ pathname: '/news', search: `city=beijing` }) //search传参
//navigate('/news', { state: { city: 'beijing' } }) //state传参
}
return (
<div>
<button onClick={jump}>跳转到News</button>
</div>
)
}
至于这三种方式传参的接收方式,上个章节已经提到,这里不再做过多说明
路由懒加载 💎
路由懒加载是非常有必要的,当项目过于庞大,一次性加载太多页面容易造成卡顿,此时懒加载页面就可以改变这一问题!
import React from 'react';
import { Routes, Route } from "react-router-dom"
const App = React.lazy(() => import("../App"))
const Home = React.lazy(() => import("../views/home/home"))
const News = React.lazy(() => import("../views/news/news"))
const About = React.lazy(() => import("../views/about/about"))
const About1 = React.lazy(() => import("../views/about/about1/about1"))
const About2 = React.lazy(() => import("../views/about/about2/about2"))
const Error = React.lazy(() => import("../views/error/error"))
const Routers: any = () => {
return (
<Routes>
<Route path='/' element={<App />} >
<Route index element={<Home />} />
<Route path='home' index element={<Home />} />
<Route path='news' element={<News />} />
<Route path='about' element={<About />} >
<Route index element={<About1 />} />
<Route path='about1' index element={<About1 />} />
<Route path='about2' element={<About2 />} />
</Route>
</Route>
<Route path='*' element={<Error />} />
</Routes>
)
}
export default Routers
要注意引入方式的变化
import Header from './components/header';
更改为
const About = React.lazy(() => import("./components/header"))
除了引入方式更改外,还需使用Suspense
进行包裹
import React from 'react';
import ReactDOM from 'react-dom/client';
//antd国际化处理
import zhCN from 'antd/locale/zh_CN';
import { ConfigProvider } from 'antd'
import 'antd/dist/reset.css';
import "./assets/scss/public.scss"
//路由
import Routers from "./router/router"
import { BrowserRouter } from "react-router-dom"
import { Suspense } from 'react'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<ConfigProvider locale={zhCN} theme={{ token: { colorPrimary: "#00b96b" } }}>
<Suspense fallback={<div>页面跳转中...</div>}>
<BrowserRouter>
<Routers />
</BrowserRouter>
</Suspense>
</ConfigProvider>
</React.StrictMode >
);
错误匹配 💎
import React from 'react';
import { Routes, Route } from "react-router-dom"
const App = React.lazy(() => import("../App"))
const Home = React.lazy(() => import("../views/home/home"))
const News = React.lazy(() => import("../views/news/news"))
const About = React.lazy(() => import("../views/about/about"))
const About1 = React.lazy(() => import("../views/about/about1/about1"))
const About2 = React.lazy(() => import("../views/about/about2/about2"))
const Error = React.lazy(() => import("../views/error/error"))
const Routers: any = () => {
return (
<Routes>
<Route path='/' element={<App />} >
<Route index element={<Home />} />
<Route path='home' index element={<Home />} />
<Route path='news' element={<News />} />
<Route path='about' element={<About />} >
<Route index element={<About1 />} />
<Route path='about1' index element={<About1 />} />
<Route path='about2' element={<About2 />} />
</Route>
</Route>
<Route path='*' element={<Error />} />
</Routes>
)
}
export default Routers
注意
定义错误配置的路由必须放到最后面
使用useRoutes 💎
useRoutes
跟<Routes>
有同等的功能,只不过它使用对象来替代<Route>
下面我用HooKs来改造路由(目录结构仍旧一致)
import React from 'react';
import { useRoutes } from "react-router-dom"
const App = React.lazy(() => import("../App"))
const Home = React.lazy(() => import("../views/home/home"))
const News = React.lazy(() => import("../views/news/news"))
const About = React.lazy(() => import("../views/about/about"))
const About1 = React.lazy(() => import("../views/about/about1/about1"))
const About2 = React.lazy(() => import("../views/about/about2/about2"))
const Error = React.lazy(() => import("../views/error/error"))
const Routers = () => {
return (useRoutes([
{
path: '/',
element: <App />,
children: [
{
index: true,
element: <Home />
},
{
path: 'home',
element: <Home />,
},
{
path: 'news',
element: <News />
},
{
path: 'about',
element: <About />,
children: [
{
index: true,
element: <About1 />
},
{
path: 'about1',
element: <About1 />
},
{
path: 'about2',
element: <About2 />
},
]
},
]
},
{
path: '*',
element: <Error />
},
]))
}
export default Routers