React 组件性能优化最佳实践
React 组件性能优化最佳实践
React组件性能优化的核心是降低渲染真实DOM节点的频率,降低Virtual DOM比较的频率。如果子组件没有改变数据,则子组件不会被渲染。
组件卸载前清理
下面的代码会在组件挂载时创建一个间隔,并在组件销毁后清空定时器,每1秒触发一次渲染 计数+1
,如果组件销毁后定时器没有清零,会继续消耗资源
从“反应”导入反应,{ useState,useEffect }
从“react-dom”导入 ReactDOM
常量应用 = () => {
让 [index, setIndex] = useState(0)
使用效果(()=> {
让计时器 = setInterval( () => {
设置索引(上一个 => 上一个 + 1)
安慰。 log('计时器正在运行...')
}, 1000)
return () => clearInterval(timer)
}, [])
返回 (
< button onClick = {() => ReactDOM.unmountComponentAtNode(document.getElementById("root"))}> {index}</ button >
)
}
导出默认应用
复制代码
每次数据更新都会触发组件重新渲染,这里的优化是:组件销毁清理定时器
类组件使用纯组件 纯组件
什么是纯成分
纯组件将浅层比较组件输入数据。如果当前输入数据与上次输入数据相同,则不会重新渲染组件
什么是浅比较
比较内存中引用数据类型的引用地址是否相同,比较基本数据类型的值是否相同。
为什么不直接进行diff操作,而是先进行浅比较,浅比较没有性能消耗吗?
浅比较比进行差异比较消耗更少的性能。 diff 操作会重新遍历整个 virtualDOM 树,而浅层比较只对当前组件的 state 和 props 进行操作。
从“反应”导入反应
导出默认类 App 扩展 React.Component {
构造函数(){
极好的()
这个。 state = { name: "张三"}
}
更新名称(){
setInterval( () => this.setState({ name: "张三"}), 1000)
}
组件DidMount() {
这个。更新名称()
}
使成为() {
返回 (
<div>
< 常规组件名称 = {this.state.name} />
< PureChildComponent name = {this.state.name} />
</ div >
)
}
}
类 RegularComponent 扩展 React.Component {
使成为() {
安慰。日志(“常规组件”)
返回 <div> {this.props.name}</ div >
}
}
类 PureChildComponent 扩展 React.PureComponent {
使成为() {
安慰。日志(“纯子组件”)
返回 <div> {this.props.name}</ div >
}
}
复制代码
组件挂载后,每1秒会设置一个定时器 姓名
,我们可以看到 常规组件
一直渲染,即使数据没有改变。 纯子组件
只有一个渲染,所以使用纯组件将 道具`` 状态
为了比较,相同的数据不会被重新渲染。
应该组件更新
纯组件只能进行浅比较。要执行深度比较,请使用 shouldComponentUpdate,它用于编写自定义比较逻辑。
返回 true 以重新渲染组件, false 以防止重新渲染。
函数的第一个参数是nextProps,第二个参数是nextState。
从“反应”导入反应
导出默认类 App 扩展 React.Component {
构造函数(){
极好的()
这个。 state = { 姓名:“张三”,年龄:20,工作:“服务员”}
}
组件DidMount() {
setTimeout( () => this.setState({ job: "chef" }), 1000)
}
shouldComponentUpdate(nextProps,nextState){
if ( this.state.name !== nextState.name || this.state.age !== nextState.age) {
返回真
}
返回假
}
使成为() {
安慰。日志(“渲染”)
让{姓名,年龄} =这个。状态
返回 < div > {姓名} {年龄}</ div >
}
}
复制代码
即使继承 零件
组件定时器不断修改数据,不会触发重新渲染
使用纯功能组件 反应备忘录
优化性能
备忘录的基本使用
把一个功能组件变成一个纯组件,对当前的 props 和最后一个 props 进行浅显的比较,如果相同则防止组件重新渲染。
从“react”导入反应,{备忘录,useEffect,useState}
函数显示名称({名称}){
安慰。 log("showName 渲染...")
返回 < div > {名称}</ div >
}
const ShowNameMemo = memo(ShowName)
函数应用程序(){
const [索引,setIndex] = useState(0)
const [name] = useState("张三")
使用效果(()=> {
设置间隔(()=> {
设置索引(上一个 => 上一个 + 1)
}, 1000)
}, [])
返回 (
< div > {index} < ShowNameMemo name = {name} />
</ div >
)
}
导出默认应用
复制代码
memo 传递比较逻辑(使用 memo 方法自定义比较逻辑以执行深度比较。)
从“react”导入反应,{备忘录,useEffect,useState};
函数显示名称({人}){
安慰。 log("showName 渲染...");
返回 (
< div > {person.name}丨{person.job} </ div >
);
}
功能比较人(prevProps,nextProps){
如果 (
prev道具。人。名称!== nextProps。人。姓名 ||
prev道具。人。年龄!== nextProps。人。年龄
) {
返回假
}
返回真
}
const ShowNameMemo = memo(ShowName, comparePerson);
函数应用程序(){
const [person, setPerson] = useState({ name: "张三", job: "developer" });
使用效果(()=> {
设置间隔(()=> {
setPerson( ( data ) => ({ ...data, name: "haoxuan" }));
}, 1000);
}, []);
返回 (
<div>
< ShowNameMemo 人 = {人} />
</ div >
);
}
导出默认应用程序;
复制代码
组件的延迟加载
使用组件延迟加载可以减少包文件大小并加快组件渲染。
路由组件的延迟加载
从“反应”导入反应,{懒惰,悬念}
从“react-router-dom”导入 { BrowserRouter, Link, Route, Switch }
const Home = lazy( () => import( /* webpackChunkName: "Home" */ "./Home"))
const List = lazy( () => import( /* webpackChunkName: "List" */ "./List"))
函数应用程序(){
返回 (
<浏览器路由器>
< 链接到 = "/" > 主页</ Link >
< 链接到 = "/list" > 列表</ Link >
< 开关 >
<Suspense fallback = { <div> 加载中</ div >}> < 路由路径 = "/" 组件 = {Home} 精确 />
< 路由路径 = "/list" 组件 = {List} />
</ Suspense >
</ Switch >
</ BrowserRouter >
)
}
导出默认应用
复制代码
根据条件延迟加载组件(适用于不频繁随条件切换的组件)
从“反应”导入反应,{懒惰,悬念}
函数应用程序(){
让 LazyComponent = null
如果真实) {
LazyComponent = lazy( () => import( /* webpackChunkName: "Home" */ "./Home"))
} 别的 {
LazyComponent = lazy( () => import( /* webpackChunkName: "List" */ "./List"))
}
返回 (
<Suspense fallback = { <div> 加载中</ div >}> < 懒惰组件 />
</ Suspense >
)
}
导出默认应用
复制代码
使用 Fragment 避免额外的标记
为了满足这个条件,我们通常会在最外层添加一个div,但这会添加一个无意义的标记。如果每个组件都有这样一个无意义的标记,浏览器渲染引擎的负担将会加重。
从“反应”导入{片段}
函数应用程序(){
返回 (
<片段>
<div> 消息一个</ div >
<div> 消息 b</ div >
</ Fragment >
)
}
复制代码 函数应用程序(){
返回 (
<>
<div> 消息一个</ div >
<div> 消息 b</ div >
</>
)
}
复制代码
不要使用内联函数定义
使用内联函数后,render方法每次运行都会创建一个新的函数实例,导致React在比较Virtual DOM时对新旧函数的比较不同,导致React总是绑定一个新的函数实例到元素上,旧的函数实例交给垃圾收集器处理。
错误示例:
从“反应”导入反应
导出默认类 App 扩展 React.Component {
构造函数(){
极好的()
这个。状态 = {
输入值:“”
}
}
使成为() {
返回 (
<输入
值 = {this.state.inputValue}
onChange = {e => this.setState({ inputValue: e.target.value })} />
)
}
}
复制代码
正确的做法是在组件中单独定义函数,并将函数绑定到事件:
从“反应”导入反应
导出默认类 App 扩展 React.Component {
构造函数(){
极好的()
这个。状态 = {
输入值:“”
}
}
设置输入值 = e => {
这个。 setState({ inputValue: e.target.value })
}
使成为() {
返回 (
< 输入值 = {this.state.inputValue} onChange = {this.setInputValue} />
)
}
}
复制代码
在构造函数中执行此绑定
如果在类组件中使用 fn() {} 定义函数,函数 this 默认指向 undefined。也就是说,函数内部的this点需要修正。
函数的this可以在构造函数中修正,也可以内联修正,两者看起来差别不大,但是对性能的影响是不一样的
导出默认类 App 扩展 React.Component {
构造函数(){
极好的()
//方法一
// 构造函数只执行一次,所以this指向的正确代码只执行一次。
这个。手柄点击 = 这个。手柄点击。绑定(这个)
}
手柄点击(){
安慰。日志(这个)
}
使成为() {
//方法二
// 问题:每次执行render方法,都会调用bind方法生成一个新的函数实例。
return < button onClick = {this.handleClick.bind(this)} > 按钮</ button >
}
}
复制代码
类组件中的箭头函数
在类组件中使用箭头函数不存在 this 指向问题,因为箭头函数本身并没有绑定 this。
导出默认类 App 扩展 React.Component {
handleClick = () => 控制台。日志(这个)
使成为() {
return < button onClick = {this.handleClick} > 按钮</ button >
}
}
复制代码
箭头函数在 this-pointing 问题中具有优势,但它们也有缺点。
使用箭头函数时,该函数被添加为类的实例对象属性,而不是原型对象属性。如果组件被多次复用,那么每个组件实例对象中都会有一个相同的函数实例,降低了函数实例资源的复用性,就是对资源的浪费。
综上所述,修正函数内部this指针的最佳实践还是使用构造函数中的bind方法进行绑定
优化条件渲染
频繁安装和卸载组件是一项性能密集型操作。为了保证应用的性能,应减少元件的装卸次数。
在 React 中,我们经常有条件地渲染不同的组件。条件渲染是必须做的优化。
函数应用程序(){
如果真实) {
返回 (
<>
< 管理头 />
< 标题 />
<内容/>
</>
)
} 别的 {
返回 (
<>
< 标题 />
<内容/>
</>
)
}
}
复制代码
上面代码中,当渲染条件发生变化时,React内部对比Virtual DOM,发现第一个组件是AdminHeader,现在第一个组件是Header,第二个组件是Header,现在第一个组件是Header。第二个组件是内容。当组件发生变化时,React 会卸载 AdminHeader、Header 和 Content,并重新挂载 Header 和 Content。这种安装和卸载是不必要的。
函数应用程序(){
返回 (
<> {true && < AdminHeader /> } < 标头 />
<内容/>
</>
)
}
复制代码
避免使用内联样式属性
使用内联样式给元素添加样式时,内联样式会被编译成 JavaScript 代码,样式规则会通过 JavaScript 代码映射到元素上,浏览器会花费更多时间执行脚本和渲染 UI,从而增加组件的渲染时间。
函数应用程序(){
return < div style = {{ backgroundColor: "skyblue " }}> 应用正常</ div >
}
复制代码
避免重复无限渲染
当应用程序状态发生变化时,React 会调用 render 方法。如果在render方法中继续改变应用状态,会递归调用render方法,应用会报错。
导出默认类 App 扩展 React.Component {
构造函数(){
极好的()
这个。 state = { name: "张三"}
}
使成为() {
这个。 setState({ name: "李四"})
返回 <div> {this.state.name}</ div >
}
}
复制代码
与其他生命周期函数不同,render 方法应被视为纯函数。这意味着,在 render 方法中,不要做调用 setState 方法之类的事情,不要使用其他方式查询和更改原生 DOM 元素,以及对应用程序的 Any 操作进行其他更改。 render 方法的执行是基于状态变化的,它使组件的行为和渲染保持一致。
避免数据结构突变
组件中 props 和 state 的数据结构要一致,数据结构的突变会导致输出不一致。
从“反应”导入反应,{组件}
导出默认类 App 扩展组件 {
构造函数(){
极好的()
这个。状态 = {
员工: {
name: "Zhang San",
年龄:20
}
}
}
使成为() {
常量 { 姓名,年龄 } = 这个。状态。员工
返回 (
< div > {name} {age} < 按钮
onClick = {() => this.setState({ ...this.state, employee: { ...this.state.employee, age: 30 } }) } > 更改年龄</ button >
</ div >
)
}
}
复制代码
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议。转载请附上原文出处链接和本声明。
这篇文章的链接: https://homecpp.art/0913/8285/2313
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明