single page web application
,SPA)。ajax
请求获取, 并在前端异步展现。key:value
)key
为路径, value
可能是function
或component
理解: value
是function
, 用来处理客户端提交的请求。
注册路由:
router.get(path, function(req, res))
工作过程:当node
接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
浏览器端路由,value
是component
,用于展示页面内容。
注册路由:
工作过程:当浏览器的path
变为/test
时, 当前路由组件就会变为Test
组件
需求:
完成组件的静态结构,明确好界面中的导航区、展示区
//App.jsx
export default class App extends Component {
render() {
return (
)
}
}
//Header.jsx
export default class Header extends Component {
render() {
return (
React Router Demo
)
}
}
//About.jsx
export default class About extends Component {
render() {
return (
我是About的内容
)
}
}
//Home.jsx
export default class Home extends Component {
render() {
return (
我是Home的内容
)
}
}
引入并使用路由API注册路由
导航区的a标签改为Link标签
Demo
展示区写Route标签进行路径的匹配
import { Link, BrowserRouter, Route } from 'react-router-dom'
export default class App extends Component {
render() {
return (
{/* 原生html中,靠跳转不同的页面 */}
{/* About
Home */}
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
{/* */}
About
Home
{/* */}
{/* 注册路由 */}
{/* */}
{/* */}
)
}
}
注意点:
是两种路由模式中的一种,需要将路由链接和注册路由的标签写在同一个标签中产生关联,为了简化后期编码的便利,一般都是直接用标签将父组件包裹起来,这样整个应用中子组件都可以产生路由关联
import {BrowserRouter} from 'react-router-dom'
createRoot(document.getElementById('root')).render(<BrowserRouter><App/></BrowserRouter>)
将案例说,
Header
组件为一般组件,About
和Home
组件为路由组件,所以可以将About
和Home
组件放在一个名为pages
文件夹中,这个文件夹专门用来存放路由组件文件,components
文件夹用来存放一般组件的文件
1.写法不同:
一般组件:
路由组件:
2.存放位置不同:
一般组件:components
路由组件:pages
3.接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性:
history:
go:f go(n)
goBack:f goBack()
goForward:f goForward()
push:f push(path,state)
replace:f replace(path,state)
location:
pathname:"/about"
search:"
state:undefined
match:
params:{}
path:"/about"
url:"/about"
//About.jsx
export default class About extends Component {
render() {
console.log('About路由组件:',this.props);
return (
我是About的内容
)
}
}
需求:当点击某个组件链接时,该链接会高亮。
实现:
- 原生JS:通过链接元素的点击事件将高亮样式
active
加到该链接元素上- CSS:通过元素的
:hover
伪类选择器为元素添加高亮效果- Vue:通过
@click
事件通过判断条件将高亮样式active
加到该链接元素上- React:通过
NavLink
标签实现,且该标签的默认高亮样式名就为active
<head>
<meta charset="UTF-8">
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./css/bootstrap.css">
<style>
.atguigu{
background-color: rgb(209, 137, 4) !important;
color: white !important;
}
style>
<title>08title>
head>
<body>
<div id="root">div>
body>
html>
//App.jsx
export default class App extends Component {
render() {
return (
{/* NavLink可以实现路由链接的高亮通过activeClassName指定样式名 */}
About
Home
)
}
}
可以通过封装
NavLink
的方式来减少几个NavLink
标签实际应用时重复相同的内容
//MyNacLink.jsx
import { NavLink } from 'react-router-dom'
export default class MyNacLink extends Component {
render() {
const {to,title}=this.prosp
return (
{title}
)
}
}
export default class App extends Component {
render() {
return (
)
}
}
不足之处在于假如在
MyNacLink
组件中传递了太多的数值,那么MyNacLink
组件取值将会变得繁琐,比如
- 通过解构赋值的方式将数值从
this.props
中取很繁琐:title
值需要另外通过{}
取
export default class MyNacLink extends Component {
render() {
const {to,title,a,b}=this.props
return (
{title}
)
}
}
假如将
title
值写在标签体中,这样就可以通过this.props.children
可以获取标签体内容:
//标签体内容是一个特殊的标签属性
About
export default class MyNacLink extends Component {
render() {
console.log(this.props);
// const {to,title}=this.props
return (
{title}
)
}
}
这样就可以通过
...
运算符将直接将this.props
中所有的值都传递到NavLink
组件中:
export default class MyNacLink extends Component {
render() {
return (
{title}
)
}
}
通常情况下,
path
和component
是一一对应的关系,但是不免有时候会重复path
,这样会造成同时显示两个组件的内容:
export default class App extends Component {
render() {
return (
About
Home
)
}
}
可以使用
Switch
来解决这个问题,Switch可以提高路由匹配效率(单一匹配)。
export default class App extends Component {
render() {
return (
About
Home
)
}
}
假如在路由匹配路径为多重,那么当网页刷新时就会出现样式丢失的问题:
export default class App extends Component {
render() {
return (
About
Home
)
}
}
问题:为什么会造成样式的丢失呢?
回答:在刷新后,请求的
bootstrap.css
的响应内容发现了变化:
刷新前请求的
bootstrap.css
的响应内容为一段CSS样式代码:
刷新后请求的
bootstrap.css
的响应内容为一段HTML代码,而且返回的HTML网页内容就是当前react
脚手架项目的index.html
文件的内容:
**分析原因:**刷新前后请求bootstrap.css
的路径不一样
刷新后的路径:
可以看到脚手架在请求
bootstrap.css
资源时,错误的将/atguigu
也当成了请求路径的一部分,因此导致请求不到正确的样式资源。在脚手架中有一个配置,当脚手架请求不到正确内容时,会将根目录
public
下的index.html
文件返回作为响应
解决方案:
将引入样式文件的相对路径进行修改
<link rel="stylesheet" href="/css/bootstrap.css">
在引入样式文件的路径中添加%PUBLIC_URL%
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
将路由模式修改为HashRouter
import { HashRouter } from "react-router-dom";
createRoot(document.getElementById('root')).render(<HashRouter><App/></HashRouter>)
在路由匹配中有严格匹配与模糊匹配两种模式,默认是模糊模式,因此可以在路由匹配正确路径后面添加其他内容:
export default class App extends Component {
render() {
return (
About
Home
)
}
}
需要注意:模糊匹配只会匹配路径的第一个路径字段,如果第一个匹配不上也不会往后匹配了
export default class App extends Component {
render() {
return (
About
Home
)
}
}
export default class App extends Component {
render() {
return (
About
Home
)
}
}
当需要组件一加载就显示某个路由组件时,可以使用
Redirect
重定向到指定的路由组件
export default class App extends Component {
render() {
return (
About
Home
)
}
}
需求:
实现:
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
//Home.js
export default class Home extends Component {
render() {
return (
Home组件内容
-
News
-
Message
)
}
}
需求:
路由链接(携带参数):
详情
注册路由(声明接收):
export default class Message extends Component {
state = {
messageArr: [
{ id: '001', title: '消息1' },
{ id: '002', title: '消息2' },
{ id: '003', title: '消息3' },
]
}
render() {
const { messageArr } = this.state
return (
{
messageArr.map(msgObj => {
return (
-
{/* 向路由组件传递params参数 */}
{msgObj.title}
)
})
}
)
}
}
接收参数:
const{id,title}=this.props.match.params
const DetailData = [
{ id: '001', content: "你好,中国" },
{ id: '002', content: "你好,比奇堡" },
{ id: "003", content: "你好,海绵宝宝" }
]
export default class Detail extends Component {
render() {
// 接收params参数
console.log(this.props);
const { id, title } = this.props.match.params
const findResult = DetailData.find((detailObj) => {
return detailObj.id === id
})
return (
- ID:{id}
- TITLE:{title}
- CONTENT:{findResult.content}
)
}
}
路由链接(携带参数):
详情
注册路由(无需声明,正常注册即可):
export default class Message extends Component {
state = {
messageArr: [
{ id: '001', title: '消息1' },
{ id: '002', title: '消息2' },
{ id: '003', title: '消息3' },
]
}
render() {
const { messageArr } = this.state
return (
{
messageArr.map(msgObj => {
return (
-
{/* 向路由组件传递search参数 */}
{msgObj.title}
)
})
}
{/* */}
{/* search参数无需声明接收,正常注册路由即可 */}
)
}
}
接收参数:
const{search}=this.props.location
备注:获取到的search
是urlencoded
编码字符串,需要借助querystring
解析
import qs from "query-string"
console.log(qs.stringify({name:"tom",age:18}));
urlencoded
编码方式:
let obj={name:"tom",age:18} => name=tom&age=18 key=value&key=value
const DetailData = [
{ id: '001', content: "你好,中国" },
{ id: '002', content: "你好,比奇堡" },
{ id: "003", content: "你好,海绵宝宝" }
]
export default class Detail extends Component {
render() {
// 接收search参数
console.log(this.props);
const {search}=this.props.location
// search.slice(1):去掉字符串前的?
const {id,title}=qs.parse(search.slice(1))
const findResult = DetailData.find((detailObj) => {
return detailObj.id === id
})
return (
- ID:{id}
- TITLE:{title}
- CONTENT:{findResult.content}
)
}
}
路由链接(携带参数):
详情
注册路由(无需声明,正常注册即可):
export default class Message extends Component {
state = {
messageArr: [
{ id: '001', title: '消息1' },
{ id: '002', title: '消息2' },
{ id: '003', title: '消息3' },
]
}
render() {
const { messageArr } = this.state
return (
{
messageArr.map(msgObj => {
return (
-
{/* 向路由组件传递state参数 */}
{msgObj.title}
)
})
}
{/* state参数无需声明接收,正常注册路由即可 */}
)
}
}
接收参数:this,props.location.state
const DetailData = [
{ id: '001', content: "你好,中国" },
{ id: '002', content: "你好,比奇堡" },
{ id: "003", content: "你好,海绵宝宝" }
]
export default class Detail extends Component {
render() {
// 接收state参数
// 切记需要添加 ||{} 以免引起报错
const {id,title}=this.props.location.state||{}
const findResult = DetailData.find((detailObj) => {
return detailObj.id === id
})||{}
return (
- ID:{id}
- TITLE:{title}
- CONTENT:{findResult.content}
)
}
}
备注:刷新也可以保留住参数
在注册路由时开启replace模式:
当需要在事件中进行跳转路由时,就可以使用编程式路由。
我们知道在路由组件的
props
中包含有history
、location
、match
,其中history
中的go
、goBack
、goForward
、push
、replace
三个方法就可以供我们实现编程式路由导航
export default class Message extends Component {
state = {
messageArr: [
{ id: '001', title: '消息1' },
{ id: '002', title: '消息2' },
{ id: '003', title: '消息3' },
]
}
replaceShow = (id, title) => {
this.props.history.replace(`/home/message/detail`)
}
pushShow = () => {
this.props.history.push(`/home/message/detail`)
}
back=()=>{
this.props.history.goBack()
}
forward=()=>{
this.props.history.goForward()
}
render() {
console.log(this.props);
const { messageArr } = this.state
return (
{
messageArr.map(msgObj => {
return (
-
{msgObj.title}
)
})
}
)
}
}
export default class Message extends Component {
state = {
messageArr: [
{ id: '001', title: '消息1' },
{ id: '002', title: '消息2' },
{ id: '003', title: '消息3' },
]
}
replaceShow = (id, title) => {
// replace跳转+传递param参数
this.props.history.replace(`/home/message/detail/${id}/${title}`)
}
pushShow = (id, title) => {
// push跳转+传递param参数
this.props.history.push(`/home/message/detail/${id}/${title}`)
}
back=()=>{
this.props.history.goBack()
}
forward=()=>{
this.props.history.goForward()
}
render() {
console.log(this.props);
const { messageArr } = this.state
return (
{
messageArr.map(msgObj => {
return (
-
{/* 向路由组件传递params参数 */}
{{msgObj.title}
)
})
}
)
}
}
export default class Message extends Component {
state = {
messageArr: [
{ id: '001', title: '消息1' },
{ id: '002', title: '消息2' },
{ id: '003', title: '消息3' },
]
}
replaceShow = (id, title) => {
// replace跳转+传递query参数
this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
}
pushShow = (id, title) => {
// push跳转+传递query参数
this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
}
back=()=>{
this.props.history.goBack()
}
forward=()=>{
this.props.history.goForward()
}
render() {
console.log(this.props);
const { messageArr } = this.state
return (
{
messageArr.map(msgObj => {
return (
-
{/* 向路由组件传递search参数 */}
{msgObj.title}
)
})
}
)
}
}
export default class Message extends Component {
state = {
messageArr: [
{ id: '001', title: '消息1' },
{ id: '002', title: '消息2' },
{ id: '003', title: '消息3' },
]
}
replaceShow = (id, title) => {
// replace跳转+传递state参数
this.props.history.replace(`/home/message/detail`, { id, title })
}
pushShow = (id, title) => {
// push跳转+传递state参数
this.props.history.push(`/home/message/detail`, { id, title })
}
back=()=>{
this.props.history.goBack()
}
forward=()=>{
this.props.history.goForward()
}
render() {
console.log(this.props);
const { messageArr } = this.state
return (
{
messageArr.map(msgObj => {
return (
-
{/* 向路由组件传递state参数 */}
{msgObj.title}
)
})
}
)
}
}
如果需要使一般路由也有跟路由组件一样的
props
属性以此来实现一些效果,比如浏览记录的回退和前进,可以使用withRouter
让一般组件具备路由组件所特有的API
//Header.jsx
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Header extends Component {
back = () => {
this.props.history.goBack()
}
forward = () => {
this.props.history.goForward()
}
render() {
return (
React Router Demo
)
}
}
export default withRouter(Header)
loca1host:3009/demo/test
localhost:3o00/#/demo/test