源码地址:https://github.com/BLKNjyty/reactstudy
npm install --save react-router-dom
- 1
单页Web应用(single page web application,SPA)。
整个应用只有一个完整的页面。
点击页面中的链接不会刷新页面,只会做页面的局部更新。
数据都需要通过ajax请求获取, 并在前端异步展现。
理解:
1.一个路由就是一个映射关系(key:value)
- key为路径, value可能是function或component
后端路由:
理解: value是function, 用来处理客户端提交的请求。
注册路由: router.get(path, function(req, res))
工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
前端路由:
浏览器端路由,value是component,用于展示页面内容。
注册路由:
工作过程:当浏览器的path变为/test时, 被前端路由器检测到了路径变化,当前路由组件就会变为Test组件
依赖于浏览器浏览历史工作,
见:其他 -前端路由的基石 ,由此可见,有两种方法控制浏览器
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>前端路由的基石_historytitle>
head>
<body>
<a href="http://www.atguigu.com" onclick="return push('/test1') ">push test1a><br><br>
<button onClick="push('/test2')">push test2button><br><br>
<button onClick="replace('/test3')">replace test3button><br><br>
<button onClick="back()"><= 回退button>
<button onClick="forword()">前进 =>button>
<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js">script>
<script type="text/javascript">
// let history = History.createBrowserHistory() //方法一,直接使用H5推出的history身上的API
let history = History.createHashHistory() //方法二,hash值(锚点)
//向浏览器的历史记录中放一些记录
function push (path) {
history.push(path)
return false
}
//replace是替换栈顶的那条历史记录
function replace (path) {
history.replace(path)
}
function back() {
history.goBack()
}
function forword() {
history.goForward()
}
//监听url路径的变化
history.listen((location) => {
console.log('请求路由路径变化了', location)
})
script>
body>
html>
react的一个插件库。
专门用来实现一个SPA应用。
基于react的项目基本都会用到此库。
1.写法
//一般 <Demo/> //路由组件 <Routes> <Route path="/about" element={<About/>}/> <Route path="/home" element={<Home/>}/> </Routes>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
2.放的位置应该不一样
3.接受的props不同,一般组件一般是传递了什么就接收什么。
路由组件接受三个固定属性:history,location,match
history: go: goBack: goForward: push: replace: location: pathname:获取当前的路由路径 search: state: match: params: path:获取当前的路由路径 url:获取当前的路由路径
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生html中,靠跳转不同的页面 */}
{/* About
Home */}
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
Home About
export default class Home extends Component {
render() {
return (
<h3>我是Home的内容</h3>
)
}
}
export default class About extends Component {
render() {
console.log('About组件收到的props是',this.props);
return (
<h3>我是About的内容</h3>
)
}
}
在React中靠路由链接实现切换组件–编写路由链接
About
Home
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<Header/>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生html中,靠跳转不同的页面 */}
{/* About
Home */}
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
<NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default class Header extends Component {
render() {
// console.log('Header组件收到的props是',this.props);
return (
<div className="page-header"><h2>React Router Demo</h2></div>
)
}
}
export default class MyNavLink extends Component {
render() {
// console.log(this.props);
return (
<NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>
)
}
}
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<Header/>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生html中,靠跳转不同的页面 */}
{/* About
Home */}
{/* 在React中靠路由链接实现切换组件--编写路由链接
标签体的值是key为children的标签属性的值传递过去了,MyNavLink组件的{...this.props}直接接受了这个参数:
children:About
*/}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
{/* 注册路由,/home下有两个路由组件,加入switch之后,匹配到第一个就不会再匹配了 */}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Swtch>
原因:
路由多级时这时刷新,样式会丢失!
原因:
index.html的样式引入改为,点表示当前目录
解决办法:
1.将index.html的样式引入改为
2.将index.html的样式引入改为
3.使用HashRouter代替BrowserRouter,因为将url的#后的值视为无效
当请求的东西不存在,则把public下的index.html返回 (这是脚手架设置的)
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<Header/>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生html中,靠跳转不同的页面 */}
{/* About
Home */}
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<MyNavLink to="/atguigu/about">About</MyNavLink>
<MyNavLink to="/atguigu/home">Home</MyNavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由
路由多级时这时刷新,样式会丢失!
原因:
index.html的样式引入改为,点表示当前目录
解决办法:
1.将index.html的样式引入改为
2.将index.html的样式引入改为
3.使用HashRouter代替BrowserRouter,因为将url的#后的值视为无效
当请求的东西不存在,则把public下的index.html返回 (这是脚手架设置的)*/}
<Switch>
<Route path="/atguigu/about" component={About}/>
<Route path="/atguigu/home" component={Home}/>
</Switch>
</div>
</div>
</div>
</div>
</div>
)
}
}
{/* 注册路由 exact开启了严格匹配 */}
<Switch>
<Route exact path="/about" component={About}/>
<Route exact path="/home" component={Home}/>
</Switch>
{/* 注册路由 Redirect 谁都匹配不上 跳转的路由*/}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
home路由下面增加Message和News路由
export default class Home extends Component {
render() {
return (
<div>
<h3>我是Home的内容</h3>
<div>
<ul className="nav nav-tabs">
{/* 在父路由的下面接着写路由
/home/news 模糊匹配,和Home路由也匹配,所以父组件也会展示
/home/news 模糊匹配,又和news路由匹配
所以不能开启严格匹配
*/}
<li>
<MyNavLink to="/home/news">News</MyNavLink>
</li>
<li>
<MyNavLink to="/home/message">Message</MyNavLink>
</li>
</ul>
{/* 注册路由 */}
<Switch>
<Route path="/home/news" component={News}/>
<Route path="/home/message" component={Message}/>
<Redirect to="/home/news"/>
</Switch>
</div>
</div>
)
}
}
点击Message页面的详细信息链接在下面显示detail,并且把参数传递过去
export default class Message extends Component {
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
render() {
const {messageArr} = this.state
return (
<div>
<ul>
{
messageArr.map((msgObj)=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr/>
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
</div>
)
}
}
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,尚硅谷'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收params参数
const {id,title} = this.props.match.params
const findResult = DetailData.find((detailObj)=>{
//查找的条件
return detailObj.id === id
})
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
}
export default class Message extends Component {
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
render() {
const {messageArr} = this.state
return (
<div>
<ul>
{
messageArr.map((msgObj)=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr/>
{/* 声明接收params参数 */}
{/* */ }
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
}
// qs有两个有用的功能:
//针对urlencoding编码:key=value&key1=value1==>qs
//将json对象格式转化为此编码格式:qs.stringfy(urlencoding);反过来qs.paese(json)
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,尚硅谷'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
})
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
}
export default class Message extends Component {
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
render() {
const {messageArr} = this.state
return (
<div>
<ul>
{
messageArr.map((msgObj)=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr/>
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
}
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,尚硅谷'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收state参数,刷新页面 上述三种参数传递方式 参数都不会丢失
//为防止查出来是undefined(比如清除了缓存或者就没传递数据),则使用{}空对象,防止浏览器报错
//HashRouter路由会导致参数的丢失
const {id,title} = this.props.location.state || {}
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
}) || {}
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
}
即不靠路由标签Link或者MyNavLink 标签
自己写函数完成路由的跳转
export default class Message extends Component {
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
replaceShow = (id,title)=>{
//replace跳转+携带params参数
//this.props.history.replace(`/home/message/detail/${id}/${title}`)
//replace跳转+携带search参数
// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
//replace跳转+携带state参数
this.props.history.replace(`/home/message/detail`,{id,title})
}
pushShow = (id,title)=>{
//push跳转+携带params参数
// this.props.history.push(`/home/message/detail/${id}/${title}`)
//push跳转+携带search参数
// this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
//push跳转+携带state参数
this.props.history.push(`/home/message/detail`,{id,title})
}
back = ()=>{
this.props.history.goBack()
}
forward = ()=>{
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(-2)
}
render() {
const {messageArr} = this.state
return (
<div>
<ul>
{
messageArr.map((msgObj)=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
<button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button>
<button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button>
</li>
)
})
}
</ul>
<hr/>
{/* 声明接收params参数 */}
{/* */ }
{/* search参数无需声明接收,正常注册路由即可 */}
{/* */ }
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
class Header extends Component {
back = ()=>{
this.props.history.goBack()
}
forward = ()=>{
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(-2)
}
render() {
console.log('Header组件收到的props是',this.props);
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
export default withRouter(Header)
//withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
//withRouter的返回值是一个新组件
//要不然一般组件就没有路由组件的history这些api
1.底层原理不一样:
BrowerRouter使用的时h5的history(不是this.props.history这个,这个是React对h5的history的封装),不兼容ie9以及以下版本
HashRouter使用的时URL的哈希值
2.url表现形式不一样:
BrowerRouter没有#,如:localhost:3000/demo/test
HashRouter的路径包含#,如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响(重要)
BrowerRouter没有影响,因为statev保存在state对象中
HashRouter刷新为导致state参数丢失
4.HashRouter可以用来解决一些路径错误的相关问题(比如样式丢失的问题)
1.代替Switch
2.和Route配合使用,Route外边必须包裹Routes
3.Route相当于if语句,如路径和当前的url匹配,则呈现对应的组件
4.属性用于指定:路径匹配知否忽略大小写(more那位false)
5.当路由变化是,Routes匹配所有的Route元素找到最佳的匹配并呈现组件
6.Routes也可以嵌套使用,且配合useRoutes配置路由表,但需要通过Outlet组件来渲染子路由
重要:使用useRoutes
路由表
export default [
{
path:'/about',
element:<About/>
},
{
path:'/home',
element:<Home/>,
children:[
{
path:'news',
element:<News/>
},
{
path:'message',
element:<Message/>,
children:[
{
// path:'detail/:id/:title',
path:'detail',
element:<Detail/>
}
]
}
]
},
{
path:'/',
element:<Navigate to="/about"/>
}
]
路由表暴露在App.jsx中,
import { useRoutes} from 'react-router-dom'
// 路由表,根据路由表生成路由
const element=useRoutes(routes)
//在函数组件的return片段需要注册路由的片段中添加
{element}
例子:
end:子路由匹配到,父路由就不高亮了。
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
<NavLink className={computedClassName} to="/about">About</NavLink>
<NavLink className={computedClassName} end to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{element}
</div>
</div>
</div>
</div>
</div>
)
function computedClassName({isActive}){
//console.log(isActive)
return isActive ? 'list-group-item atguigu' : 'list-group-item'
}
只要被渲染在页面上就会切换视图
除了to属性,还有replace属性。
//1.在路由表中,表示url以/结尾默认展示about组件
{
path:'/',
element:<Navigate to="/about"/>
}
//2.满足条件即跳转,比如当前页面的sum变为2时,自动跳转为about组件(替换当前的home组件)
export default function Home() {
const [sum, setSum] = React.useState(1)
return (
<div>
<h3>我是Home的内容</h3>
{/* {this.state.sum}
*/}
{sum === 2 ? <Navigate to="/about" replace={false} /> : <h4>当前的sum值是{sum}</h4>}
<button onClick={() => setSum(sum => sum + 1)}>点击将sum变为2</button>
</div>
)
}
computedClassName({isActive}){
console.log(isActive)
return isActive ? 'list-group-item atguigu' : 'list-group-item'
}
//不可以在标签里直接用activeClassName="atguigu" 这种形式了
Home
点击message组件中的链接,在下方展示另外一个组件,里面有传递过去的内容
export default function Message() {
const [messageArr,setessageArr] = React.useState(
[
{ id: '01', title: '消息1' },
{ id: '02', title: '消息2' },
{ id: '03', title: '消息3' },
]
)
return (
<div>
<ul>
{
messageArr.map((msgObj) => {
return (
<li key={msgObj.id}>
{/* 向路由组件传递params参数 */}
{/* {msgObj.title} */}
{/* search参数 */}
{/* {msgObj.title} */}
<Link to="detail" state={{
id: msgObj.id,
title: msgObj.title,
}}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr />
<Outlet />
</div>
)
}
{
path:'/home',
element:<Home/>,
children:[
{
path:'news',
element:<News/>
},
{
path:'message',
element:<Message/>,
children:[
{
//params传参数需要声明,其他两种不需要
// path:'detail/:id/:title',
path:'detail',
element:<Detail/>
}
]
}
]
},
const DetailData = [
{ id: '01', content: '你好,中国' },
{ id: '02', content: '你好,尚硅谷' },
{ id: '03', content: '你好,未来的自己' }
]
export default function Detail() {
// console.log(this.props);
//`接收params参数
// const { id, title } = useParams()
//接收search参数,用于更新接收到的参数setSearch(`id=0001&title=hahha`)
// const [search,setSearch]=useSearchParams()
// const id=search.get('id')
// const title=search.get('title')
//state对象
const {state:{id,title}}=useLocation()
const findResult = DetailData.find((detailObj) => {
//查找的条件
return detailObj.id === id
})
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
不需要Link navLink,比如点击图片跳转的时候或者点击一个自定义标签跳转的时候
使用useNavigate进行自定义跳转,同时这种形式只能接收state参数,可以设置跳转类型push/replace
export default function Message() {
const [messageArr,setessageArr] = React.useState(
[
{ id: '01', title: '消息1' },
{ id: '02', title: '消息2' },
{ id: '03', title: '消息3' },
]
)
const navigate=useNavigate()
function showDetail(msgObj){
navigate('detail',{
replace:false,
// paramhe search参数不能这样写,直接写在路径上
state:{
id:msgObj.id,
title:msgObj.title
}
})
}
return (
<div>
<ul>
{
messageArr.map((msgObj) => {
return (
<li key={msgObj.id}>
<Link to="detail" state={{
id: msgObj.id,
title: msgObj.title,
}}>{msgObj.title}</Link>
<button onClick={()=>showDetail(msgObj)}>展示详情</button>
</li>
)
})
}
</ul>
<hr />
<Outlet />
</div>
)
}
export default function Header() {
const navigate=useNavigate()
function back(){
navigate(-1)
}
function forward(){
navigate(1)
}
return (
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
<button onClick={forward}>前进</button>
<button onClick={back}>后退</button>
</div>
)
}
如果组件在Router的上下文呈现,则其返回true,否则返回false
//一般,表示APP 及其子组件都是在上下文环境中,即被被BrowserRouter包裹(不区分路由和非路由组件)
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
)
返回用户是如何来到当前页面的
返回值:
pop,push,replace
备注:
pop是指在浏览器中直接打开路由组件(刷新当前页面)
作用:用来呈现当前组件中渲染的嵌套路由组件
const result=useOutLet() console.log(result) //如果嵌套路由组件已经挂在,则显示嵌套路由对象,否则为null
- 1
- 2
- 3
解析一个url,解析其中的path,search,hash
本文参考b站尚硅谷视频 链接