做者:風不識途前端
https://segmentfault.com/a/1190000039776687react
setState的同步和異步
1.爲何使用setState
-
開發中咱們並 不能直接經過修改 state
的值來 讓界面發生更新: -
由於咱們修改了 state
以後, 但願React
根據最新的Stete
來從新渲染界面, 可是這種方式的修改React
並不知道數據發生了變化 -
React
並無實現相似於Vue2
中的Object.defineProperty
或者Vue3
中的Proxy
的方式來監聽數據的變化 -
咱們必須經過 setState
來告知React
數據已經發生了變化 -
疑惑: 在組件中並無實現 steState
方法, 爲何能夠調用呢? -
緣由很簡單: setState
方法是從Component
中 繼承過來的
2.setState異步更新
setState是異步更新的 web
-
爲何 setState
設計爲異步呢? -
setState
設計爲異步其實以前在GitHub
上也有不少的討論 -
React核心成員(Redux的做者)Dan Abramov也有對應的回覆, 有興趣的能夠看一下 -
簡單的總結: setState
設計爲異步, 能夠 顯著的提升性能 -
若是每次調用 setState
都進行一次更新, 那麼意味着render
函數會被頻繁的調用界面從新渲染, 這樣的效率是很低的 -
最好的方法是獲取到多個更新, 以後進行批量更新 -
若是同步更新了 state
, 但尚未執行render
函數, 那麼state
和props
不能保持同步 -
state
和props
不能保持一致性, 會在開發中產生不少的問題
3.如何獲取異步的結果
-
如何獲取 setState
異步更新state
後的值? -
方式一: setState
的回調 -
setState
接收兩個參數: 第二個參數是回調函數(callback
), 這個回調函數會在state
更新後執行
-
方式二: componentDidUpdate
生命週期函數
3.setState必定是異步的嗎?
其實能夠分紅兩種狀況 在組件生命週期或React合成事件中, setState
是異步的在 setTimeou
或原生DOM事件中,setState
是同步的
-
驗證一: 在 setTimeout
中的更新 —> 同步更新
-
驗證二: 在原生 DOM
事件 —> 同步更新
4.源碼分析
setState的合併
1.數據的合併
-
經過 setState
去修改message
,是 不會對其餘state
中的數據產生影響的 -
源碼中實際上是有對 原對象 和 新對象 進行合併的
2.多個state的合併
-
當咱們的 屢次調用了 setState
, 只會生效最後一次state
-
setState
合併時進行累加: 給setState傳遞函數, 使用前一次state
中的值
React 更新機制
1.React 更新機制
-
咱們在前面已經學習 React
的渲染流程:
-
那麼 React 的更新流程呢?
-
React基本流程
2.React 更新流程
-
React
在props
或state
發生改變時,會調用React
的render
方法,會建立一顆不一樣的樹面試 -
React
須要基於這兩顆不一樣的樹之間的差異來判斷如何有效的更新UI
:算法 -
若是一棵樹參考另一棵樹進行徹底比較更新, 那麼即便是最早進的算法, 該算法的複雜程度爲 O(n 3 ^3 3),其中 n 是樹中元素的數量編程
-
若是在 React
中使用了該算法, 那麼展現1000
個元素所須要執行的計算量將在十億
的量級範圍 -
這個開銷太過昂貴了, React的更新性能會變得很是低效segmentfault
-
因而,
React
對這個算法進行了優化,將其優化成了O(n)
,如何優化的呢?性能優化 -
同層節點之間相互比較,不會跨節點比較微信
-
不一樣類型的節點,產生不一樣的樹結構app
-
開發中,能夠經過key來指定哪些節點在不一樣的渲染下保持穩定
狀況一: 對比不一樣類型的元素
-
當節點爲不一樣的元素,React會拆卸原有的樹,而且創建起新的樹:
-
當一個元素從
<a>
變成<img>
,從<Article>
變成<Comment>
,或從<button>
變成<div>
都會觸發一個完整的重建流程 -
當卸載一棵樹時,對應的
DOM
節點也會被銷燬,組件實例將執行componentWillUnmount()
方法 -
當創建一棵新的樹時,對應的
DOM
節點會被建立以及插入到DOM
中,組件實例將執行componentWillMount()
方法,緊接着componentDidMount()
方法 -
好比下面的代碼更改:
-
React 會銷燬 Counter 組件而且從新裝載一個新的組件,而不會對Counter進行復用
狀況二: 對比同一類型的元素
-
當比對兩個相同類型的 React 元素時,React 會保留 DOM 節點, 僅對比更新有改變的屬性 -
好比下面的代碼更改: -
經過比對這兩個元素, React
知道只須要修改DOM
元素上的className
屬性
-
好比下面的代碼更改:
-
當更新
style
屬性時,React
僅更新有所改變的屬性。 -
經過比對這兩個元素,
React
知道只須要修改DOM
元素上的color
樣式,無需修改fontWeight
-
若是是同類型的組件元素:
-
組件會保持不變,
React
會更新該組件的props
,而且調用componentWillReceiveProps()
和componentWillUpdate()
方法 -
下一步,調用
render()
方法,diff
算法將在以前的結果以及新的結果中進行遞歸
狀況三: 對子節點進行遞歸
-
在默認條件下,當遞歸
DOM
節點的子元素時,React
會同時遍歷兩個子元素的列表;當產生差別時,生成一個mutation
-
咱們來看一下在最後插入一條數據的狀況:👇
-
前面兩個比較是徹底相同的,因此不會產生mutation
-
最後一個比較,產生一個mutation,將其插入到新的DOM樹中便可
-
可是若是咱們是在前面插入一條數據:
-
React會對每個子元素產生一個mutation,而不是保持 <li>星際穿越</li>
和<li>盜夢空間</li>
的不變 -
這種低效的比較方式會帶來必定的性能問題
React 性能優化
1.key的優化
-
咱們在前面遍歷列表時,老是會提示一個警告,讓咱們加入一個 key
屬性:
-
方式一:在最後位置插入數據
-
這種狀況,有無 key
意義並不大 -
方式二:在前面插入數據
-
這種作法,在沒有 key
的狀況下,全部的<li>
都須要進行修改 -
在下面案例: 當子元素 (這裏的
li
元素) 擁有key
時 -
React
使用key
來匹配原有樹上的子元素以及最新樹上的子元素: -
下面這種場景下, key爲 111 和 222 的元素僅僅進行位移,不須要進行任何的修改
-
將
key
爲333
的元素插入到最前面的位置便可
key
的注意事項:
key
應該是惟一的key
不要使用隨機數(隨機數在下一次render時,會從新生成一個數字)使用 index
做爲key
,對性能是沒有優化的
2.render函數被調用
-
咱們使用以前的一個嵌套案例:
-
在App中,咱們增長了一個計數器的代碼 -
當點擊
+1
時,會從新調用App
的render
函數 -
而當 App 的 render函數被調用時,全部的子組件的 render 函數都會被從新調用
-
那麼,咱們能夠思考一下,在之後的開發中,咱們只要是修改 了App中的數據,全部的子組件都須要從新 render
,進行diff
算法,性能必然是很低的: -
事實上,不少的組件沒有必需要從新 render
-
它們調用 render 應該有一個前提,就是 依賴的數據(state、 props) 發生改變時, 再調用本身的 render
方法 -
如何來控制 render
方法是否被調用呢? -
經過 shouldComponentUpdate
方法便可
3.shouldComponentUpdate
React
給咱們提供了一個生命週期方法shouldComponentUpdate
(不少時候,咱們簡稱爲SCU
),這個方法接受參數,而且須要有返回值;主要做用是:**控制當前類組件對象是否調用render
**方法
-
該方法有兩個參數: -
參數一: nextProps
修改以後, 最新的porps
屬性 -
參數二: nextState
修改以後, 最新的state
屬性 -
該方法 返回值是一個 booolan 類型 -
返回值爲 true
, 那麼就須要調用render
方法 -
返回值爲 false
, 那麼不須要調用render
方法 -
好比咱們在App中增長一個 message
屬性: -
JSX
中並 沒有依賴這個message
, 那麼 它的改變不該該引發從新渲染 -
可是經過 setState
修改state
中的值, 因此最後render
方法仍是被從新調用了
// 決定當前類組件對象是否調用render方法
// 參數一: 最新的props
// 參數二: 最新的state
shouldComponentUpdate(nextProps, nextState) {
// 默認是: return true
// 不須要在頁面上渲染則不調用render函數
return false
}
4.PureComponent
-
若是全部的類, 咱們都須要手動來實現 shouldComponentUpdate
, 那麼會給咱們開發者增長很是多的工做量 -
咱們設想一下在 shouldComponentUpdate
中的 各類判斷目的是什麼? -
props
或者state
中數據是否發生了改變, 來決定shouldComponentUpdate
返回true
或false
-
事實上 React
已經考慮到了這一點, 因此React
已經默認幫咱們實現好了, 如何實現呢? -
將 class 繼承自 PureComponent -
內部會進行 淺層對比最新的 state
和porps
, 若是組件內沒有依賴porps
或state
將不會調用render
-
解決的問題: 好比某些子組件沒有依賴父組件的 state
或props
, 但卻調用了render
函數
5.shallowEqual方法
這個方法中,調用
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
,這個shallowEqual
就是進行淺層比較:
6.高階組件memo
-
函數式組件如何解決
render
: 在沒有依賴state
或props
但卻從新渲染render
問題 -
咱們須要使用一個高階組件
memo
: -
咱們將以前的Header、Banner、ProductList都經過 memo 函數進行一層包裹
-
Footer沒有使用 memo 函數進行包裹;
-
最終的效果是,當
counter
發生改變時,Header、Banner、ProductList的函數不會從新執行,而 Footer 的函數會被從新執行
import React, { PureComponent, memo } from 'react'
// MemoHeader: 沒有依賴props,不會被從新調用render渲染
const MemoHeader = memo(function Header() {
console.log('Header被調用')
return <h2>我是Header組件</h2>
})
React知識點總結腦圖
最後
本文分享自微信公衆號 - 前端瓶子君(pinzi_com)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。