1. 一個React Component 實例中有些什麼?
基於實踐的真理,才經得起推敲,開始與混沌鬥爭的第一步:寫個例子看看。
import React from 'react';
class Test extends React.Component {
state = {
title: "空"
};
updateTitleHander = () => {
this.setState({ title: "我是test" });
}
render() {
const {title} = this.state;
return (
<div>
<h1>{ title }</h1>
<div onClick = {this.updateTitleHander} >
Hello, 我是Test。
</div>
</div>
);
}
}
export default Test;
斷點看看一個React Component實例到底什麼樣。可以看到這個實例有很多構造方法和屬性,並且原型鏈指向了Component,這,毫無疑問。
Component構造函數
主要維護props和context兩個對象,以及一個更新器updater。
function Component(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; // We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
Component原型鏈方法包含setState
方法和forceUpdate
原型鏈之setState
setState接受兩個參數partialState
, callback
。partialState可以是對象,也可以是函數,callback將在state更新完畢後執行。
Component.prototype.setState = function (partialState, callback) {
if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
{
throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");
}
}
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
原型鏈之forceUpdate
forceUpdate只接受一個回調函數。
Component.prototype.forceUpdate = function (callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
我們可以看到,以上兩個更新方法都用到了構造函數中的updater。
updater是什麼
構造函數中有這樣一句話this.updater = updater || ReactNoopUpdateQueue;
,先查看ReactNoopUpdateQueue,可以認爲是一個state的更新器,提供了強制跟新(立即更新)和 批量更新。,那麼這倆個方法都是通過調用warnNoop
實現的,warnNoop
又是什麼呢?==而且調用warnNoop時只傳遞了這個公共實例,並沒傳遞setState傳入的待更新的部分state。==問題先放這裏,繼續往下走。
var ReactNoopUpdateQueue = {
// 檢查是否已掛載此複合組件
isMounted: function (publicInstance) {
return false;
},
// 強制更新。只有在確定 **不是** 在React代理的DOM事務中時,才應該調用這個函數。
enqueueForceUpdate: function (publicInstance, callback, callerName) {
warnNoop(publicInstance, 'forceUpdate');
},
// 替換所有狀態。 始終使用此方法或`setState`來改變state。 您應該將“ this.state”視爲不可變的。
// 無法保證“this.state”會立即更新,因此調用此方法後訪問“this.state”可能會返回舊值。
enqueueReplaceState: function (publicInstance, completeState, callback, callerName) {
warnNoop(publicInstance, 'replaceState');
},
// 設置狀態的子集。 這僅是因爲_pendingState是內部的。
// 這提供了合併策略,該合併策略不適用於深層屬性,這很令人困惑。
// 暴露pendingState或在合併期間不要使用它。
enqueueSetState: function (publicInstance, partialState, callback, callerName) {
warnNoop(publicInstance, 'setState');
}
};
但是!,ReactNoopUpdateQueue
只是未傳入updater時的備用選擇,在我進行單步調試的時候,實際上進入了react-dom.development.js
下的classComponentUpdater.enqueueSetState
。看看去,此次略去N秒,這classComponentUpdater
提供的方法和ReactNoopUpdateQueue
提供的一樣,但是方法內部的操作可就有意思了。先來看看enqueueSetState。
var classComponentUpdater = {
isMounted: isMounted,
enqueueSetState: function (inst, payload, callback) {},
enqueueReplaceState: function (inst, payload, callback) { },
enqueueForceUpdate: function (inst, callback) { }
};
classComponentUpdater.enqueueSetState
enqueueSetState接受三個參數,前面以及提到過了,inst是Component實例,payload是部分更新的state,callback還是那個更新結束後執行的那個callback。
enqueueSetState: function (inst, payload, callback) {
var fiber = get(inst); // 基於實例,生成了一個FiberNode對象
var currentTime = requestCurrentTimeForUpdate();
var suspenseConfig = requestCurrentSuspenseConfig();
var expirationTime = computeExpirationForFiber(currentTime, fiber, suspenseConfig); // 計算過期時間
var update = createUpdate(expirationTime, suspenseConfig); // 計算更新優先級
update.payload = payload;
if (callback !== undefined && callback !== null) {
{
warnOnInvalidCallback$1(callback, 'setState');
}
update.callback = callback;
}
enqueueUpdate(fiber, update); //創建更新隊列(createUpdateQueue),加到新隊列隊尾(appendUpdateToQueue),此時update中保存着待更新的部分state、過期時間及更新優先級等
scheduleWork(fiber, expirationTime); // 生成fiberRootNode
},
最後兩個方法涉及Fiber的運作原理,此次單步調試還看到了React的頂層事件代理機制,以及最後flushSyncCallbackQueue進行批量更新,內容衆多,將分次進行理解和記錄吧。
2. PureComponent是繼承而來嗎?
使用空函數原型鏈繼承方式,拷貝繼承了React Component的所有原型方法和屬性。徹底繼承,隔離了與Component的關係。PureComponent構造函數與Component構造函數簡直一般無二。特別的在原型鏈上添加了isPureReactComponent的屬性,用於後續更新階段的判斷。
function ComponentDummy() {}
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;
}
var pureComponentPrototype = PureComponent.prototype = new ComponentDummy();
pureComponentPrototype.constructor = PureComponent; // Avoid an extra prototype jump for these methods.
_assign(pureComponentPrototype, Component.prototype); // _assign = require('object-assign'); // Object.assign() ponyfill
pureComponentPrototype.isPureReactComponent = true;
這裏簡單回顧下現有的幾種繼承方法,從而對比分析React純組件繼承普通組件的優勢何在。
PureComponent.prototype指向了一個名爲ComponentDummy的空函數作爲構造函數的實例,此時PureComponent.prototype可以繼承這個空函數原型鏈上的所有原型屬性和方法,然後使用Objec.assign將Component原型鏈是的方法和屬性都拷貝過來了,直接拷貝,和Component並不會產生任何繼承相關的副作用。
3. PureComponent的更新策略怎麼實現的?
React PureComponent 源碼解析
Component & PureComponent這兩個類基本相同,唯一的區別是PureComponent的原型上多了一個標識。
這是檢查組件是否需要更新的一個判斷,ctor就是你聲明的繼承自Component or PureComponent的類,他會判斷你是否繼承自PureComponent,如果是的話就shallowEqual比較state和props。
順便說一下:React中對比一個ClassComponent是否需要更新,只有兩個地方。一是看有沒有shouldComponentUpdate方法,二就是這裏的PureComponent判斷
react更新前做判斷是否更新的源碼
if (ctor.prototype && ctor.prototype.isPureReactComponent) { //判斷要不要進行淺比較
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
看看淺比較的方法,基於Object.is()
function shallowEqual(objA, objB) {
// 判別基本類型異同
if (is$1(objA, objB)) { // is$1 = Object.is() 判斷兩個值是否相同,與強等行爲基本一致
// 特別的Object.is(+0, -0) // false
// Object.is(NaN, NaN) // 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;
}
for (var i = 0; i < keysA.length; i++) {
// hasOwnProperty$2 = Object.prototype.hasOwnProperty
// 檢測objB是否有keysA這個屬性, 再對這個屬性做Object.is比較,Object.is比較引用對象與強等一致,都是比較引用地址的
if (!hasOwnProperty$2.call(objB, keysA[i]) || !is$1(objA[keysA[i]], objB[keysA[i]])) {
return false;
}
}
return true;
}