最近一直在學習關於React方面的知識,並有幸正好獲得一個機會將其用在了實際的項目中。因此我打算以博客的形式,將我在學習和開發(React)過程當中遇到的問題記錄下來。html
這兩天遇到了關於組件沒必要要的重複渲染問題,看了不少遍官方文檔以及網上各位大大們的介紹,下面我會經過一些demo結合本身的理解進行彙總,並以此做爲學習React的第一篇筆記(本身學習,什麼都好,就是費頭髮...)。vue
本文主要介紹如下三種優化方式(三種方式有着類似的實現原理):react
其中shouldComponentUpdate
和React.PureComponent
是類組件中的優化方式,而React.memo
是函數組件中的優化方式。編程
import React, { Component } from 'react'
import Child from './Child'
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
parentInfo: 'parent',
sonInfo: 'son'
}
this.changeParentInfo = this.changeParentInfo.bind(this)
}
changeParentInfo() {
this.setState({
parentInfo: `改變了父組件state:${Date.now()}`
})
}
render() {
console.log('Parent Component render')
return (
<div>
<p>{this.state.parentInfo}</p>
<button onClick={this.changeParentInfo}>改變父組件state</button>
<br/>
<Child son={this.state.sonInfo}></Child>
</div>
)
}
}
export default Parent
複製代碼
import React, {Component} from 'react'
class Child extends Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
console.log('Child Component render')
return (
<div>
這裏是child子組件:
<p>{this.props.son}</p>
</div>
)
}
}
export default Child
複製代碼
Parent Component render
和Child Component render
。
Parent Component render
和Child Component render
。
Parent
state中的parentInfo
的值,Parent
更新的同時子組件Child
也進行了從新渲染,這確定是咱們不肯意看到的。因此下面咱們就圍繞這個問題介紹本文的主要內容。React提供了生命週期函數shouldComponentUpdate()
,根據它的返回值(true | false),判斷 React 組件的輸出是否受當前 state 或 props 更改的影響。默認行爲是 state 每次發生變化組件都會從新渲染(這也就說明了上面👆Child組件從新渲染的緣由)。api
引用一段來自官網的描述:數組
當 props 或 state 發生變化時,
shouldComponentUpdate()
會在渲染執行以前被調用。返回值默認爲 true。目前,若是shouldComponentUpdate
返回 false,則不會調用UNSAFE_componentWillUpdate()
,render()
和componentDidUpdate()
方法。後續版本,React 可能會將shouldComponentUpdate()
視爲提示而不是嚴格的指令,而且,當返回 false 時,仍可能致使組件從新渲染。緩存
shouldComponentUpdate
方法接收兩個參數nextProps
和nextState
,能夠將this.props
與nextProps
以及this.state
與nextState
進行比較,並返回 false 以告知 React 能夠跳過更新。性能優化
shouldComponentUpdate (nextProps, nextState) {
return true
}
複製代碼
此時咱們已經知道了shouldComponentUpdate
函數的做用,下面咱們在Child
組件中添加如下代碼:bash
shouldComponentUpdate(nextProps, nextState) {
return this.props.son !== nextProps.son
}
複製代碼
這個時候再點擊按鈕修改父組件 state 中的parentInfo
的值時,Child
組件就不會再從新渲染了。數據結構
這裏有個注意點就是,咱們從父組件Parent
向子組件Child
傳遞的是基本類型的數據,若傳遞的是引用類型的數據,咱們就須要在shouldComponentUpdate
函數中進行深層比較。但這種方式是很是影響效率,且會損害性能的。因此咱們在傳遞的數據是基本類型是能夠考慮使用這種方式進行性能優化。
(關於基本類型數據和引用類型數據的介紹,能夠參考一下這篇文章:傳送門)
React.PureComponent
與React.Component
很類似。二者的區別在於React.Component
並未實現 shouldComponentUpdate
,而React.PureComponent
中以淺層對比 prop 和 state 的方式來實現了該函數。
將Child
組件的內容修改成如下內容便可,這是否是很方便呢。
import React, { PureComponent } from 'react'
class Child extends PureComponent {
constructor(props) {
super(props)
this.state = {
}
}
render() {
console.log('Child Component render')
return (
<div>
這裏是child子組件:
<p>{this.props.son}</p>
</div>
)
}
}
export default Child
複製代碼
因此,當組件的 props 和 state 均爲基本類型時,使用React.PureComponent
能夠起到優化性能的做用。
若是對象中包含複雜的數據結構,則有可能由於沒法檢查深層的差異,產生錯誤的比對結果。
爲了更好的感覺引用類型數據傳遞的問題,咱們先改寫一下上面的例子:
Child
組件。import React, {Component} from 'react'
class Child extends Component {
constructor(props) {
super(props)
this.state = {}
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.parentInfo !== nextProps.parentInfo
}
updateChild () {
this.forceUpdate()
}
render() {
console.log('Child Component render')
return (
<div>
這裏是child子組件:
<p>{this.props.parentInfo[0].name}</p>
</div>
)
}
}
export default Child
複製代碼
Parent
組件。import React, { Component } from 'react'
import Child from './Child'
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
parentInfo: [
{ name: '哈哈哈' }
]
}
this.changeParentInfo = this.changeParentInfo.bind(this)
}
changeParentInfo() {
let temp = this.state.parentInfo
temp[0].name = '呵呵呵:' + new Date().getTime()
this.setState({
parentInfo: temp
})
}
render() {
console.log('Parent Component render')
return (
<div>
<p>{this.state.parentInfo[0].name}</p>
<button onClick={this.changeParentInfo}>改變父組件state</button>
<br/>
<Child parentInfo={this.state.parentInfo}></Child>
</div>
)
}
}
export default Parent
複製代碼
此時在控制檯能夠看到,Parent
和Child
都進行了一次渲染,顯示的內容是一致的。
點擊按鈕,那麼問題來了,如圖所示,父組件Parent
進行了從新渲染,從頁面上咱們能夠看到,Parent
組件中的parentInfo
確實已經發生了改變,而子組件卻沒有發生變化。
因此當咱們在傳遞引用類型數據的時候,shouldComponentUpdate()
和React.PureComponent
存在必定的侷限性。
針對這個問題,官方給出的兩個解決方案:
forceUpdate()
來確保組件被正確地更新(不推薦使用);immutable
對象加速嵌套數據的比較(不一樣於深拷貝);當咱們明確知道父組件Parent
修改了引用類型的數據(子組件的渲染依賴於這個數據),此時調用forceUpdate()
方法強制更新子組件,注意,forceUpdate()
會跳過子組件的shouldComponentUpdate()
。
修改Parent
組件(將子組件經過ref暴露給父組件,在點擊按鈕後調用子組件的方法,強制更新子組件,此時咱們能夠看到在父組件更新後,子組件也進行了從新渲染)。
{
...
changeParentInfo() {
let temp = this.state.parentInfo
temp[0].name = '呵呵呵:' + new Date().getTime()
this.setState({
parentInfo: temp
})
this.childRef.updateChild()
}
render() {
console.log('Parent Component render')
return (
<div>
<p>{this.state.parentInfo[0].name}</p>
<button onClick={this.changeParentInfo}>改變父組件state</button>
<br/>
<Child ref={(child)=>{this.childRef = child}} parentInfo={this.state.parentInfo}></Child>
</div>
)
}
}
複製代碼
Immutable.js是 Facebook 在 2014 年出的持久性數據結構的庫,持久性指的是數據一旦建立,就不能再被更改,任何修改或添加刪除操做都會返回一個新的 Immutable 對象。可讓咱們更容易的去處理緩存、回退、數據變化檢測等問題,簡化開發。而且提供了大量的相似原生 JS 的方法,還有 Lazy Operation 的特性,徹底的函數式編程。
Immutable 則提供了簡潔高效的判斷數據是否變化的方法,只需 === 和 is 比較就能知道是否須要執行 render(),而這個操做幾乎 0 成本,因此能夠極大提升性能。首先將Parent
組件中調用子組件強制更新的代碼this.childRef.updateChild()
進行註釋,再修改Child
組件的shouldComponentUpdate()
方法:
import { is } from 'immutable'
shouldComponentUpdate (nextProps = {}, nextState = {}) => {
return !(this.props === nextProps || is(this.props, nextProps)) ||
!(this.state === nextState || is(this.state, nextState))
}
複製代碼
此時咱們再查看控制檯和頁面的結果能夠發現,子組件進行了從新渲染。
關於shouldComponentUpdate()
函數的優化,上面👆的方法還有待驗證,僅做爲demo使用,實際的開發過程當中可能須要進一步的探究選用什麼樣的插件,什麼樣的判斷方式纔是最全面、最合適的。若是你們有好的建議和相關的文章歡迎砸過來~
關於React.memo
的介紹,官網描述的已經很清晰了,這裏我就直接照搬了~
React.memo
爲高階組件。它與React.PureComponent
很是類似,但只適用於函數組件,而不適用 class 組件。
若是你的函數組件在給定相同 props 的狀況下渲染相同的結果,那麼你能夠經過將其包裝在React.memo
中調用,以此經過記憶組件渲染結果的方式來提升組件的性能表現。這意味着在這種狀況下,React 將跳過渲染組件的操做並直接複用最近一次渲染的結果。
React.memo
僅檢查 props 變動。若是函數組件被React.memo
包裹,且其實現中擁有 useState 或 useContext 的 Hook,當 context 發生變化時,它仍會從新渲染。
默認狀況下其只會對複雜對象作淺層對比,若是你想要控制對比過程,那麼請將自定義的比較函數經過第二個參數傳入來實現。
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
若是把 nextProps 傳入 render 方法的返回結果與
將 prevProps 傳入 render 方法的返回結果一致則返回 true,
不然返回 false
*/
}
export default React.memo(MyComponent, areEqual)
複製代碼
使用函數組件改寫一下上面的例子:
Child
組件:
import React, {useEffect} from 'react'
// import { is } from 'immutable'
function Child(props) {
useEffect(() => {
console.log('Child Component')
})
return (
<div>
這裏是child子組件:
<p>{props.parentInfo[0].name}</p>
</div>
)
}
export default Child
複製代碼
Parent
組件:
import React, {useEffect, useState} from 'react'
import Child from './Child'
function Parent() {
useEffect(() => {
console.log('Parent Component')
})
const [parentInfo, setParentInfo] = useState([{name: '哈哈哈'}])
const [count, setCount] = useState(0)
const changeCount = () => {
let temp_count = count + 1
setCount(temp_count)
}
return (
<div>
<p>{count}</p>
<button onClick={changeCount}>改變父組件state</button>
<br/>
<Child parentInfo={parentInfo}></Child>
</div>
)
}
export default Parent
複製代碼
運行程序後,和上面的例子進行同樣的操做,咱們會發現隨着父組件count
的值的修改,子組件也在進行重複渲染,因爲是函數組件,因此咱們只能經過React.memo
高階組件來跳過沒必要要的渲染。
修改Child
組件的導出方式:export default React.memo(Child)
。
再運行程序,咱們能夠看到父組件雖然修改了count
的值,但子組件跳過了渲染。
這裏我用的是React hooks的寫法,在hooks中useState
修改引用類型數據的時候,每一次修改都是生成一個新的對象,也就避免了引用類型數據傳遞的時候,子組件不更新的狀況。
剛接觸react,最大的感觸就是它的自由度是真的高,全部的內容均可以根據本身的喜愛設置,但這也增長了初學者的學習成本。(不過付出和收穫是成正比的,繼續個人救贖之路!)
shouldComponentUpdate()
和 React.PureComponent
在基本類型數據傳遞時均可以起到優化做用,當包含引用類型數據傳遞的時候,shouldComponentUpdate()
更合適一些。React.memo
。另外吐槽一下如今的網上的部分「博客」,一堆重複(如出一轍)的文章。複製別人的文章也請本身驗證一下吧,API變動、時代發展等因素引發的問題理解,可是連錯別字,錯誤的使用方法都全篇照搬,而後文末貼一下別人的地址這就結束了???怕別人的地址失效,想保存下來?但這種方式不說誤導別人,就說本身回顧的時候也會有問題吧,這是什麼樣的心態?
再說下上個月身邊的真實例子。有個同事寫了篇關於vue模板方面的博客,過了兩天居然在今日頭條的推薦欄裏面看到了如出一轍的一篇文章,連文中使用的圖片都是徹底同樣(這個侵權的博主是誰這裏就不透露了,他發的文章、關注者還挺多,只能表示呵呵了~)。和這位「光明磊落」的博主進行溝通,獲得的倒是:「什麼你的個人,我看到了就是個人」這樣的回覆。真是天下之大,無奇不有,果斷向平臺提交了侵權投訴。而後該博主又舔着臉求放過,否則號要被封了,可真是可笑呢...
這篇文章就先到這裏啦,畢竟還處於自學階段,不少理解還不是很全面,文中如有不足之處,歡迎各位看官大大們的指正
看完點個贊吧,謝謝~