一、是什么
React
凭借virtual DOM
和diff
算法拥有高效的性能,但是某些情况下,性能明显可以进一步提高
在前面文章中,我们了解到类组件通过调用setState
方法, 就会导致render
,父组件一旦发生render
渲染,子组件一定也会执行render
渲染
当我们想要更新一个子组件的时候,如下图绿色部分:
理想状态只调用该路径下的组件render
:
但是react
的默认做法是调用所有组件的render
,再对生成的虚拟DOM
进行对比(黄色部分),如不变则不进行更新
从上图可见,黄色部分diff
算法对比是明显的性能浪费的情况
#二、如何做
在React中如何避免不必要的render (opens new window)中,我们了解到如何避免不必要的render
来应付上面的问题,主要手段是通过shouldComponentUpdate
、PureComponent
、React.memo
,这三种形式这里就不再复述
除此之外, 常见性能优化常见的手段有如下:
-
避免使用内联函数
-
使用 React Fragments 避免额外标记
-
使用 Immutable
-
懒加载组件
-
事件绑定方式
-
服务端渲染
#避免使用内联函数
如果我们使用内联函数,则每次调用render
函数时都会创建一个新的函数实例,如下:
import React from "react";
export default class InlineFunctionComponent extends React.Component {
render() {
return (
<div>
<h1>Welcome Guest</h1>
<input type="button" onClick={(e) => { this.setState({inputValue: e.target.value}) }} value="Click For Inline Function" />
</div>
)
}
}
我们应该在组件内部创建一个函数,并将事件绑定到该函数本身。这样每次调用 render
时就不会创建单独的函数实例,如下:
import React from "react";
export default class InlineFunctionComponent extends React.Component {
setNewStateData = (event) => {
this.setState({
inputValue: e.target.value
})
}
render() {
return (
<div>
<h1>Welcome Guest</h1>
<input type="button" onClick={this.setNewStateData} value="Click For Inline Function" />
</div>
)
}
}
#使用 React Fragments 避免额外标记
用户创建新组件时,每个组件应具有单个父标签。父级不能有两个标签,所以顶部要有一个公共标签,所以我们经常在组件顶部添加额外标签div
这个额外标签除了充当父标签之外,并没有其他作用,这时候则可以使用fragement
其不会向组件引入任何额外标记,但它可以作为父级标签的作用,如下所示:
export default class NestedRoutingComponent extends React.Component {
render() {
return (
<>
<h1>This is the Header Component</h1>
<h2>Welcome To Demo Page</h2>
</>
)
}
}
#事件绑定方式
在事件绑定方式 (opens new window)中,我们了解到四种事假绑定的方式
从性能方面考虑,在render
方法中使用bind
和render
方法中使用箭头函数这两种形式在每次组件render
的时候都会生成新的方法实例,性能欠缺
而constructor
中bind
事件与定义阶段使用箭头函数绑定这两种形式只会生成一个方法实例,性能方面会有所改善
#使用 Immutable
在理解Immutable中 (opens new window),我们了解到使用 Immutable
可以给 React
应用带来性能的优化,主要体现在减少渲染的次数
在做react
性能优化的时候,为了避免重复渲染,我们会在shouldComponentUpdate()
中做对比,当返回true
执行render
方法
Immutable
通过is
方法则可以完成对比,而无需像一样通过深度比较的方式比较
#懒加载组件
从工程方面考虑,webpack
存在代码拆分能力,可以为应用创建多个包,并在运行时动态加载,减少初始包的大小
而在react
中使用到了Suspense
和 lazy
组件实现代码拆分功能,基本使用如下:
const johanComponent = React.lazy(() => import(/* webpackChunkName: "johanComponent" */ './myAwesome.component'));
export const johanAsyncComponent = props => (
<React.Suspense fallback={<Spinner />}>
<johanComponent {...props} />
</React.Suspense>
);
#服务端渲染
采用服务端渲染端方式,可以使用户更快的看到渲染完成的页面
服务端渲染,需要起一个node
服务,可以使用express
、koa
等,调用react
的renderToString
方法,将根组件渲染成字符串,再输出到响应中
例如:
import { renderToString } from "react-dom/server";
import MyPage from "./MyPage";
app.get("/", (req, res) => {
res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
res.write("<div id='content'>");
res.write(renderToString(<MyPage/>));
res.write("</div></body></html>");
res.end();
});
客户端使用render方法来生成HTML
import ReactDOM from 'react-dom';
import MyPage from "./MyPage";
ReactDOM.render(<MyPage />, document.getElementById('app'));
#其他
除此之外,还存在的优化手段有组件拆分、合理使用hooks
等性能优化手段...
#三、详细
在React应用中,可以采取以下几种方式来优化性能:
-
使用轻量级组件:避免创建过于复杂或庞大的组件,将组件拆分为更小、更简单的部分。
-
使用shouldComponentUpdate或React.memo:通过实现shouldComponentUpdate方法或使用React.memo高阶组件,可以优化组件的渲染过程,避免不必要的重新渲染。
-
使用key属性:在使用列表渲染时,为每个列表项提供唯一的key属性,这可以帮助React更好地识别哪些元素需要更新,提高渲染性能。
-
使用虚拟化技术:对于大型列表或表格,可以使用虚拟化库(如react-virtualized或react-window)来只渲染可见区域内的元素,减少DOM操作,提高性能。
-
优化事件处理:避免在render方法中绑定新的事件处理函数,可以将事件处理函数提取到组件的构造函数中或使用箭头函数绑定。
-
使用React DevTools进行性能分析:React DevTools提供了一系列有用的工具,可以帮助你分析组件的渲染性能,并找出潜在的性能问题。
-
使用代码分割和懒加载:通过使用React的代码分割功能和React.lazy()函数,可以按需加载组件,减少初始加载时的资源压力。
-
开启生产模式:在生产环境中使用React的生产模式,可以启用额外的性能优化,如JSX的编译优化和代码压缩等。
-
使用Immutable数据结构:使用Immutable.js或其他类似库,可以避免不必要的数据更新,提高性能。
-
使用Memoization技术:通过使用Memoization技术,可以缓存计算结果,避免重复计算,提高性能。
需要注意的是,性能优化并非一成不变的规则,具体的优化策略还需根据应用的特点和实际情况来确定。
#四、总结
通过上面初步学习,我们了解到react
常见的性能优化可以分成三个层面:
- 代码层面
- 工程层面
- 框架机制层面
通过这三个层面的优化结合,能够使基于react
项目的性能更上一层楼