摘要:四是在年出的持久性數(shù)據(jù)結(jié)構(gòu)的庫(kù),持久性指的是數(shù)據(jù)一旦創(chuàng)建,就不能再被更改,任何修改或添加刪除操作都會(huì)返回一個(gè)新的對(duì)象。避免大量使用操作,這樣會(huì)浪費(fèi)性能。盡量將設(shè)計(jì)成扁平狀的。
一、痛點(diǎn)
在我們的印象中,React 好像就意味著組件化、高性能,我們永遠(yuǎn)只需要關(guān)心數(shù)據(jù)整體,兩次數(shù)據(jù)之間的 UI 如何變化,則完全交給 React Virtual Dom 的 Diff 算法 去做。以至于我們很隨意的去操縱數(shù)據(jù),基本優(yōu)化shouldComponentUpdate 也懶得去寫,畢竟不寫也能正確渲染。但隨著應(yīng)用體積越來(lái)越大,會(huì)發(fā)現(xiàn)頁(yè)面好像有點(diǎn)變慢了,特別是組件嵌套比較多,數(shù)據(jù)結(jié)構(gòu)比較復(fù)雜的情況下,隨便改變一個(gè)表單項(xiàng),或者對(duì)列表做一個(gè)篩選都要耗時(shí) 100ms 以上,這個(gè)時(shí)候我們就需要優(yōu)化了!當(dāng)然如果沒(méi)有遇到性能瓶頸,完全不用擔(dān)心,過(guò)早優(yōu)化是邪惡的。這里我們總結(jié)一個(gè)很簡(jiǎn)單的方案來(lái)讓 React 應(yīng)用性能發(fā)揮到極致。在下面一部分,我們先回顧一下一些背景知識(shí),包括:JavaScript 變量類型和 React 渲染機(jī)制,如果你是老鳥可以直接跳過(guò)。
二、一些背景知識(shí)的回顧 1. 變量類型JavaScript的變量類型有兩類:
基本類型:6 種基本數(shù)據(jù)類型, Undefined 、 Null 、 Boolean 、 Number 、 String 、 Symbol
引用類型:統(tǒng)稱為 Object 類型,細(xì)分為:Object 類型、 Array 類型、 Date 類型、 RegExp 類型、 Function 類型等。
舉個(gè)例子:
let p1 = { name: "neo" }; let p2 = p1; p2.name = "dave"; console.log(p1.name); // dave
在引用類型里,聲明一個(gè) p1 的對(duì)象,把 p1 賦值給 p2 ,此時(shí)賦的其實(shí)是該對(duì)象的在堆中的地址,而不是堆中的數(shù)據(jù),也就是兩個(gè)變量指向的是同一個(gè)存儲(chǔ)空間,后面 p2.name 改變后,也就影響到了 p1。雖然這樣做可以節(jié)約內(nèi)存,但當(dāng)應(yīng)用復(fù)雜后,就需要很小心的操作數(shù)據(jù)了,因?yàn)橐徊蛔⒁庑薷囊粋€(gè)變量的值可能就影響到了另外一個(gè)變量。如果我們想要讓他們不互相影響,就需要拷貝出一份一模一樣的數(shù)據(jù),拷貝又分淺拷貝與深拷貝,淺拷貝只會(huì)拷貝第一層的數(shù)據(jù),深拷貝則會(huì)遞歸所有層級(jí)都拷貝一份,比較消耗性能。
2. React在 React 中,每次 setState , Virtual DOM 會(huì)計(jì)算出前后兩次虛擬 DOM 對(duì)象的區(qū)別,再去修改真實(shí)需要修改的 DOM 。由于 js 計(jì)算速度很快,而操作真實(shí) DOM 相對(duì)比較慢,Virtual DOM 避免了沒(méi)必要的真實(shí) DOM 操作,所以 React 性能很好。但隨著應(yīng)用復(fù)雜度的提升, DOM 樹越來(lái)越復(fù)雜,大量的對(duì)比操作也會(huì)影響性能。比如一個(gè) Table 組件,修改其中一行 Tr 組件的某一個(gè)字段, setState 后,其他所有行 Tr 組件也都會(huì)執(zhí)行一次 render 函數(shù),這其實(shí)是不必要的。我們可以通過(guò) shouldComponentUpdate 函數(shù)決定是否更新組件。大部分時(shí)候我們是可以知道哪些組件是不會(huì)變的,根本就沒(méi)必要去計(jì)算那一部分虛擬 DOM。
三、 PureComponentReact15.3 中新加了一個(gè)類PureComponent,前身是 PureRenderMixin ,和 Component 基本一樣,只不過(guò)會(huì)在 render 之前幫組件自動(dòng)執(zhí)行一次shallowEqual(淺比較),來(lái)決定是否更新組件,淺比較類似于淺復(fù)制,只會(huì)比較第一層。使用 PureComponent 相當(dāng)于省去了寫 shouldComponentUpdate 函數(shù),當(dāng)組件更新時(shí),如果組件的 props 和 state:
引用和第一層數(shù)據(jù)都沒(méi)發(fā)生改變, render 方法就不會(huì)觸發(fā),這是我們需要達(dá)到的效果。
雖然第一層數(shù)據(jù)沒(méi)變,但引用變了,就會(huì)造成虛擬 DOM 計(jì)算的浪費(fèi)。
第一層數(shù)據(jù)改變,但引用沒(méi)變,會(huì)造成不渲染,所以需要很小心的操作數(shù)據(jù)。
四、 Immutable.jsImmutable.js是 Facebook 在 2014 年出的持久性數(shù)據(jù)結(jié)構(gòu)的庫(kù),持久性指的是數(shù)據(jù)一旦創(chuàng)建,就不能再被更改,任何修改或添加刪除操作都會(huì)返回一個(gè)新的 Immutable 對(duì)象??梢宰屛覀兏菀椎娜ヌ幚砭彺?、回退、數(shù)據(jù)變化檢測(cè)等問(wèn)題,簡(jiǎn)化開發(fā)。并且提供了大量的類似原生 JS 的方法,還有 Lazy Operation 的特性,完全的函數(shù)式編程。
import { Map } from "immutable"; const map1 = Map({ a: { aa: 1 }, b: 2, c: 3 }); const map2 = map1.set("b", 50); map1 !== map2; // true map1.get("b"); // 2 map2.get("b"); // 50 map1.get("a") === map2.get("a"); // true
可以看到,修改 map1 的屬性返回 map2,他們并不是指向同一存儲(chǔ)空間,map1 聲明了只有,所有的操作都不會(huì)改變它。
ImmutableJS 提供了大量的方法去更新、刪除、添加數(shù)據(jù),極大的方便了我們操縱數(shù)據(jù)。除此之外,還提供了原生類型與 ImmutableJS 類型判斷與轉(zhuǎn)換方法:
import { fromJS, isImmutable } from "immutable"; const obj = fromJS({ a: "test", b: [1, 2, 4] }); // 支持混合類型 isImmutable(obj); // true obj.size(); // 2 const obj1 = obj.toJS(); // 轉(zhuǎn)換成原生 `js` 類型
ImmutableJS 最大的兩個(gè)特性就是: immutable data structures(持久性數(shù)據(jù)結(jié)構(gòu))與 structural sharing(結(jié)構(gòu)共享),持久性數(shù)據(jù)結(jié)構(gòu)保證數(shù)據(jù)一旦創(chuàng)建就不能修改,使用舊數(shù)據(jù)創(chuàng)建新數(shù)據(jù)時(shí),舊數(shù)據(jù)也不會(huì)改變,不會(huì)像原生 js 那樣新數(shù)據(jù)的操作會(huì)影響舊數(shù)據(jù)。而結(jié)構(gòu)共享是指沒(méi)有改變的數(shù)據(jù)共用一個(gè)引用,這樣既減少了深拷貝的性能消耗,也減少了內(nèi)存。比如下圖:
左邊是舊值,右邊是新值,我需要改變左邊紅色節(jié)點(diǎn)的值,生成的新值改變了紅色節(jié)點(diǎn)到根節(jié)點(diǎn)路徑之間的所有節(jié)點(diǎn),也就是所有青色節(jié)點(diǎn)的值,舊值沒(méi)有任何改變,其他使用它的地方并不會(huì)受影響,而超過(guò)一大半的藍(lán)色節(jié)點(diǎn)還是和舊值共享的。在 ImmutableJS 內(nèi)部,構(gòu)造了一種特殊的數(shù)據(jù)結(jié)構(gòu),把原生的值結(jié)合一系列的私有屬性,創(chuàng)建成 ImmutableJS 類型,每次改變值,先會(huì)通過(guò)私有屬性的輔助檢測(cè),然后改變對(duì)應(yīng)的需要改變的私有屬性和真實(shí)值,最后生成一個(gè)新的值,中間會(huì)有很多的優(yōu)化,所以性能會(huì)很高。
五、 案例首先我們看看只使用 React 的情況下,應(yīng)用性能為什么會(huì)被浪費(fèi),代碼地址:https://github.com/wulv/fe-ex... ,這個(gè)案例使用 create-react-app,檢測(cè)工具使用 chrome 插件:React Perf。執(zhí)行
git clone https://github.com/wulv/fe-example.git cd fe-example/react-table yarn yarn start
可以打開頁(yè)面,開始記錄,然后隨便對(duì)一列數(shù)據(jù)進(jìn)行修改,結(jié)束記錄,可以看到我們僅修改了一行數(shù)據(jù),但在 Print Wasted 那一項(xiàng)里,渲染 Tr 組件浪費(fèi)了5次:
無(wú)論是添加,刪除操作,都會(huì)浪費(fèi) n-1 次 render ,因?yàn)?App 組件的整個(gè) state 改變了,所有的組件都會(huì)重新渲染一次,最后對(duì)比出需要真實(shí) DOM 的操作。我們把 Table 組件和 Tr 繼承的 Component 改成 PureComponent ,那么, Tr 組件每次更新都會(huì)進(jìn)行一次 shallowEqual 比較,在記錄一次,會(huì)發(fā)現(xiàn)修改操作沒(méi)有了浪費(fèi),然而這個(gè)時(shí)候添加和刪除操作卻無(wú)效了,分析一下添加的操作是:
add = () => { const { data } = this.state; data.push(dataGenerate()) this.setState({ data }) }
data.push 并沒(méi)有改變 data 的引用,所以 PureComponent 的 shallowEqual 直接返回了 true ,不去 render 了。這并不是我們想要的,所以如果使用 Component 必定帶來(lái)性能浪費(fèi),使用 PureComponent 又必須保證組件需要更新時(shí),props 或 state 返回一個(gè)新引用,否則不會(huì)更新 UI。
這個(gè)時(shí)候, ImmutableJS 就可以顯示出它的威力了,因?yàn)樗梢员WC每次修改返回一個(gè)新的 Object,我們看看修改后的例子:代碼地址:https://github.com/wulv/fe-ex... ,執(zhí)行上面例子同樣的操作,可以看到:
添加,刪除,修改操作,沒(méi)有一次浪費(fèi)。沒(méi)有浪費(fèi)的原因是所有的子組件都使用了 PureComponent, ImmutableJS 保證修改操作返回一個(gè)新引用,并且只修改需要修改的節(jié)點(diǎn)(PureComponent 可以渲染出新的改動(dòng)),其他的節(jié)點(diǎn)引用保持不變(PureComponent 直接不渲染)。可以看出, PureComponent 與 ImmutableJS 簡(jiǎn)直是天生一對(duì)啊,如果結(jié)合 redux ,那就更加完美了。因?yàn)?redux 的 reducer 必須每次返回一個(gè)新的引用,有時(shí)候我們必須使用 clone 或者 assign 等操作來(lái)確保返回新引用,使用 ImmutanleJS 天然保證了這一點(diǎn),根本就不需要 lodash 等函數(shù)庫(kù)了,比如我使用redux + immutable + react-router + express 寫了一個(gè)稍微復(fù)雜點(diǎn)的例子:https://github.com/wulv/fe-ex...,可以看到 pageIndex 的 store 的狀態(tài)是:
{ loading: false, tableData: [{ "name": "gyu3w0oa5zggkanciclhm2t9", "age": 64, "height": 121, "width": 71, "hobby": { "movie": { "name": "zrah6zrvm9e512qt4typhkt9", "director": "t1c69z1vd4em1lh747dp9zfr" } } }], totle: 0 }
如果我需要快速修改 width 的值為90,比較一下使用深拷貝、 Object.assign 和 ImmutableJS 三種方式的區(qū)別:
// payload = { name: "gyu3w0oa5zggkanciclhm2t9", width: 90 } // 1. 使用深拷貝 updateWidth(state, payload) { const newState = deepClone(state); return newState.tableData.map(item => { if (tem.name === payload.name) { item.width = payload.width; } return item; }); } // 2. 使用Object.assign updateWidth(state, payload) { return Object.assign({}, state, { tableData: state.state.map(item => { if (item.name === payload.name) { return Object.assign({}, item, { width: payload.width }); } return item; }) }) } // 3. 使用ImmutableJS updateWidth(state, payload) { return state.update("tableData", list => list.update( list.findIndex((item) => item.get("name") === payload.name), item => item.set("width", payload.width))); }
使用深拷貝是一個(gè)昂貴的操作,而且引用都改變了,必然造成 re-render, 而 Object.assign 會(huì)淺復(fù)制第一層,雖然不會(huì)造成 re-render,但淺復(fù)制把其他的屬性也都復(fù)制了一次,在這里也是很沒(méi)有必要的,只有使用 ImmutableJS 完美的完成了修改,并且代碼也最少。
六、 優(yōu)勢(shì)與不足可以看出, ImmutableJS 結(jié)合 PureComponent 可以很大程度的減少應(yīng)用 re-render 的次數(shù),可以大量的提高性能。但還是有一些不足的地方:
獲取組件屬性必須用 get 或 getIn 操作(除了 Record 類型),這樣和原生的.操作比起來(lái)就麻煩多了,如果組件之前已經(jīng)寫好了,還需要大量的修改。
ImmutableJS 庫(kù)體積比較大,大概56k,開啟 gzip 壓縮后16k。
學(xué)習(xí)成本。
難以調(diào)試,在 redux-logger 里面需要在 stateTransformer 配置里執(zhí)行 state.toJS()。
七、 最佳實(shí)踐其實(shí),重要的是編程者需要有性能優(yōu)化的意識(shí),熟悉 js 引用類型的特性,了解事情的本質(zhì)比會(huì)使用某個(gè)框架或庫(kù)更加重要。用其他的方法也是完全可以達(dá)到 ImmutableJS 的效果,比如添加數(shù)據(jù)可以使用解構(gòu)操作符的方式:
add = () => { const { data } = this.state; this.setState({ data: [...data, dataGenerate()] }) }
只不過(guò)如果數(shù)據(jù)嵌套比較深,寫起來(lái)還是比較麻煩。以下有一些小技巧:
還有兩個(gè)輕量庫(kù)可以實(shí)現(xiàn)不可變數(shù)據(jù)結(jié)構(gòu):seamless-immutable或者immutability-helper,只不過(guò)原理完全不一樣,效率也沒(méi)那么高。
避免大量使用 toJS 操作,這樣會(huì)浪費(fèi)性能。
不要將簡(jiǎn)單的 JavaScript 對(duì)象與 Immutable.JS 混合
結(jié)合 redux 的時(shí)候,要使用import { combineReducers } from "redux-immutablejs";,因?yàn)?redux 的 combineReducers 期望 state 是一個(gè)純凈的 js 對(duì)象。
盡量將 state 設(shè)計(jì)成扁平狀的。
展示組件不要使用 Immutable 數(shù)據(jù)結(jié)構(gòu)。
不要在 render 函數(shù)里一個(gè) PureComponent 組件的 props 使用 bind(this) 或者 style={ { width: "100px" } },因?yàn)?shallowEqual 一定會(huì)對(duì)比不通過(guò)。
八、 參考鏈接Immutable.js, persistent data structures and structural sharing
immutable.js is much faster than native javascript
Immutable 詳解及 React 中實(shí)踐
本文首發(fā)于有贊技術(shù)博客。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/88786.html
摘要:數(shù)據(jù)管理及性能優(yōu)化統(tǒng)一管理數(shù)據(jù)這一部份算是重頭戲吧。重復(fù)渲染導(dǎo)致卡頓這套的東西在家校群頁(yè)面上用得很歡樂(lè),以至于不用怎么寫都沒(méi)遇到過(guò)什么性能問(wèn)題。但放到移動(dòng)端上,我們?cè)诹斜眄?yè)重構(gòu)的時(shí)候就馬上遇到卡頓的問(wèn)題了。列表頁(yè)目前的處理辦法是將值換成。 本文來(lái)自于騰訊bugly開發(fā)者社區(qū),非經(jīng)作者同意,請(qǐng)勿轉(zhuǎn)載,原文地址:http://dev.qq.com/topic/57908... 最近一個(gè)季度...
摘要:前端日?qǐng)?bào)精選被譽(yù)為神器的我是怎樣讓網(wǎng)站用上的性能優(yōu)化一當(dāng)遇上異步隊(duì)列實(shí)現(xiàn)及拓展技術(shù)大會(huì)震撼登陸,明星團(tuán)隊(duì)講師傾城而出中文秋招前端工程師百度阿里網(wǎng)易騰訊全面經(jīng)之自定義頭像功能實(shí)現(xiàn)掘金中支持簡(jiǎn)書發(fā)布了字符串轉(zhuǎn)數(shù)字陷阱示例眾成翻譯性 2017-09-29 前端日?qǐng)?bào) 精選 被譽(yù)為神器的requestAnimationFrame我是怎樣讓網(wǎng)站用上HTML5 ManifestReact 的性能優(yōu)化...
摘要:函數(shù)組件上面我們探討了如何使用和的方法優(yōu)化類組件的性能。它的作用和類似,是用來(lái)控制函數(shù)組件的重新渲染的。其實(shí)就是函數(shù)組件的。 原文鏈接: Improving Performance in React Functional Component using React.memo 原文作者: Chidume Nnamdi 譯者: 進(jìn)擊的大蔥 推薦理由: 本文講述了開發(fā)React應(yīng)用時(shí)如...
摘要:引言本周精讀的文章是,看看作者是如何解釋這個(gè)多態(tài)性含義的。讀完文章才發(fā)現(xiàn),文章標(biāo)題改為的多態(tài)性更妥當(dāng),因?yàn)檎恼露荚谡f(shuō),而使用場(chǎng)景不局限于。更多討論討論地址是精讀的多態(tài)性如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。 1 引言 本周精讀的文章是:surprising-polymorphism-in-react-applications,看看作者是如何解釋這個(gè)多態(tài)性含...
閱讀 1363·2023-04-26 00:10
閱讀 2461·2021-09-22 15:38
閱讀 3937·2021-09-22 15:13
閱讀 3552·2019-08-30 13:11
閱讀 672·2019-08-30 11:01
閱讀 3069·2019-08-29 14:20
閱讀 3236·2019-08-29 13:27
閱讀 1753·2019-08-29 11:33