哪些場(chǎng)景下,父組件和子組件會(huì)重新渲染?
1.在同一組件或父組件中調(diào)用 setState 時(shí)。
2.從父級(jí)收到的“props”的值發(fā)生變化。
3.調(diào)用組件中的 forceUpdate。
1. 使用純組件
如果 React 組件為相同的狀態(tài)和 props 渲染相同的輸出,則可以將其視為純組件。
對(duì)于像 this 的類(lèi)組件來(lái)說(shuō),React 提供了 PureComponent 基類(lèi)。擴(kuò)展 React.PureComponent 類(lèi)的類(lèi)組件被視為純組件。
它與普通組件是一樣的,只是 PureComponents 負(fù)責(zé) shouldComponentUpdate——它對(duì)狀態(tài)和 props 數(shù)據(jù)進(jìn)行淺層比較(shallow comparison)。
如果先前的狀態(tài)和 props 數(shù)據(jù)與下一個(gè) props 或狀態(tài)相同,則組件不會(huì)重新渲染。
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 進(jìn)行組件記憶
React.memo 是一個(gè)高階組件。與上面說(shuō)的PureComponent很像,只是他是用在函數(shù)式組件上的,它允許你自定義比較邏輯,用戶(hù)可以用自定義邏輯深度對(duì)比(deep comparison)對(duì)象。如果比較函數(shù)返回 false 則重新渲染組件,否則就不會(huì)重新渲染。如下。
// 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生命周期事件
這個(gè)函數(shù)將 nextState 和 nextProps 作為輸入,并可將其與當(dāng)前 props 和狀態(tài)做對(duì)比,以決定是否需要重新渲染。
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>
)
}
}
假設(shè)有兩個(gè)組件 WelcomeComponent 或 GuestComponents,我們根據(jù)用戶(hù)是否登錄而渲染其中一個(gè)。
我們可以根據(jù)具體的條件延遲組件加載,無(wú)需一開(kāi)始就加載兩個(gè)組件。
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 避免額外標(biāo)記
6. 不要使用內(nèi)聯(lián)函數(shù)定義
<input type="button" onClick={(e) => { this.setState({inputValue: e.target.value}) }} value="Click For Inline Function" />
如果我們使用內(nèi)聯(lián)函數(shù),則每次調(diào)用“render”函數(shù)時(shí)都會(huì)創(chuàng)建一個(gè)新的函數(shù)實(shí)例。
當(dāng) React 進(jìn)行虛擬 DOM diffing 時(shí),它每次都會(huì)找到一個(gè)新的函數(shù)實(shí)例;因此在渲染階段它會(huì)會(huì)綁定新函數(shù)并將舊實(shí)例扔給垃圾回收。
因此直接綁定內(nèi)聯(lián)函數(shù)就需要額外做垃圾回收和綁定到 DOM 的新函數(shù)的工作。
7. 避免componentWillMount()中的異步請(qǐng)求
因?yàn)檎?qǐng)求是異步的,首次render時(shí)數(shù)據(jù)也是空的,而且該鉤子函數(shù)中無(wú)法獲取頁(yè)面dom
注意:React 16.3 不推薦使用 componentWillMount。如果你使用的是最新版本的 React,請(qǐng)避免使用這個(gè)生命周期事件。
8. 在 Constructor 的早期綁定函數(shù)
當(dāng)我們?cè)?React 中創(chuàng)建函數(shù)時(shí),我們需要使用 bind 關(guān)鍵字將函數(shù)綁定到當(dāng)前上下文。
綁定可以在構(gòu)造函數(shù)中完成,也可以在我們將函數(shù)綁定到 DOM 元素的位置上完成。
兩者之間似乎沒(méi)有太大差異,但性能表現(xiàn)是不一樣的。
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)} />
</>
)
}
}
在上面的代碼中,我們?cè)?render 函數(shù)的綁定期間將函數(shù)綁定到按鈕上。
上面代碼的問(wèn)題在于,每次調(diào)用 render 函數(shù)時(shí)都會(huì)創(chuàng)建并使用綁定到當(dāng)前上下文的新函數(shù),但在每次渲染時(shí)使用已存在的函數(shù)效率更高。優(yōu)化方案如下:
......
constructor() {
this.state = {
name: "Mayank"
}
this.handleButtonClick = this.handleButtonClick.bind(this)
}
這將減少將函數(shù)綁定到當(dāng)前上下文的開(kāi)銷(xiāo),無(wú)需在每次渲染時(shí)重新創(chuàng)建函數(shù),從而提高應(yīng)用的性能。
9. 箭頭函數(shù)與構(gòu)造函數(shù)中的綁定
處理類(lèi)時(shí)的標(biāo)準(zhǔn)做法就是使用箭頭函數(shù)。使用箭頭函數(shù)時(shí)會(huì)保留執(zhí)行的上下文。
箭頭函數(shù)好處多多,但也有缺點(diǎn)。
當(dāng)我們添加箭頭函數(shù)時(shí),該函數(shù)被添加為對(duì)象實(shí)例,而不是類(lèi)的原型屬性。這意味著如果我們多次復(fù)用組件,那么在組件外創(chuàng)建的每個(gè)對(duì)象中都會(huì)有這些函數(shù)的多個(gè)實(shí)例。
每個(gè)組件都會(huì)有這些函數(shù)的一份實(shí)例,影響了可復(fù)用性。此外因?yàn)樗菍?duì)象屬性而不是原型屬性,所以這些函數(shù)在繼承鏈中不可用。
因此箭頭函數(shù)確實(shí)有其缺點(diǎn)。實(shí)現(xiàn)這些函數(shù)的最佳方法是在構(gòu)造函數(shù)中綁定函數(shù),如上所述。
10. 避免使用內(nèi)聯(lián)樣式屬性
使用內(nèi)聯(lián)樣式時(shí)瀏覽器需要花費(fèi)更多時(shí)間來(lái)處理腳本和渲染,因?yàn)樗仨氂成鋫鬟f給實(shí)際 CSS 屬性的所有樣式規(guī)則。
import React from "react";
export default class InlineStyledComponents extends React.Component {
render() {
return (
<b style={{"backgroundColor": "blue"}}>Welcome to Sample Page</b>
)
}
}
在上面創(chuàng)建的組件中,我們將內(nèi)聯(lián)樣式附加到組件。添加的內(nèi)聯(lián)樣式是 JavaScript 對(duì)象而不是樣式標(biāo)記。
樣式 backgroundColor 需要轉(zhuǎn)換為等效的 CSS 樣式屬性,然后才應(yīng)用樣式。這樣就需要額外的腳本處理和 JS 執(zhí)行工作。
更好的辦法是將 CSS 文件導(dǎo)入組件。
11. 使用唯一鍵迭代
如果開(kāi)發(fā)人員沒(méi)有為元素提供鍵,則它將 index 作為默認(rèn)鍵。在下面的代碼中我們默認(rèn)不添加任何鍵,因此 index 將用作列表的默認(rèn)鍵。
使用 index 作為鍵就不會(huì)出現(xiàn)標(biāo)識(shí)不唯一的問(wèn)題了,因?yàn)?index 只會(huì)標(biāo)識(shí)所渲染的組件。
我們可以在以下場(chǎng)景中使用 index 作為鍵:
1.列表項(xiàng)是靜態(tài)的,項(xiàng)目不隨時(shí)間變化。
2.Items 沒(méi)有唯一 ID。
3.List 永遠(yuǎn)不會(huì)重新排序或過(guò)濾。
4.不會(huì)從頂部或中間添加或刪除項(xiàng)目。
12.事件節(jié)流和防抖
13.使用 CDN
使用 CDN 有以下好處:
*. 不同的域名。瀏覽器限制了單個(gè)域名的并發(fā)連接數(shù)量,具體取決于瀏覽器設(shè)置。假設(shè)允許的并發(fā)連接數(shù)為 10。如果要從單個(gè)域名中檢索 11 個(gè)資源,那么同時(shí)完成的只有 10 個(gè),還有 1 個(gè)需要再等一會(huì)兒。CDN 托管在不同的域名 / 服務(wù)器上。因此資源文件可以分布在不同的域名中,提升了并發(fā)能力。
*. 文件可能已被緩存。有很多網(wǎng)站使用這些 CDN,因此你嘗試訪(fǎng)問(wèn)的資源很可能已在瀏覽器中緩存好了。這時(shí)應(yīng)用將訪(fǎng)問(wèn)文件的已緩存版本,從而減少腳本和文件執(zhí)行的網(wǎng)絡(luò)調(diào)用和延遲,提升應(yīng)用性能。
*. 高容量基礎(chǔ)設(shè)施。這些 CDN 由大公司托管,因此可用的基礎(chǔ)設(shè)施非常龐大。他們的數(shù)據(jù)中心遍布全球。向 CDN 發(fā)出請(qǐng)求時(shí),它們將通過(guò)最近的數(shù)據(jù)中心提供服務(wù),從而減少延遲。這些公司會(huì)對(duì)服務(wù)器做負(fù)載平衡,以確保請(qǐng)求到達(dá)最近的服務(wù)器并減少網(wǎng)絡(luò)延遲,提升應(yīng)用性能。
14.使用 Web Workers 處理 CPU 密集任務(wù)
JavaScript 是一個(gè)單線(xiàn)程應(yīng)用,但在渲染網(wǎng)頁(yè)時(shí)需要執(zhí)行多個(gè)任務(wù):
處理 UI 交互、處理響應(yīng)數(shù)據(jù)、操縱 DOM 元素、啟用動(dòng)畫(huà)等。所有這些任務(wù)都由單個(gè)線(xiàn)程處理。
可以使用 worker 來(lái)分擔(dān)主線(xiàn)程的負(fù)載。
Worker 線(xiàn)程在后臺(tái)運(yùn)行,可以在不中斷主線(xiàn)程的情況下執(zhí)行多個(gè)腳本和 JavaScript 任務(wù)。
每當(dāng)需要執(zhí)行長(zhǎng)時(shí)間的 CPU 密集任務(wù)時(shí),可以使用 worker 在單獨(dú)的線(xiàn)程上執(zhí)行這些邏輯塊。
它們?cè)诟綦x環(huán)境中執(zhí)行,并且使用進(jìn)程間線(xiàn)程通信與主線(xiàn)程交互。主線(xiàn)程就可以騰出手來(lái)處理渲染和 DOM 操作任務(wù)。