持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
写在前面
在最近看了React之后,一直觉得学的懵懵然,虽然很多大佬的手写笔记,写的都很不错,但是我一直没有我想要的那种细无巨细,比如类式组件this指向问题的追根溯源,又比如三大实例属性简写的由来,总之我还是决定做一份事无巨细的笔记。
那就让我们开始吧!
对SPA应用的理解
1. 单页Web应用(single page web application,SPA)。
2. 整个应用只有一个完整的页面。
3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。
4. 数据都需要通过ajax请求获取, 并在前端异步展现。
路由的理解
1. 什么是路由?
1. 一个路由就是一个映射关系(key:value)
2. key为路径, value可能是function或component
路由分类
1. 后端路由:
1) 理解: value是function, 用来处理客户端提交的请求。
2) 注册路由: router.get(path, function(req, res))
3) 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
2. 前端路由:
1) 浏览器端路由,value是component,用于展示页面内容。
2) 注册路由: `<Route path="/test" component={Test}>`
3) 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
前端路由原理
BOM对象上的history属性
- 引入history库
<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
- 创建history对象
方法一:会刷新页面
// let history = History.createBrowserHistory() //方法一,直接使用H5推出的history身上的API
方法二:并不会刷新页面
let history = History.createHashHistory() //方法二,hash值(锚点)
- 浏览器的历史记录是一个栈的数据结构
路由的基本使用
react-router-dom的理解
1. react的一个插件库。
2. 专门用来实现一个SPA应用。
3. 基于react的项目基本都会用到此库。
-
react-router库有三种实现 有web,native,any3个平台。
-
路由叫做Route、路由器叫做Router
-
一般React-xxx就是一个React插件库
官方网站
- 印记中文
传送门
- React-route-dom
传送门
安装React-router-dom
下载react-router-dom: npm install --save react-router-dom
yarn add react-router-dom
- 通过点击导航区的导航,引起历史记录的路径改变,被前端路由器检测到。
页面更新
原生html中,靠<a>跳转不同的页面
{/* 原生html中,靠<a>跳转不同的页面 */}
{/* <a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item active" href="./home.html">Home</a> */}
在React中靠路由链接实现切换组件--编写路由链接
- 引入Link
import {Link,Route} from 'react-router-dom'
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
- 注意to属性的值没有“.”。
选择确切的Router
-
选择BrowserRouter
<BrowserRouter>
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
</BrowserRouter>
2.注册路由
// 错误写法
<BrowserRouter>
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</BrowserRouter>
注意:需要将注册路由和路由链接写在一个路由器内
- 在Index入口文件,书写路由器,为了组件更新书写的方便
//引入ReactDOM
import ReactDOM from 'react-dom'
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
)
路由组件和一般组件
使用hashRouter
//
import {HashRouter} from 'react-router-dom'
ReactDOM.render(
<HashRouter>
<App/>
</HashRouter>,
document.getElementById('root')
)
路由组件
-
创建pages文件夹,内部放入路由组件的文件夹
-
路由组件带有属性
1.写法不同:
一般组件:`<Demo/>`
路由组件:`<Route path="/demo" component={Demo}/>`
2.存放位置不同:
一般组件:components
路由组件:pages
3.接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/about"
search: ""
state: undefined
match:
params: {}
path: "/about"
url: "/about"
NavLink的使用
动态追加属性名
NavLink被点击的时候会给标签添加一个active属性。Bootstrap库中avtive正好就是这个背景色加深。可以使用动态activeClassName。
1. NavLink可以实现路由链接的高亮,通过activeClassName指定样式名。点击哪个标签就添加这个属性名。
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
<NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink>
- index.html文件书写样式
<style>
.atguigu{
background-color: rgb(209, 137, 4) !important;
color: white !important;
}
</style>
注意:这里引入的bootstrap库,所以记得添加权限
封装NavLink组件
目的
- 解决代码冗余度
创建一个MyNavLink组件封装路由组件
import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
return (
<NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>
)
}
}
标签内容是一个特殊的标签属性,叫做children。可以通过this.props.children可以获取标签体内容。
Switch的使用
报错
报错提示:'Switch' is not exported from 'react-router-dom'.
原因分析:因为最新版本的react-router-dom@v6版本已经将Switch替换成了Routes了,所以报错了。
解决方法
1.使用Routes替换Switch
2.卸载最新版本安装@v5版本就可以继续使用Switch了
Switch的使用
1.通常情况下,path和component是一一对应的关系。
2.Switch可以提高路由匹配效率(单一匹配)。
3.从上到下依次匹配
解决样式丢失问题
脚手架内置服务器
http://localhost:3000/ 就是通过webpack devServer开启的服务器
- 解决多级路径刷新页面样式丢失的问题
Public文件夹就是localhost3000下面根路径,index.html就是访问路径的兜底页面。(路径出错)
解决办法:
1.public/index.html 中 引入样式时不写 ./ (代表在当前路径的文件夹下寻找)写 / (在http://localhost:3000/下直接寻找)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)绝对路径
3.使用HashRouter(少见)
路由路径为多级
请求bootstrap路径的变化
- 在注册路由中添加了一级
在第一次启动项目,默认路径在http://localhost:3000/
也就是请求bootstrap的路径是http://localhost:3000/css/bootstrap.css
- 刷新页面
刷新页面之后就会更换请求路径http://localhost:3000/atguigu/css/bootstrap.css
这样就会导致请求样式失败。由于路径层级错误,那么就会返回index.html
路由的模糊匹配与严格匹配
不用混用yarn 和 npm
- 避免包的丢失
1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致,多给可以,少给不行,顺序不能改变)
2.开启严格匹配:<Route exact={true} path="/about" component={About}/>(exact可以简写)
简写<Route exact path="/about" component={About}/>
3严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
Redirect(重定向)的使用
1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
2.具体编码:
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
嵌套路由
嵌套路由
1.注册子路由时要写上父路由的path值(保证之前注册的路由也可以匹配上,保证父组件不会丢)
2.路由的匹配是按照所有注册路由的顺序进行的
3.组件一挂载,组件内部注册路由就会开始匹配,结合使用Redirect可以达到默认选择子组件的效果。
具体实现
-
编写路由链接(App组件)
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
-
注册路由(App组件)
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
- 注册路由(Home组件)
{/* 注册路由 */}
<Switch>
<Route path="/home/news" component={News}/>
<Route path="/home/message" component={Message}/>
<Redirect to="/home/news"/>
</Switch>
向路由组件传递参数数据
在message组件遍历状态
// 状态
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
messageArr.map((msgObj)=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
</li>
)
})
Detail组件
- 编写路由链接
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
- 注册路由(在Message组件)
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
- 实现效果
想Detail组件传递参数(使用parmas)
- Link标签操作传递属性
注意:使用js表达式记得添加{}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
- 注册路径(接受参数)
注意:接受参数书写:,:后面的就是属性名
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
这里传递的属性会保存在路由组件的props属性内。
根据传递的数据进行渲染
- 初始化状态
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,尚硅谷'},
{id:'03',content:'你好,未来的自己'}
]
- 根据传入id使用数组的find方法
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
})
params参数
路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情`</Link>`
注册路由(声明接收):`<Route path="/demo/test/:name/:age" component={Test}/>`
接收参数:this.props.match.params
向路由组件传递search参数
- Link标签操作传递属性
注意:使用js表达式记得添加{},书写带&的字符串
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
-
search参数无需声明接收,正常注册路由即可
-
查询传递的search属性是字符串形式
qs库使用
接收search参数
// 接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1)) // 去掉问号
向路由组件传递state参数
state参数(不是组件中的那个state)
- 向路由组件传递state参数(书写对象形式)
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
- state参数无需声明接收,正常注册路由即可
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
- Detail组件接受参数
// 接收state参数
const {id,title} = this.props.location.state || {}
- 地址栏不带参数,刷新不改变
刷新仍然可以保住数据,因为BrowserRouter会维护history对象,state在location上,location在history身上。
- 为了避免清除历史记录之后,请求失败
给state一个备用值“空对象”
路由跳转的两种模式 push与repalce
实现效果
历史记录回退
0 在回退的时候,都是根据栈保存的地址进行回退
push
- 是一种压栈操作
replace
- 是一种替换操作
在Link中添加replace属性。
Replace = {true} 简写为replace
编程式路由导航
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
-this.prosp.history.push()
-this.prosp.history.replace()
// 对应路由导航那个组件。Message组件
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})
}
// 使用onClcik事件函数进行调用,这是使用的是非柯里化函数
<button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button>
<button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button>
goBack()、goForward()
-
this.prosp.history.goBack()
-
this.prosp.history.goForward()
-
this.prosp.history.go() :是前面2个API的合集,传递一个参数就是前进或者倒退的步数。
-
函数书写
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
back = ()=>{
this.props.history.goBack()
}
forward = ()=>{
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(-2)
}
withRouter的使用(是一个函数)
加工组件 withRoute
- 引入withRoute
import {withRouter} from 'react-router-dom'
- 暴露组件
export default withRouter(Header)
// withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
// withRouter的返回值是一个新组件
BrowserRouter与HashRouter的区别
BrowserRouter与HashRouter的区别
1.底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。(不会给服务器发送请求,会形成历史记录)
2.path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
(1).BrowserRouter没有任何影响,因为state保存在history对象中。
(2).HashRouter刷新后会导致路由state参数的丢失!!!
4.备注:HashRouter可以用于解决一些路径错误相关的问题。