本文簡要介紹了 React 中 PureComponent 與 Component 的區別以及使用時須要注意的問題,並在後面附上了源碼解析,但願對有疑惑的朋友提供一些幫助。css
先介紹一下 PureComponent,平時咱們建立 React 組件通常是繼承於 Component,而 PureComponent 至關因而一個更純淨的 Component,對更新先後的數據進行了一次淺比較。只有在數據真正發生改變時,纔會對組件從新進行 render。所以能夠大大提升組件的性能。react
裏面的 state 有兩個屬性,text 屬性是基本數據類型,todo 屬性是引用類型。針對這兩種數據類型分別進行對比:git
import React, { Component, PureComponent } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props)
this.state = {
text: 'Hello',
todo: {
id: 1,
message: '學習 React'
}
}
}
/** * 修改 state 中 text 屬性的函數 */
changeText = () => {
this.setState({
text: 'World'
});
}
/** * 修改 state 中 todo 對象的函數 */
changeTodo = () => {
this.setState({
id: 1,
message: '學習 Vue'
});
}
render() {
// 打印 log,查看渲染狀況
console.log('tag', 'render');
const { text, todo } = this.state;
return (
<div className="App"> <div> <span>文字:{ text }</span> <button onClick={ this.changeText }>更改文字</button> </div> <br /> <div> <span>計劃:{ todo.message }</span> <button onClick={ this.changeTodo }>更改計劃</button> </div> </div>
);
}
}
export default App;
複製代碼
運行項目,打開控制檯,此時看到只有一個 log:tag render
github
點擊 5 次 ·更改文字· 按鈕,能夠看到控制檯再次多打印了 5 次 log,瀏覽器中的 Hello
文字變成了 World
瀏覽器
點擊 5 次 ·更改計劃· 按鈕,控制檯同樣多打印 5 次 log,瀏覽器中的 學習 React
計劃變成了 學習 Vue
函數
分析一下,其實 5 次點擊中只有一次是有效的,後來的數據其實並無真正改變,可是因爲依然使用了 setState(),因此仍是會從新 render。因此這種模式是比較消耗性能的。工具
其實 PureComponent 用法也是和 Component 同樣,只不過是將繼承 Component 換成了 PureComponent。oop
...
// 上面的代碼和以前一致
class App extends PureComponent {
// 下面的代碼也和以前同樣
...
}
export default App;
複製代碼
和上面 Component 的測試方式同樣源碼分析
點擊 5 次 ·更改文字· 按鈕,能夠看到控制檯只多打印了** 1 次** log,瀏覽器中的 Hello
文字變成了 World
post
點擊 5 次 ·更改計劃· 按鈕,控制檯只多打印了 1 次 log,瀏覽器中的 學習 React
計劃變成了 學習 Vue
由此能夠看出,使用 PureComponent 仍是比較節省性能的,即使是使用了 setState(),也會在數據真正改變時纔會從新渲染組件
下面咱們將代碼中 changeText
和 changeTodo
方法修改一下
/** * 修改 state 中 text 屬性的函數 */
changeText = () => {
let { text } = this.state;
text = 'World';
this.setState({
text
});
}
/** * 修改 state 中 todo 對象的函數 */
changeTodo = () => {
let { todo } = this.state;
todo.message = "學習 Vue";
this.setState({
todo
});
}
複製代碼
此時咱們再從新測試一下:
點擊 ·更改文字· 按鈕,控制檯多打印一次 log,瀏覽器中的 Hello
文字變成了 World
**注意:**點擊 ·更改計劃· 按鈕,控制檯沒有 log 打印,瀏覽器中的計劃也沒有發生改變
爲何代碼修改以後,明明 todo 裏的 message 屬性也已經發生變化了,調用 setState(),卻不進行渲染了呢?這是由於 PureComponent 在調用 shouldComponent 生命週期的時候,對數據進行了一次淺比較,判斷數據是否發生改變,沒發生改變,返回 false,改變了,就返回 true。那這個淺比較的機制是怎麼作的呢?咱們一塊兒看下面源碼解析,來分析一下。
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
/** * Convenience component with default shallow equality check for sCU. */
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
複製代碼
能夠看到 PureComponent 的使用和 Component 一致,只時最後爲其添加了一個 isPureReactComponent 屬性。ComponentDummy 就是經過原型模擬繼承的方式將 Component 原型中的方法和屬性傳遞給了 PureComponent。同時爲了不原型鏈拉長致使屬性查找的性能消耗,經過 Object.assign 把屬性從 Component 拷貝了過來。
可是這裏只是 PureComponent 的聲明建立,沒有顯示如何進行比較更新的,那咱們繼續看下面的代碼。
function checkShouldComponentUpdate( workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext, ) {
...
// 這裏根據上面 PureComponent 設置的屬性 isPureReactComponent 判斷一下,若是是 PureComponent,就會走裏面的代碼,將比較的值返回出去
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
}
複製代碼
shallowEqual 是在 share
包中一個工具方法,看一下其中的內部實現吧。
import is from './objectIs';
const hasOwnProperty = Object.prototype.hasOwnProperty;
/** * Performs equality by iterating through keys on an object and returning false * when any key has values which are not strictly equal between the arguments. * Returns true when the values of all keys are strictly equal. */
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++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
export default shallowEqual;
複製代碼
這裏面還調用了 is 函數,這個函數也是 share 包中的一個工具方法。
/** * inlined Object.is polyfill to avoid requiring consumers ship their own * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is */
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}
export default is;
複製代碼
由上面的源碼能夠發現,其實 PureComponent 和 Component 中的方法和屬性基本一致,只不過 PureComponent 多了一個 isPureReactComponent 爲 true 的屬性。在 checkShouldComponentUpdate 的時候,會根據這個屬性判斷是不是 PureComponent,若是是的話,就會根據 !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState) 這個判斷語句的返回值做爲更新依據。因此,查看了 shallowEqual 和 objectIs 的文件源碼,咱們能夠得出 PureComponent 的淺比較結論:
先經過 is 函數判斷兩個參數是否相同,相同則直接返回 ture,也就是不更新組件。
若 is 函數判斷爲 false,則判斷兩個參數是否都爲 對象 且 都不爲 null,若任意一個 不是對象 或 任意一個爲 null,直接返回 false,也就是更新組件
若前兩個判斷都經過,則可判定兩個參數皆爲對象,此時判斷它們 keys 的長度是否相同,若不一樣,則直接返回 false,即更新組件
若 keys 長度不一樣,則對兩個對象中的第一層屬性進行比較,若都相同,則返回 true,有任一屬性不一樣,則返回 false
閱讀源碼以後,能夠發現以前咱們修改了 changeTodo 方法的邏輯以後,爲何數據改變,組件卻依然不更新的緣由了。是由於修改的是同一個對象,因此 PureComponent 默認引用相同,不進行組件更新,因此纔會出現這個陷阱,在使用的過程當中但願你們注意一下這個問題。
對比 PureComponent 和 Component,能夠發現,PureComponent 性能更高,通常有幾回有效修改,就會進行幾回有效更新
爲了不出現上面所說的陷阱問題,建議將 React 和 Immutable.js 配合使用,由於 Immutable.js 中的數據類型都是不可變,每一個變量都不會相同。可是因爲 Immutable 學習成本較高,能夠在項目中使用 immutability-helper 插件,也能實現相似的功能。關於 immutability-helper 的使用,能夠查看個人另外一篇博客:immutability-helper 插件的基本使用
雖然 PureComponent 提升了性能,可是也只是對數據進行了一次淺比較,最能優化性能的方式仍是本身在 shouldComponent 生命週期中實現響應邏輯
關於 PureComponent 淺比較的總結能夠查看上面的PureComponent 源碼分析總結