哪些場景下,父組件和子組件會重新渲染?
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 操作任務。