哪些场景下,父组件和子组件会重新渲染?
1.在同一组件或父组件中调用 setState 时。
2.从父级收到的“props”的值发生变化。
3.调用组件中的 forceUpdate。
1. 使用纯组件
如果 React 组件为相同的状态和 props 渲染相同的输出,则可以将其视为纯组件。
对于像 this 的类组件来说,React 提供了 PureComponent 基类。扩展 React.PureComponent 类的类组件被视为纯组件。
它与普通组件是一样的,只是 PureComponents 负责 shouldComponentUpdate——它对状态和 props 数据进行浅层比较(shallow comparison)。
如果先前的状态和 props 数据与下一个 props 或状态相同,则组件不会重新渲染。
import React from 'react';
export default class ApplicationComponent extends React.Component {
constructor() {
super();
this.state = {
name: "Mayank"
}
}
updateState = () => {
setInterval(() => {
this.setState({
name: "Mayank"
})
}, 1000)
}
componentDidMount() {
this.updateState();
}
render() {
console.log("Render Called Again")
return (
<div>
<RegularChildComponent name={this.state.name} />
<PureChildComponent name={this.state.name} />
</div>
)
}
}
class RegularChildComponent extends React.Component {
render() {
console.log("Regular Component Rendered..");
return <div>{this.props.name}</div>;
}
}
class PureChildComponent extends React.PureComponent {
// Pure Components are the components that do not re-render if the State data or props data is still the same
render() {
console.log("Pure Component Rendered..")
return <div>{this.props.name}</div>;
}
}
2. 使用 React.memo 进行组件记忆
React.memo 是一个高阶组件。与上面说的PureComponent很像,只是他是用在函数式组件上的,它允许你自定义比较逻辑,用户可以用自定义逻辑深度对比(deep comparison)对象。如果比较函数返回 false 则重新渲染组件,否则就不会重新渲染。如下。
// The following function takes "user" Object as input parameter in props
function CustomisedComponen(props) {
return (
<div>
<b>User name: {props.user.name}</b>
<b>User age: {props.user.age}</b>
<b>User designation: {props.user.designation}</b>
</div>
)
}
function userComparator(previosProps, nextProps) {
if(previosProps.user.name == nextProps.user.name ||
previosProps.user.age == nextProps.user.age ||
previosProps.user.designation == nextProps.user.designation) {
return false
} else {
return true;
}
}
var memoComponent = React.memo(CustomisedComponent, userComparator);
3. 使用shouldComponentUpdate生命周期事件
这个函数将 nextState 和 nextProps 作为输入,并可将其与当前 props 和状态做对比,以决定是否需要重新渲染。
import React from "react";
export default class ShouldComponentUpdateUsage extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "Mayank";
age: 30,
designation: "Architect";
}
}
componentDidMount() {
setTimeout(() => {
this.setState({
designation: "Senior Architect"
});
}
}
shouldComponentUpdate(nextProps, nextState) {
if(nextState.age != this.state.age || netState.name = this.state.name) {
return true;
}
return false;
}
render() {
return (
<div>
<b>User Name:</b> {this.state.name}
<b>User Age:</b> {this.state.age}
</div>
)
}
}
4. 懒加载组件
使用 Suspense 和 lazy。
import React, { lazy, Suspense } from "react";
export default class CallingLazyComponents extends React.Component {
render() {
var ComponentToLazyLoad = null;
if(this.props.name == "Mayank") {
ComponentToLazyLoad = lazy(() => import("./mayankComponent"));
} else if(this.props.name == "Anshul") {
ComponentToLazyLoad = lazy(() => import("./anshulComponent"));
}
return (
<div>
<h1>This is the Base User: {this.state.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<ComponentToLazyLoad />
</Suspense>
</div>
)
}
}
假设有两个组件 WelcomeComponent 或 GuestComponents,我们根据用户是否登录而渲染其中一个。
我们可以根据具体的条件延迟组件加载,无需一开始就加载两个组件。
import React, { lazy, Suspense } from "react";
export default class UserSalutation extends React.Component {
render() {
if(this.props.username !== "") {
const WelcomeComponent = lazy(() => import("./welcomeComponent"));
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<WelcomeComponent />
</Suspense>
</div>
)
} else {
const GuestComponent = lazy(() => import("./guestComponent"));
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<GuestComponent />
</Suspense>
</div>
)
}
}
}
5. 使用 React Fragments 避免额外标记
6. 不要使用内联函数定义
<input type="button" onClick={(e) => { this.setState({inputValue: e.target.value}) }} value="Click For Inline Function" />
如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例。
当 React 进行虚拟 DOM diffing 时,它每次都会找到一个新的函数实例;因此在渲染阶段它会会绑定新函数并将旧实例扔给垃圾回收。
因此直接绑定内联函数就需要额外做垃圾回收和绑定到 DOM 的新函数的工作。
7. 避免componentWillMount()中的异步请求
因为请求是异步的,首次render时数据也是空的,而且该钩子函数中无法获取页面dom
注意:React 16.3 不推荐使用 componentWillMount。如果你使用的是最新版本的 React,请避免使用这个生命周期事件。
8. 在 Constructor 的早期绑定函数
当我们在 React 中创建函数时,我们需要使用 bind 关键字将函数绑定到当前上下文。
绑定可以在构造函数中完成,也可以在我们将函数绑定到 DOM 元素的位置上完成。
两者之间似乎没有太大差异,但性能表现是不一样的。
import React from "react";
export default class DelayedBinding extends React.Component {
constructor() {
this.state = {
name: "Mayank"
}
}
handleButtonClick() {
alert("Button Clicked: " + this.state.name)
}
render() {
return (
<>
<input type="button" value="Click" onClick={this.handleButtonClick.bind(this)} />
</>
)
}
}
在上面的代码中,我们在 render 函数的绑定期间将函数绑定到按钮上。
上面代码的问题在于,每次调用 render 函数时都会创建并使用绑定到当前上下文的新函数,但在每次渲染时使用已存在的函数效率更高。优化方案如下:
......
constructor() {
this.state = {
name: "Mayank"
}
this.handleButtonClick = this.handleButtonClick.bind(this)
}
这将减少将函数绑定到当前上下文的开销,无需在每次渲染时重新创建函数,从而提高应用的性能。
9. 箭头函数与构造函数中的绑定
处理类时的标准做法就是使用箭头函数。使用箭头函数时会保留执行的上下文。
箭头函数好处多多,但也有缺点。
当我们添加箭头函数时,该函数被添加为对象实例,而不是类的原型属性。这意味着如果我们多次复用组件,那么在组件外创建的每个对象中都会有这些函数的多个实例。
每个组件都会有这些函数的一份实例,影响了可复用性。此外因为它是对象属性而不是原型属性,所以这些函数在继承链中不可用。
因此箭头函数确实有其缺点。实现这些函数的最佳方法是在构造函数中绑定函数,如上所述。
10. 避免使用内联样式属性
使用内联样式时浏览器需要花费更多时间来处理脚本和渲染,因为它必须映射传递给实际 CSS 属性的所有样式规则。
import React from "react";
export default class InlineStyledComponents extends React.Component {
render() {
return (
<b style={{"backgroundColor": "blue"}}>Welcome to Sample Page</b>
)
}
}
在上面创建的组件中,我们将内联样式附加到组件。添加的内联样式是 JavaScript 对象而不是样式标记。
样式 backgroundColor 需要转换为等效的 CSS 样式属性,然后才应用样式。这样就需要额外的脚本处理和 JS 执行工作。
更好的办法是将 CSS 文件导入组件。
11. 使用唯一键迭代
如果开发人员没有为元素提供键,则它将 index 作为默认键。在下面的代码中我们默认不添加任何键,因此 index 将用作列表的默认键。
使用 index 作为键就不会出现标识不唯一的问题了,因为 index 只会标识所渲染的组件。
我们可以在以下场景中使用 index 作为键:
1.列表项是静态的,项目不随时间变化。
2.Items 没有唯一 ID。
3.List 永远不会重新排序或过滤。
4.不会从顶部或中间添加或删除项目。
12.事件节流和防抖
13.使用 CDN
使用 CDN 有以下好处:
*. 不同的域名。浏览器限制了单个域名的并发连接数量,具体取决于浏览器设置。假设允许的并发连接数为 10。如果要从单个域名中检索 11 个资源,那么同时完成的只有 10 个,还有 1 个需要再等一会儿。CDN 托管在不同的域名 / 服务器上。因此资源文件可以分布在不同的域名中,提升了并发能力。
*. 文件可能已被缓存。有很多网站使用这些 CDN,因此你尝试访问的资源很可能已在浏览器中缓存好了。这时应用将访问文件的已缓存版本,从而减少脚本和文件执行的网络调用和延迟,提升应用性能。
*. 高容量基础设施。这些 CDN 由大公司托管,因此可用的基础设施非常庞大。他们的数据中心遍布全球。向 CDN 发出请求时,它们将通过最近的数据中心提供服务,从而减少延迟。这些公司会对服务器做负载平衡,以确保请求到达最近的服务器并减少网络延迟,提升应用性能。
14.使用 Web Workers 处理 CPU 密集任务
JavaScript 是一个单线程应用,但在渲染网页时需要执行多个任务:
处理 UI 交互、处理响应数据、操纵 DOM 元素、启用动画等。所有这些任务都由单个线程处理。
可以使用 worker 来分担主线程的负载。
Worker 线程在后台运行,可以在不中断主线程的情况下执行多个脚本和 JavaScript 任务。
每当需要执行长时间的 CPU 密集任务时,可以使用 worker 在单独的线程上执行这些逻辑块。
它们在隔离环境中执行,并且使用进程间线程通信与主线程交互。主线程就可以腾出手来处理渲染和 DOM 操作任务。