原文連接:Cache your React event listeners to improve performance.
github 的地址 歡迎 star!react
在 JavaScript 中對象和函數是怎麼被引用好像不被人重視的,但它卻直接影響了 React 的性能。假設你分別創造了兩個徹底相同的函數,它們仍是不相等的。以下:git
const functionOne = function() { alert('Hello world!'); };
const functionTwo = function() { alert('Hello world!'); };
functionOne === functionTwo; // false
複製代碼
若是你把一個早已經存在的函數賦值給一個變量,比較它們時,你又會發現:github
const functionThree = function() { alert('Hello world!'); };
const functionFour = functionThree;
functionThree === functionFour; // true
複製代碼
對象也是一樣的狀況。(記住 JavaScript 中函數即對象)web
const object1 = {};
const object2 = {};
const object3 = object1;
object1 === object2; // false
object1 === object3; // true
複製代碼
若是你其餘語言編程經驗,你應該熟悉指針的。每次你建立一個對象,計算機都會分配一些內存儲存它。當我聲明 object1 = {}
,會在內存分配空間 object1
的變量。object1
又指向了儲存 {}
那塊空間的地址。當我又聲明瞭 object2 = {}
,又會在內存中開闢另外一個空間存儲這個新的 {}
,將 object2
的變量指向了那塊空間的地址。因此 object1
和 object2
指向的地址是不匹配的,這也就是爲何兩個變量比較不相等的緣由。儘管兩個變量指向的地址的內容的鍵-值是一致的,但它們表明的地址指針是不同的。編程
當我進行賦值 object3 = object1
,其實我是把 object3
和 object1
指向了內存中同一塊空間的地址。 它不是一個新的對象。你能夠這樣驗證:數組
const object1 = { x: true };
const object3 = object1;
object3.x = false;
object1.x; // false
複製代碼
這個例子中,在內存中建立一個對象,object1
指向了那個對象的地址。把 object1
賦值給 object3
的時候,object3
也指向了同一個對象的地址。當改變 object3
的時候,改變了它指向的內存空間的對象的鍵-值, 那麼其它全部引用到這個內存空間對象的地方都會發生改變。故 object1
也就會發生相同的變化。緩存
對於初級開發者,這是一個常見的錯誤,須要儘可能深刻的去了解它(本文沒有深刻涉及,能夠看看《JavaScript高級程序設計》);這篇文章主要是針對 React 性能進行討論的,可能有不少經驗的開發者都沒有考慮過引用類型變量對 React 性能的影響。bash
你會疑惑變量引用會影響 React 嗎? React 是一個性能很高,減小渲染時間的智能的庫:若是組件的 state 和 props 沒有改變,那麼 render 的輸出也不會改變。固然,全部的值都相等,根本不須要改變。假設沒有值改變, render 必須返回相同的輸出,所以沒有必要花費時間從新執行。這也是 React 快速的緣由,它僅僅在須要的時候才 render。閉包
React 肯定組件 props 和 state 的值先後是否相等,用了 JavaScript 中簡單比較 ==
的操做符進行的。 React 比較它們是否相等不是對對象進行淺(shallow )比較或者深(deep)比較。淺比較用來描述比較對象的每一個鍵值對的術語,通俗點,通常而言是對對象,遍歷它的枚舉屬性,依次用Object.is()
對對象每一個鍵對應的值進行比較,所有相等才判斷爲相等。深比較是更進一步,若是這個對象的鍵值對的值是一個對象,則繼續對那個值進行嚴格的相等驗證(繼續用 Object.is()對那個對象的每個鍵的值判斷),直到沒有對象爲止,所有深層次的比較。React 不是如此,它是比較 props 和 state 的引用是否改變。(注意 React 中的 PureComponent 是對 props 和 state 進行的淺比較)。ide
假如你改變了組件的 props,從{ x: 1}
變到另一個對象 { x: 1}
, React 是會從新 render,由於兩個對象在內存中的地址不同。假如你把組件 props 從 object1
(上面例子中)變成 boject3
, React 是不會從新 render 的,由於兩個對象是同一個的引用。
在 JavaScript 中,函數也是這種特性(函數即對象)。假如 React 組件 接受了一個功能相同但內存地址不一樣的函數,它也會從新 render。若是 React 接受相同功能的函數引用,它就不會從新 render。
不幸的是,這是我在 code review 中遇到的常見場景:
class SomeComponent extends React.PureComponent {
get instructions() {
if (this.props.do) {
return 'Click the button: ';
}
return 'Do NOT click the button: ';
}
render() {
return (
<div>
{this.instructions}
<Button onClick={() => alert('!')} />
</div>
);
}
}
複製代碼
這是一個很是簡單的組件。它是一個按鈕,當點擊的時候,它會 alert('!')
,instructions
屬性告訴你是否應該點擊它,這是由 SomeComponent
的 prop 中的 do
來控制的。
每次當 SomeComponent
從新 render (例如 do
從 true 變成 false),Button
組件也會從新 render。onClick
的事件儘管都是同樣的,但每次 render 調用都會從新建立。每次 render,一個新的函數在內存中儲存,當這個新的內存地址的引用傳遞給 Button
組件的時候,Button
組件就會從新渲染,儘管它的輸出沒有什麼改變。
若是函數沒有依賴於你的組件(沒有用 this
),你能夠在組件的外面的定義函數。你全部組件的實例都將會共享相同的一份函數的引用,假定那個函數在全部用例中功能都相同。
const createAlertBox = () => alert('!');
class SomeComponent extends React.PureComponent {
get instructions() {
if (this.props.do) {
return 'Click the button: ';
}
return 'Do NOT click the button: ';
}
render() {
return (
<div>
{this.instructions}
<Button onClick={createAlertBox} />
</div>
);
}
}
複製代碼
與先前的例子相反的,每次 render,createAlertBox
都是指向了內存中相同的地址,Button
組件毫不會從新 render。
雖然 Button
多是很小的,渲染很快的組件,(你感覺不出來),可是當你在更大的,複雜的組件上看到這些內嵌的函數定義時,你能真實地感覺到性能的影響。這是一個很是棒的又簡單的實踐:不要再 render 的方法裏面去定義這些函數。
若是函數依賴於你的組件,你不在組件外部定義它,但你能夠把組件的方法做爲事件處理函數:
class SomeComponent extends React.PureComponent {
createAlertBox = () => {
alert(this.props.message);
};
get instructions() {
if (this.props.do) {
return 'Click the button: ';
}
return 'Do NOT click the button: ';
}
render() {
return (
<div>
{this.instructions}
<Button onClick={this.createAlertBox} />
</div>
);
}
}
複製代碼
固然,每一個 SomeComponent
組件的實例的彈出框是不一樣的,這是沒法避免的,每個 SomeComponent
中 Button
組件的點擊事件監聽器必需要惟一的(不能互相干擾)。經過調用 createAlertBox
的方法,你不用關心 SomeComponent
是否從新 render,props 的 message 是否改變,Button
組件都不會從新渲染,由於它永遠指向是組件實例的那個方法,這樣能減小沒必要要渲染,提升你應用的性能。
可是若是個人函數是動態生成的,怎麼處理呢?
做者筆記:做者不假思索的寫下下面的例子,來反覆引用內存中相同的函數。這些例子旨在讓你更容易地理解引用。做者建議大家閱讀文章這一部份內容來理解引用,更但願大家在評論處給出你本身的理解。一些讀者慷慨地給出了更好的實現,其中考慮到了緩存失效和 React 中內置的內存管理器。
在單個組件的動態事件處理中,這是一種很常見不惟一的用法,像對一個數組遍歷:
class SomeComponent extends React.PureComponent {
render() {
return (
<ul>
{this.props.list.map(listItem =>
<li key={listItem.text}>
<Button onClick={() => alert(listItem.text)} />
</li>
)}
</ul>
);
}
}
複製代碼
在這個例子中,你建立了 SomeComponent
,聲明瞭動態的數量不固定的 Button
,建立了動態的事件監聽器,每一個事件監聽函數都是惟一不一樣的。怎麼解決這個難題呢?
進行記憶,或者更簡單的說法,緩存。對於每個惟一的值,建立並緩存函數;對於那個惟一值的全部未來的引用,都返回之前緩存的那個函數。
下面展現了我如何實現上面的方法:
class SomeComponent extends React.PureComponent {
// Each instance of SomeComponent has a cache of click handlers
// that are unique to it.
clickHandlers = {};
// Generate and/or return a click handler,
// given a unique identifier.
getClickHandler(key) {
// If no click handler exists for this unique identifier, create one.
if (!Object.prototype.hasOwnProperty.call(this.clickHandlers, key)) {
this.clickHandlers[key] = () => alert(key);
}
return this.clickHandlers[key];
}
render() {
return (
<ul>
{this.props.list.map(listItem =>
<li key={listItem.text}>
<Button onClick={this.getClickHandler(listItem.text)} />
</li>
)}
</ul>
);
}
}
複製代碼
list
數組裏面的每個目標值都是經過 getClickHandler
的方法調用。這個方法在第一次用參數調用它時,就會建立一個函數對應那個值,而後返回那個建立的函數。全部未來對那個函數的調用都不用再建立新的函數,相反地,它將會返回先前在內存中建立的函數的引用。
結果,從新渲染 SomeComponent
將不會致使 Button
的從新渲染。
當它們不僅由一個變量決定時,你須要發揮本身的聰明才智,給每個事件處理生成一個惟一標誌。固然,它並不比簡單地爲返回的每一個 JSX 對象生成惟一的 key 難多少。
使用索引 index 做爲惟一標誌符是須要警告的:若是這個列表 list 改變順序或者刪除某一項你將會獲得錯誤的結果。當數組從 [ 'soda', 'pizza' ]
變爲 [ 'pizza' ]
, 你緩存了你的事件監聽器像這樣 listeners[0] = () => alert('soda')
,你會發現,當你點擊索引是0的 pizza
的 Button時,彈出來是 soda
。 這也是 React 建議不要將數組的索引做爲 key 的緣由。
若是你喜歡這篇文章,請點一下贊哦。若是你有任何問題或者更好的建議,請在評論區留言。
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!
memorize-decorator
庫一塊兒使用是很是棒的在這個問題不構成性能的主要因素時,能夠直接用閉包(或者bind)的方式來解決動態事件監聽問題(能夠不作優化);影響性能的時候才進行緩存。這篇文章主要是認識通常的 React 組件更新是直接比較 props 和 state 的引用。而 PureComponent 組件則是對 props 和 state 分別先後進行淺比較。這纔是我想表達的。