React 性能优化
setState()
作用: 修改state 更新组件UI
更新机制:
父组件更新时 父组件包含的子组件都会进行更新 子组件包含的组件也会进行更新 只会更新当前父组件包含的当前子组件树
减轻 state:
在state中只存放和组件渲染相关的 需要变动的页面数据
例: 计时器、id等这类数据可以存放在this中
// this可以多个方法直接进行访问 也可以实现数据共享
// 存放时 this.xxx = 当前的变量
this.id = setInterval(() => {}, 200)
// 使用时
clearInterval(this.id)
减少不必要的渲染:
根据更新机制的概念 当子组件没有被改变时也会被更新 这种情况被称为不必要的更新 如何解决这种不必要的更新
解决方法: shouldComponentUpdate(nextProps, nextState)
// 写在类组件中
// render之前触发 返回值是布尔值
// false 不重新渲染
// true 重新渲染
// 使用nextState时
shouldComponentUpdate(nextProps,nextState) {
if(nextState.xx == this.state.xx){
return false
} // 根据判断是否渲染 或三元表达式
return true
}
render () {...}
// 使用nextProps时
shouldComponentUpdate(nextProps,nextState) {
if(nextProps.xx == this.state.xx){
return false
} // 根据判断是否渲染 或三元表达式
return true
}
render () {...}
// nextProps 当前最新的props通信传递的值 在需要通过传递的值来作为是否渲染组件的条件时 使用nextProps
// nextState 当前最新的state状态值 在需要通过组件自身内部的state状态值来作为是否渲染组件条件时 使用nextState
// this.state 获取更新前的state状态值
纯组件 PureComponent
作用:内部实现了shouldComponentUpdate钩子函数的功能 不需要手动比较
原理: 内部已经通过对比前后props和前后state的值 来决定是否渲染组件
缺点:只是将props和state的值进行了对比 是浅层对比 一旦对比的是复杂数据类型(引用类型) 会出现问题
// 将class继承中的React.Component替换为React.PureComponent
class NumberBox extends React.PureComponent {
// 此处不需要shouldComponentUpdate钩子函数了 组件内部自动判断
render() {
return {
<div>要被渲染的内容或组件</div>
}
}
}
纯组件弊端
纯组件内部比对是浅层比对 在值对比时没有什么问题 但是在引用对比时 会出现问题 因为对比只是对比了引用的对象(地址) 是否相同 在直接修改了原始对象时 因为前后的对象并没有改变 这种情况下不会重新渲染
class App extends React.PureComponent {
state = {
obj: {
number: 0
}
}
// 事件处理程序
handleClick = () => {
// 修改state需要在setState中
// 错误示范:直接修改state对象数值中的数值
const newObj = this.state.obj
newObj.number = Math.floor(Math.random() * 3)
}
}
// 解决方法思路 修改原始对象因为修改的前后都是同一个对象 所以不会触发渲染 但是重新原始对象的基础上创建一个对象 修改时因为创建的对象和原始对象并不是同一个地址指向 所以会触发渲染
class App extends React.PureComponent {
state = {
obj: {
number: 0
}
}
// 事件处理程序
handleClick = () => {
// 修改state需要在setState中
// 错误示范:直接修改state对象数值中的数值
const newObj = this.state.obj
newObj.number = Math.floor(Math.random() * 3)
// 正确做法
const newObj = {...state.obj, number: 2}
setState({ obj: newObj })
}
}
// 总结: 在使用引用类型数据时 重新创建一个新的数据 不在原始数据上直接修改
// 注意点: 在使用数组方法时不要使用 push/unshift 直接修改原数组的方法 而应该使用 concat/slice 返回新数组的方法
// 扩展运算符 对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中
this.setStatae({
list: [...this.state.list, {新数据}]
})
注:计算属性也是浅层对比 引用类型: 也称之为复杂数据类型
虚拟DOM和diff算法 √
React是如何做到部分更新的: 通过diff算法找前后两次虚拟dom的不同点 再根据不同点进行更新
作用:实现部分更新
虚拟DOM:
虚拟DOM不是真正的DOM元素 是通过state和JSX去描述html结构的一段代码 在浏览器是展示的htmlDOM 结构 在其他平台又是展示的其他结构 是多变的 所以它可以跨平台使用 就是所谓的编写一次多处使用的理念
diff算法:
作用:寻找到虚拟DOM结构的上一次和这一次不同的地方 只将需要变化的内容进行更新
流程:在初次渲染时 会渲染一个默认的虚拟DOM树 当数据发生变化后 会重新渲染一个虚拟DOM树和上一次的DOM树进行比较 根据比较后得出的变化内容 只将变化的内容和上一次的虚拟DOM进行结合
❤ React 路由
路由是为了实现SPA单页面应用程序能够渲染对应组件的DOM结构产生的 因为SPA单页面 只有一个HTML页面 要展示不同的页面 需要根据一个html页面映射对应的路径和对应的组件来实现功能渲染
作用: 根据配置的路径来跳转到对应的组件 并展示组件渲染的DOM结构
基本使用:
-
安装:
npm i react-router-dom
-
导入核心组件:
import {BrowserRouter as Router, Route, Link} from 'react-router-dom'
-
BrowserRouter as Router
是将名称给更改为了Router 包裹的标签根据更改的名称来决定 -
BrowserRouter:
路由管理器 管理路由的匹配 -
Link:
入口:设置跳转的路径 -
Route:
出口:设置路由的规则 精确匹配设置属性exact
// 使用Router包裹组件中的整个应用 并且只需要使用一次
// Route 组件写在哪 内容就展示在哪 类似于当年的路由占位符
class App extends React.Component {
<Router>
<div>
{/* 入口 = a标签 展示在表面的一个切换组件开关 */}
<Link to="/url"></Link>
{/* 出口 = 当启动了切换开关 需要切换到对应组件的规则 */}
<Route path="/url", component={被展示的组件名称} />
</div>
</Router>
}
// 入口标签的to属性又被称为pathname
*路由的执行过程:
当url地址栏中的路径改变时 路由会侦听到变动 会更具改变后的url地址 循环所有设置的路由规则 找到对应的路由规则将对应规则的组件的内容渲染出来
HashRouter 哈希路由
是和BrowserRouter 路由一样功能的另一个路由 展示方法有所不同
Hash
路由:localhost:3000/#/url
展示有#链接
* Browser
路由:localhost:3000/url
展示没有 推荐
location.pathname
可以拿到浏览器pathname
路径地址/url
编程式导航 history
作用: 当用户需要通过点击某个按钮 跳转到对应组件页面
本质:通过js代码来实现页面的跳转
history :是React路由标签提供的 只有在被路由标签包裹的组件才有这个属性 用于获取浏览器历史记录
// 在需要点击跳转页面的类组件中
class Login extends React.Component {
// 去首页事件处理程序
route = () => {
// push('/url'):去往某一个页面
this.props.history.push('/url')
}
// 返回上一层事件处理程序
getgo = () => {
// go(-1):返回上一层页面
this.props.history.go(-1)
}
render() {
return (
<div>
<button onClick={this.route}>点击去首页</button>
<button onClick={this.getgo}>返回上一层</button>
</div>
)
}
}
// 为什么是props拿到的history呢 因为当前的Login 是通过另一个组件的路由渲染出来的 在渲染的时候 路由会往组件中存放一些方法 history就是其中之一
*默认路由
作用:打开页面显示的默认组件页面
class App extends React.Component {
<Router>
<div>
{/*
出口 = 当启动了切换开关 需要切换到对应组件的规则
只需要将path改为 ' / '
*/}
<Route path="/" component={默认显示组件} />
</div>
</Router>
}
匹配模式
因为路由的匹配模式是默认的模糊匹配 默认路由是以/开头 然而所有的路由路径开头都以 / 开头 所以默认路由展示的组件会一直展示
模糊匹配
是默认的路由匹配模式 模糊匹配只会匹配开头 当pathname开头和path开头相同就能匹配成功
-
pathname: Link to的开头
-
path:Route path的开头
为什么模糊匹配会是默认匹配模式:为了方便函数封装 不用再次进行导入
造成的问题: 因为默认路径是/ 但是所以路径都是以/ 开头 通过模糊匹配所有的路径都会被匹配成默认路由的页面
精确匹配
是模糊匹配造成问题的解决方法 当path和pathname完全匹配才会展示对应的组件
// 给Route组件添加 exact属性 将模式转换为精确匹配 精确匹配模式并不会只匹配开头 会进行全部对比
class App extends React.Component {
<Router>
<div>
{/*
出口 = 当启动了切换开关 需要切换到对应组件的规则
只需要将path改为 ' / '
*/}
<Route exact path="/" component={默认显示组件} />
</div>
</Router>
}