開發中的絕大多數時候,我們并不需要關注 React 項目的性能問題。雖然我們在前面幾個章節中,也花了幾篇文章來分析如何優化 React 的性能體驗,但是這些知識點在開發過程中能用到的機會其實比較少。面試的時候用得比較多。
但是,當你的項目遇到性能瓶頸,如何優化性能就變得非常重要。當然,我們前面幾篇文章已經把性能優化的方式和方法說得非常清晰了,大家可以回顧一下。這篇文章我們要分享的重點是,當我采用不同的方式優化之后,代碼邏輯執行所要付出的代價到底如何。
例如,當我們得知 React 的 DIFF 是全量比較的時候,可能第一個反應就是覺得他性能差。但是具體性能差到什么程度呢?有沒有一個具體的數據來支撐?不確定,這只是一種主觀感受。優化之后的性能到底強不強呢,也不敢肯定。
因此,這篇文章主要給大家介紹幾種 react 在 diff 過程中用到的比較方式,以及當這幾種方式大量執行時,執行所要花費的時間。
又稱為全等比較,這是一種成本最低的比較方式。在 React 中,state 與 props 的比較都會用到這樣的方式。
var prevProps = {}var nextProps = {}if (prevProps === nextProps) { ...}
那么,這種比較方式的成本有多低呢?我們來寫一個循環簡單驗證一下。分別看看比較一萬次需要多長時間。
var markTime = performance.now()var prev = {}var next = {}for(var i = 0; i <= 10000; i++) { if (prev === next) { console.log('相等') }}var endTime = performance.now()console.log(endTime - markTime)
執行結果會有小范圍波動,展示出來的結果都是取的多次執行的平均值,或者出現次數最多的執行結果。比如本案例執行,用時最短的是 0.3 ms,用時最長的是 0.8 ms。
可以看到,對比一萬次,用時約 0.6ms。
對比一百萬次,用時約 6.4ms。
通常情況下,我們項目的規模應該很難超過一萬次,控制得好一點,一般都在 1000 次以內。多一點也應該在 5000 次以內,5000 次用這種方式的對比只需要 0.3ms 左右。
Object.is 是一種與全等比較相似但不同的比較方式,他們的區別就在于處理帶符號的 0 和 NaN 時結果不一樣。
+0 === -0 // trueObject.is(+0, -0) // falseNaN === NaN // falseObject.is(NaN, NaN) // true
React 源碼里為 Object.is 做了兼容處理,因此多了一點判斷,所以他的性能上會比全等要差一些。
function is(x, y) { return ( (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare );}const objectIs = typeof Object.is === 'function' ? Object.is : is;
那么差多少呢?我們先寫一個邏輯來看一下執行一萬次比較需要多久。
function is(x, y) { return ( (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare );}const objectIs = typeof Object.is === 'function' ? Object.is : is;var markTime = performance.now()var prev = {}var next = {}for(var i = 0; i <= 10000; i++) { if (objectIs(prev, next)) { console.log('相等') }}var endTime = performance.now()console.log(endTime - markTime)
執行結果如下,大概是 0.8ms。
執行一百萬次,用時約 11.4ms。
那么我們的項目規模在 5000 次比較以內的話,用時估計在 0.4ms 左右,比全等比較多用了 0.1ms。
這種淺比較的成本就稍微大一些,例如,當我們對子組件使用了 memo
包裹之后,那么在 diff 過程中,對于 props 的比較方式就會轉變成這樣方式,他們會遍歷判斷 props 第一層每一項子屬性是否相等。
function shallowEqual(objA: mixed, objB: mixed): boolean { if (is(objA, objB)) { return true; } if ( typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null ) { return false; } const keysA = Object.keys(objA); const keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } // Test for A's keys different from B. for (let i = 0; i < keysA.length; i++) { const currentKey = keysA[i]; if ( !hasOwnProperty.call(objB, currentKey) || !is(objA[currentKey], objB[currentKey]) ) { return false; } } return true;}
首先,這種比較方式在 React 中出現的次數非常的少,只有我們手動新增了 memo 之后才會進行這種比較,因此,我們測試的時候,先以 1000 次為例看看結果。
我們定義兩個數量稍微多一點的 props 對象,他們最有最后一項不相同,因此比較的次數會拉滿。
var prev = {a:1, b: 1, c: 1, d: 1, e: 1, f: 1, g: 1}var next = {a:1, b: 1, c: 1, d: 1, e: 1, f: 1, g: 2}
完整代碼如下:
function is(x, y) { return ( (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare );}const objectIs = typeof Object.is === 'function' ? Object.is : is;function shallowEqual(objA, objB) { if (is(objA, objB)) { return true; } if ( typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null ) { return false; } const keysA = Object.keys(objA); const keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } // Test for A's keys different from B. for (let i = 0; i < keysA.length; i++) { const currentKey = keysA[i]; if ( !Object.hasOwnProperty.call(objB, currentKey) || !is(objA[currentKey], objB[currentKey]) ) { return false; } } return true;}var markTime = performance.now()var prev = {a:1, b: 1, c: 1, d: 1, e: 1, f: 1, g: 1}var next = {a:1, b: 1, c: 1, d: 1, e: 1, f: 1, g: 2}for(var i = 0; i <= 1000; i++) { if (shallowEqual(prev, next)) { console.log('相等') }}var endTime = performance.now()console.log(endTime - markTime)
1000 次比較結果耗時如下,約為 1.4ms。
5000 次比較結果耗時如下,約為 3.6ms。
10000 次比較結果耗時如下,約為 6.6 ms。
這里我們可以做一個簡單的調整,讓對比耗時直接少一半。那就是把唯一的變化量,寫到前面來,如圖所示,耗時只用了 3.1ms。
運用到實踐中,就是把 props 中的變量屬性,盡量寫在前面,能夠大幅度提高對比性能。
次數 | 全等 | is | shallow |
五千 | 0.3 | 0.4 | 3.6 |
一萬 | 0.6 | 0.8 | 6.6 |
百萬 | 6.4 | 11.4 | 162 |
因此我們從測試結果中看到,全量 diff 并不可怕,如果你對性能優化的理解非常到位,那么能你的項目中,全量 diff 所花費的時間只有 0.幾ms,理論的極限性能就是只在你更新的組件里對比出差異,執行 re-render。
當然,由于對于 React 內部機制的理解程度不同,會導致一些差異,例如有些同學的項目中,會執行過多的冗余 re-render。從而導致在大型項目中性能體驗可能出現問題。那么這種情況下,也不用擔心,有一種超級笨辦法,那就是在項目中,結合我們剛才在 shallowEqual 中提高的優化方案,無腦使用 useCallback 與 memo,你的項目性能就能得到明顯的提高,當然,這個方案不夠優雅但是管用。
可以看出,React 性能優化最重要的手段非常簡單:就是控制當前渲染內容的節點規模。
本文鏈接:http://www.tebozhan.com/showinfo-26-84462-0.htmlReact 中,用到的幾種淺比較方式及其比較成本科普
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com