做者:ManfredHu
連接:http://www.manfredhu.com/2016/11/08/23-reactRenderingPrinciple
聲明:版權全部,轉載請保留本段信息,不然請不要轉載css
React的優勢有不少,如今不少應用都接入React這個框架。
在我看來,有下列優勢:
- Facebook團隊研發並維護——有團隊維護更新且有質量保證
- 在MVVM結構下只起View的做用——簡單接入,不須要花費大量人力重構代碼
- 組件化形式構建Web應用——複用性強,提升開發效率
- 用Virtual DOM減小對DOM的頻繁操做提升頁面性能——批量操做減小重排(reflows)和重繪(repaints)次數——性能對比舊的方式有提升html
雅虎性能優化比較重要的點,老司機自行忽略。
以下圖,HTML被瀏覽器解析爲DOM樹,CSS代碼加載進來解析爲樣式結構體,二者關聯組成渲染樹,以後瀏覽器把渲染樹繪製出來就是咱們看到的網頁了。這裏若是咱們對DOM樹或者樣式結構體作一些操做,如刪除某個節點,樣式改成隱藏(display:none)等等,會觸發重排進而致使重繪。
前端
而React維護了一個Virtual DOM將短期的操做合併起來一塊兒同步到DOM,因此這也是它對整個前端領域提出的最重要的改變。node
上面說了React在MVVM結構下只起View的做用,那麼除了View,MVVM下還有Model,ViewModel。
而純粹的View,會讓整個邏輯耦合在一層下,數據也須要層層傳遞,不方便控制和複用。
react
故業內也有一堆的分層框架——如最先的flux,如今部門在用的Reflux,以及Redux。
對比Redux,Reflux更容易理解和上手——這也是現狀,學習成本越低,接入現有業務就越容易。linux
reflux的架構很是簡單,就是三部分git
因此Reflux只是讓咱們,更好的去操做組件,經過一個Action命令,叫組件去幹嗎,組件本身經過寫好的代碼,對命令作出反應(變化爲不一樣的state狀態)。github
如今你已經有了兩個小工具了,寫一個組件,經過Action調用組件就能夠了。
寫到這裏,你應該能體會到,全部的引入就是爲了讓代碼寫起來更有效率,更易用,複用性更強。web
純淨的組件:在給定相同props和state的狀況下會渲染出一樣結果
其優勢有這麼幾點:算法
React組件有兩部分
第一部分是初始化的生命週期:
第二部分是被action觸發,須要更新:
- shouldComponentUpdate
- componentWillUpdate
- render
- conponentDidUpdate
shouldComponentUpdate這個方法能夠說是一個預留的插入接口。
在上面更新的時候,第一步就是調用的這個方法判斷組件是否該被從新渲染。
shouldComponentUpdate是在React組件更新的生命週期中,用於判斷組件是否須要從新渲染的一個接口,它有兩個返回值:
- 返回true,則進入React的Virtual DOM比較過程
- 返回false,則跳過Virtual DOM比較與渲染等過程
如上圖,這是一棵React Virtual DOM的樹。
ShouldComponentUpdate
返回了true,即默認值,表明須要更新,進入Virtual DOM Diff
過程,返回false,不相同,須要更新ShouldComponentUpdate
返回了false,再也不更新,C4,C5由於被父節點在ShouldComponentUpdate
中返回了false,因此再也不更新ShouldComponentUpdate
返回了true進入Virtual DOM Diff
過程,比對結果爲false,新舊不同,須要更新ShouldComponentUpdate
返回了true,進入Virtual DOM Diff
的過程,返回了false,即新舊兩個節點不相同,因此這個節點須要更新ShouldComponentUpdate
返回了false,即不須要更新,節點不變ShouldComponentUpdate
返回了true,進入Virtual DOM Diff
比對過程,結果爲true,新舊相等,不更新大概就是這麼一個過程,在這裏,Diff算法其實仍是比較複雜的,比較好的作法是咱們來寫入ShouldComponentUpdate來本身控制組件的更新,而不是依賴React幫咱們作比較。
前面講了那麼多,相信懂React的都懂了,就再也不詳細講了,Diff算法有興趣的能夠本身去翻源碼,網上也有一堆模擬實現的例子。
接下來介紹一個探索reflux&react渲染優化的例子。
這裏試圖,模擬一個比較現實的例子,拋開不少業務代碼,讓問題變得直接。
首先例子有三個組件,兩個按鈕,5個數字,還有一個重複打印文本的大組件。
Immutable.js
優化頁面性能的例子源代碼請點擊這裏
tpl.js
的JSX代碼翻譯爲js代碼,須要的能夠本身修改,每次轉化模板須要gulp
運行一下cd ./xxx/
(這裏的xxx爲上面對應的 ……./4updateDemo/ 目錄)http-server -p 8888
端口能夠自定義,http-server模塊已在node_module
目錄下,擔憂版本依賴問題,已上傳node_module
目錄,直接打開就能夠了1basicDemo目錄是一個最原始的目錄,這裏你能夠看到咱們哪裏出現了問題。
cd ./example
打開這個沒優化過的例子的目錄
http-server -p xxxx
這裏端口隨意,不衝突就好
瀏覽器訪問並打開控制檯,會看到
5 tpl.js:32 createNum組件被更新了
tpl.js:10 TextComponent被更新了
2 tpl.js:57 createBtn組件被更新了
初始化createNum組件被渲染了5次,由於有5個,createBtn組件被渲染了兩次,由於有點擊開始和點擊結束兩個按鈕。經過不一樣的傳參而改變形態。
點擊開始會觸發action
,讓store的數據每次+1,點擊結束會清除定時器
點擊開始能夠看到控制檯的數據每次都會刷新整個界面的全部組件,特別是有一個大組件TextComponent
,是重複5000次文本的,每次從新渲染就有不少的損耗。這就是咱們要優化的地方——減小某些關鍵部分的從新渲染的次數,減小無用對比的消耗
這裏你能夠打開Chrome控制檯的Timeline來看一下,點擊開始,打開Timeline面板,每1S左右會有一個腳本執行的高峯期。
咱們知道特別是在移動端,CPU和內存的資源顯得尤其稀缺(大概只能佔用正常CPU和內存的10%,微信手Q等可能會由於友商系統對應用程序的優先級設計使這個限制略有提升——我說的就是小米哈哈哈),因此這樣說來,性能這一塊在移動手機web顯得很是很是重要。
Perl是react-addons帶來的性能分析工具,這裏的perfDemo是結合Chrome插件的例子。
要向全局暴露一個window.Perl
變量,而後就能夠愉快的配合Chrome插件使用了
這裏的wasted time就是在作屬性沒變化的重複渲染的過程,能夠優化。
用法與Chrome開發工具的TimeLine用法相似,點擊start開始記錄,後點擊stop結束
一個簡單的通用優化工具,經過淺對比(shallowCompare)方法對比新舊兩個組件的狀態,達到減小重複渲染的目的。
注意這裏組件的store必須無關聯,緣由是shallowCompare的時候,比較的是組件關聯的store的數據,而例子裏面store是一個,其餘組件num的變化也會引發這裏TextComponent組件的更新
這裏將store與頂級組件APP關聯起來,而後在子孫組件下自定採用props傳遞的方式處理(傳遞基本類型的數據),這樣就可讓pureRenderMixin的通用化了,惟一的缺點是,傳遞props要控制,只把組件須要的屬性傳遞下去,這裏會比較麻煩,可是這樣又是性能較高又比較好理解的處理方式(相對其餘要拷貝屬性的方式)
*store下,option裏面的對象,受pureRenderMixin的限制,不能夠出現引用類型
PureRenderMixin實際上是封裝了更底層的shallowCompare接口的
簡單用法以下:
var PureRenderMixin = require('react').addons.PureRenderMixin;
React.createClass({
mixins: [PureRenderMixin],
render: function() {
return <div className={this.props.className}>foo</div>;
}
});
就加了一個mixins,看起來簡單優雅有木有。能夠在衆多組件裏面copy通用啊有木有
那這裏幹了什麼?
React.addons = {
CSSTransitionGroup: ReactCSSTransitionGroup,
LinkedStateMixin: LinkedStateMixin,
PureRenderMixin: ReactComponentWithPureRenderMixin, //看這裏
var ReactComponentWithPureRenderMixin = {
//幫你寫了一個shouldComponentUpdate方法
shouldComponentUpdate: function (nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
};
function shallowCompare(instance, nextProps, nextState) {
//分別比較props和state屬性是否相等
return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);
}
function shallowEqual(objA, objB) {
if (objA === objB) { //store嵌套層級太深這裏就會返回true,引用類型內存指向同一空間
return true;
}
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
return false;
}
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
var bHasOwnProperty = hasOwnProperty.bind(objB);
for (var i = 0; i < keysA.length; i++) {
if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
return false;
}
}
return true;
}
因此PureRenderMixin這個插件,只能比較state和props爲基本類型的部分。
若是有更加深層次的store數據嵌套,就要藉助於update插件或者Immutablejs來深拷貝store的數據另存一份了。
update是addons裏面的一個方法,旨在對拷貝對象複雜的過程來作一些語法上的優化,具體能夠看react官方文檔
//extend複製對象屬性的時候
var newData = extend(myData, {
x: extend(myData.x, {
y: extend(myData.x.y, {z: 7}),
}),
a: extend(myData.a, {b: myData.a.b.concat(9)})
});
//用update的時候,提供了一些語法糖讓你不用寫那麼多
var update = require('react-addons-update');
var newData = update(myData, {
x: {y: {z: {$set: 7}}},
a: {b: {$push: [9]}}
});
cd ./updateDemo
打開這個用addons.update優化過的例子的目錄
http-server -p xxxx
這裏端口隨意,不衝突就好
這個例子與上面一個例子惟一的不一樣是這裏用了addons.update來進行store數據的複製,具體的能夠看todoStore和tpl這兩個模塊的代碼,其餘基本無修改
這裏update是參考了MongoDB’s query的部分語法,具體的能夠看這裏,類比數組方法,返回一個新的實例。
可是由Timeline的觀察來看,複製對象屬性的性能遠比刷新一個大組件的性能高。
Immutable.js是Facebook爲解決數據持久化而獨立出來的一個庫,傳統的,好比咱們有
var a = {b:1};
function test(obj){
obj.b = 10;
return obj;
}
test(a); //10
函數對對象的操做,你不會知道這個函數對對象進行了什麼操做。也就是說是封閉的。
而Immutable每次對對象的操做都會返回一個新對象
Immutable.js提供了7種不可變的數據類型:List Map Stack OrderedMap Set OrderedSet Record
,對Immutable對象的操做均會返回新的對象,例如:
var obj = {count: 1};
var map = Immutable.fromJS(obj);
var map2 = map.set('count', 2);
console.log(map.get('count')); // 1
console.log(map2.get('count')); // 2
引入Immutable.js,須要對現有的業務代碼進行改動,一般是對tpl和store兩部分進行操做,初始化數據的時候生成一個Immutable的數據類型,以後每次get,set操做都會返回一個共享的新的對象。
50ms渲染一次,重複渲染200次的截圖,引入了immutable用了其set方法:
50ms渲染一次,重複渲染200次的截圖,引入了immutable用了其update方法:
一個是immutable的閹割版,一個是AlloyTeam推的。
二者都是經過Object.defineProperty(IE9+)對set和get操做進行處理,優勢是文件比較小。
本身設想,組件化運用到極致,應該是像微信weui那樣
這裏也思考一些可能作到的變化: