前言
- 適合有一定 React 項(xiàng)目經(jīng)驗(yàn)閱讀焚挠,默認(rèn)對 React 的常用 api 較為熟悉
- 研究 React 源碼是結(jié)合網(wǎng)上的一些分析文章+自己看代碼理解
- 最開始看是因?yàn)轫?xiàng)目中遇到性能問題墨礁,網(wǎng)上沒有相關(guān)資料廷臼,所以想找到具體影響的點(diǎn)
- 以下的代碼解析以 15.4.1 版本為基礎(chǔ)粒督,且去除了開發(fā)環(huán)境的 warning彼棍,為了區(qū)分,保留的注釋都為英文跷乐,新增的注釋為中文肥败,盡量保持原注釋
- 文中有部分自己的演繹、理解、猜測馒稍,如有誤煩請指出
基礎(chǔ)概念
ReactElement
數(shù)據(jù)類皿哨,只包含 props refs key 等
由 React.creatElement(ReactElement.js) 創(chuàng)建,React.createClass 中 render 中返回的實(shí)際也是個(gè) ReactElement
ReactComponent
控制類纽谒,包含組件狀態(tài)证膨,操作方法等
包括字符組件、原生 DOM 組件鼓黔、自定義組件(和空組件)
在掛載組件(mountComponent)的時(shí)候央勒,會(huì)調(diào)用到 instantiateReactComponent 方法,利用工廠模式澳化,通過不同的輸入返回不同的 component
代碼(instantiateReactComponent.js):
function instantiateReactComponent(node, shouldHaveDebugID) {
var instance;
if (node === null || node === false) {
instance = ReactEmptyComponent.create(instantiateReactComponent);
} else if (typeof node === 'object') {
var element = node;
// Special case string values
if (typeof element.type === 'string') {
instance = ReactHostComponent.createInternalComponent(element);
} else if (isInternalComponentType(element.type)) {
// This is temporarily available for custom components that are not string
// representation, we can drop this code path.
} else {
instance = new ReactCompositeComponentWrapper(element);
}
} else if (typeof node === 'string' || typeof node === 'number') {
instance = ReactHostComponent.createInstanceForText(node);
} else {
}
// These two fields are used by the DOM and ART diffing algorithms
// respectively. Instead of using expandos on components, we should be
// storing the state needed by the diffing algorithms elsewhere.
instance._mountIndex = 0;
instance._mountImage = null;
return instance;
}
ReactDOMTextComponent 只關(guān)心文本崔步,ReactDOMComponent 會(huì)稍微簡單一些,ReactCompositeComponent 需要關(guān)心的最多缎谷,包括得到原生 DOM 的渲染內(nèi)容
ReactClass
這個(gè)比較特殊井濒,對比 ES5 寫法: var MyComponent = React.createClass({}),ES6寫法:class MyComponent extends React.Component列林,為什么用createClass卻得到了Component呢瑞你?通過源碼來看,這兩個(gè) api 的實(shí)現(xiàn)幾乎是一樣的希痴,也可以看到捏悬,ES6 的寫法簡潔的多,不用那些getInitialState等特定 api润梯,React 在之后的版本也會(huì)拋棄createClass這個(gè) api过牙。并且,在此 api 中纺铭,React 進(jìn)行了autobind寇钉。
ReactClass.js:
var ReactClass = {
createClass: function (spec) {
// ensure that Constructor.name !== 'Constructor'
var Constructor = identity(function (props, context, updater) {
// Wire up auto-binding
if (this.__reactAutoBindPairs.length) {
bindAutoBindMethods(this);
}
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
this.state = null;
// ReactClasses doesn't have constructors. Instead, they use the
// getInitialState and componentWillMount methods for initialization.
var initialState = this.getInitialState ? this.getInitialState() : null;
this.state = initialState;
});
Constructor.prototype = new ReactClassComponent();
Constructor.prototype.constructor = Constructor;
Constructor.prototype.__reactAutoBindPairs = [];
injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));
mixSpecIntoComponent(Constructor, spec);
// Initialize the defaultProps property after all mixins have been merged.
if (Constructor.getDefaultProps) {
Constructor.defaultProps = Constructor.getDefaultProps();
}
// Reduce time spent doing lookups by setting these on the prototype.
for (var methodName in ReactClassInterface) {
if (!Constructor.prototype[methodName]) {
Constructor.prototype[methodName] = null;
}
}
return Constructor;
}
}
var ReactClassComponent = function () {};
_assign(ReactClassComponent.prototype, ReactComponent.prototype, ReactClassMixin);
ReactComponent.js:
function ReactComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
ReactComponent.prototype.isReactComponent = {};
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
ReactComponent.prototype.forceUpdate = function (callback) {
this.updater.enqueueForceUpdate(this);
if (callback) {
this.updater.enqueueCallback(this, callback, 'forceUpdate');
}
};
對象池
- 開辟空間是需要一定代價(jià)的
- 如果引用釋放而進(jìn)入 gc,gc 會(huì)比較消耗性能和時(shí)間舶赔,如果內(nèi)存抖動(dòng)(大量的對象被創(chuàng)建又在短時(shí)間內(nèi)馬上被釋放)而頻繁 gc 則會(huì)影響用戶體驗(yàn)
- 既然創(chuàng)建和銷毀對象是很耗時(shí)的扫倡,所以要盡可能減少創(chuàng)建和銷毀對象的次數(shù)
- 使用時(shí)候申請(getPooled)和釋放(release)成對出現(xiàn),使用一個(gè)對象后一定要釋放還給池子(釋放時(shí)候要對內(nèi)部變量置空方便下次使用)
- 代碼(PooledClass.js):
// 只展示部分
var oneArgumentPooler = function (copyFieldsFrom) {
var Klass = this;
if (Klass.instancePool.length) {
var instance = Klass.instancePool.pop();
Klass.call(instance, copyFieldsFrom);
return instance;
} else {
return new Klass(copyFieldsFrom);
}
};
var standardReleaser = function (instance) {
var Klass = this;
if (Klass.instancePool.length < Klass.poolSize) {
Klass.instancePool.push(instance);
}
};
var DEFAULT_POOL_SIZE = 10;
var DEFAULT_POOLER = oneArgumentPooler;
var addPoolingTo = function (CopyConstructor, pooler) {
// Casting as any so that flow ignores the actual implementation and trusts
// it to match the type we declared
var NewKlass = CopyConstructor;
NewKlass.instancePool = [];
NewKlass.getPooled = pooler || DEFAULT_POOLER;
if (!NewKlass.poolSize) {
NewKlass.poolSize = DEFAULT_POOL_SIZE;
}
NewKlass.release = standardReleaser;
return NewKlass;
};
var PooledClass = {
addPoolingTo: addPoolingTo,
oneArgumentPooler: oneArgumentPooler,
twoArgumentPooler: twoArgumentPooler,
threeArgumentPooler: threeArgumentPooler,
fourArgumentPooler: fourArgumentPooler,
fiveArgumentPooler: fiveArgumentPooler
};
module.exports = PooledClass;
使用例子(ReactUpdate.js):
var transaction = ReactUpdatesFlushTransaction.getPooled();
destructor: function () {
this.dirtyComponentsLength = null;
CallbackQueue.release(this.callbackQueue);
this.callbackQueue = null;
ReactUpdates.ReactReconcileTransaction.release(this.reconcileTransaction);
this.reconcileTransaction = null;
}
ReactUpdatesFlushTransaction.release(transaction);
- 可以看到竟纳,如果短時(shí)間內(nèi)生成了大量的對象占滿了池子撵溃,后續(xù)的對象是不能復(fù)用只能新建的
- 對比連接池、線程池:完成任務(wù)后并不銷毀锥累,而是可以復(fù)用去執(zhí)行其他任務(wù)
事務(wù)機(jī)制
- React 通過事務(wù)機(jī)制來完成一些特定操作缘挑,比如 merge state,update component
- 示意圖(Transaction.js):
代碼(Transaction.js):
var TransactionImpl = {
perform: function (method, scope, a, b, c, d, e, f) {
var errorThrown;
var ret;
try {
this._isInTransaction = true;
// Catching errors makes debugging more difficult, so we start with
// errorThrown set to true before setting it to false after calling
// close -- if it's still set to true in the finally block, it means
// one of these calls threw.
errorThrown = true;
this.initializeAll(0);
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
if (errorThrown) {
// If `method` throws, prefer to show that stack trace over any thrown
// by invoking `closeAll`.
try {
this.closeAll(0);
} catch (err) {}
} else {
// Since `method` didn't throw, we don't want to silence the exception
// here.
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
},
// 執(zhí)行所有 wrapper 中的 initialize 方法
initializeAll: function (startIndex) {
},
// 執(zhí)行所有 wrapper 中的 close 方法
closeAll: function (startIndex) {
}
};
module.exports = TransactionImpl;
- 可以看到和后端的事務(wù)是有差異的(有點(diǎn)類似AOP)桶略,雖然都叫transaction语淘,并沒有commit诲宇,而是自動(dòng)執(zhí)行,初始方法沒有提供rollback惶翻,有二次封裝提供的(ReactReconcileTransaction.js)
- 下文會(huì)提到事務(wù)機(jī)制的具體使用場景
事件分發(fā)
- 框圖(ReactBrowserEventEmitter.js)
- 組件上聲明的事件最終綁定到了 document 上姑蓝,而不是 React 組件對應(yīng)的 DOM 節(jié)點(diǎn),這樣簡化了 DOM 原生事件吕粗,減少了內(nèi)存開銷
- 以隊(duì)列的方式纺荧,從觸發(fā)事件的組件向父組件回溯,調(diào)用相應(yīng) callback颅筋,也就是 React 自身實(shí)現(xiàn)了一套事件冒泡機(jī)制宙暇,雖然 React 對合成事件封裝了stopPropagation,但是并不能阻止自己手動(dòng)綁定的原生事件的冒泡垃沦,所以項(xiàng)目中要避免手動(dòng)綁定原生事件
- 使用對象池來管理合成事件對象的創(chuàng)建和銷毀客给,好處在上文中有描述
- ReactEventListener:負(fù)責(zé)事件注冊和事件分發(fā)
- ReactEventEmitter:負(fù)責(zé)事件執(zhí)行
- EventPluginHub:負(fù)責(zé)事件的存儲(chǔ)用押,具體存儲(chǔ)在listenerBank
- Plugin: 根據(jù)不同的事件類型肢簿,構(gòu)造不同的合成事件,可以連接原生事件和組件
- 當(dāng)事件觸發(fā)時(shí)蜻拨,會(huì)調(diào)用ReactEventListener.dispatchEvent池充,進(jìn)行分發(fā):找到具體的 ReactComponent,然后向上遍歷父組件缎讼,實(shí)現(xiàn)冒泡
- 代碼較多收夸,就不具體分析了,這種統(tǒng)一收集然后分發(fā)的思路血崭,可以用在具體項(xiàng)目中
生命周期
- 整體流程:
- 主要講述mount和update卧惜,里面也有很多相類似的操作
- componentWillMount,render夹纫,componentDidMount 都是在 mountComponent 中被調(diào)用
- 分析 ReactCompositeComponent.js 中的mountComponent咽瓷,發(fā)現(xiàn)輸出是@return {?string} Rendered markup to be inserted into the DOM.
mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
var _this = this;
this._context = context;
this._mountOrder = nextMountID++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;
var publicProps = this._currentElement.props;
var publicContext = this._processContext(context);
var Component = this._currentElement.type;
var updateQueue = transaction.getUpdateQueue();
// Initialize the public class
var doConstruct = shouldConstruct(Component);
// 最終會(huì)調(diào)用 new Component()
var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
var renderedElement;
// Support functional components
if (!doConstruct && (inst == null || inst.render == null)) {
renderedElement = inst;
inst = new StatelessComponent(Component);
this._compositeType = CompositeTypes.StatelessFunctional;
} else {
// 大家經(jīng)常在用戶端用到的 PureComponent,會(huì)對 state 進(jìn)行淺比較然后決定是否執(zhí)行 render
if (isPureComponent(Component)) {
this._compositeType = CompositeTypes.PureClass;
} else {
this._compositeType = CompositeTypes.ImpureClass;
}
}
// These should be set up in the constructor, but as a convenience for
// simpler class abstractions, we set them up after the fact.
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = updateQueue;
this._instance = inst;
// Store a reference from the instance back to the internal representation
// 以 element 為 key舰讹,存在了 Map 中茅姜,之后會(huì)用到
ReactInstanceMap.set(inst, this);
var initialState = inst.state;
if (initialState === undefined) {
inst.state = initialState = null;
}
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
var markup;
if (inst.unstable_handleError) {
markup = this.performInitialMountWithErrorHandling(renderedElement, hostParent, hostContainerInfo, transaction, context);
} else {
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
}
if (inst.componentDidMount) {
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
return markup;
}
function shouldConstruct(Component) {
return !!(Component.prototype && Component.prototype.isReactComponent);
}
- 可以看到,mountComponet 先做實(shí)例對象的初始化(props, state 等)月匣,然后調(diào)用performInitialMount掛載(performInitialMountWithErrorHandling最終也會(huì)調(diào)用performInitialMount钻洒,只是多了錯(cuò)誤處理),然后調(diào)用componentDidMount
- transaction.getReactMountReady()會(huì)得到CallbackQueue锄开,所以只是加入到隊(duì)列中素标,后續(xù)執(zhí)行
- 我們來看performInitialMount(依然在 ReactCompositeComponent.js 中)
performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) {
var inst = this._instance;
var debugID = 0;
if (inst.componentWillMount) {
inst.componentWillMount();
// When mounting, calls to `setState` by `componentWillMount` will set
// `this._pendingStateQueue` without triggering a re-render.
if (this._pendingStateQueue) {
inst.state = this._processPendingState(inst.props, inst.context);
}
}
// If not a stateless component, we now render
// 返回 ReactElement,這也就是上文說的 render 返回 ReactElement
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
var nodeType = ReactNodeTypes.getType(renderedElement);
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY);
this._renderedComponent = child;
var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);
return markup;
}
- performInitialMount 中先調(diào)用componentWillMount萍悴,這個(gè)過程中 merge state糯钙,然后調(diào)用_renderValidatedComponent(最終會(huì)調(diào)用inst.render() )返回 ReactElement粪狼,然后調(diào)用_instantiateReactComponent 由 ReactElement 創(chuàng)建 ReactComponent,最后進(jìn)行遞歸渲染任岸。
- 掛載之后再榄,可以通過setState來更新(機(jī)制較為復(fù)雜,后文會(huì)單獨(dú)分析)享潜,此過程通過調(diào)用updateComponent來完成更新困鸥。我們來看updateComponent(依然在 ReactCompositeComponent.js 中)
updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
var inst = this._instance;
var willReceive = false;
var nextContext;
// context 相關(guān),React 建議少用 context
// Determine if the context has changed or not
if (this._context === nextUnmaskedContext) {
nextContext = inst.context;
} else {
nextContext = this._processContext(nextUnmaskedContext);
willReceive = true;
}
var prevProps = prevParentElement.props;
var nextProps = nextParentElement.props;
// Not a simple state update but a props update
if (prevParentElement !== nextParentElement) {
willReceive = true;
}
// An update here will schedule an update but immediately set
// _pendingStateQueue which will ensure that any state updates gets
// immediately reconciled instead of waiting for the next batch.
if (willReceive && inst.componentWillReceiveProps) {
inst.componentWillReceiveProps(nextProps, nextContext);
}
var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate = true;
if (!this._pendingForceUpdate) {
if (inst.shouldComponentUpdate) {
shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
} else {
if (this._compositeType === CompositeTypes.PureClass) {
// 這里剑按,就是上文提到的疾就,PureComponent 里的淺比較
shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
}
}
}
this._updateBatchNumber = null;
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);
} else {
// If it's determined that a component should not update, we still want
// to set props and state but we shortcut the rest of the update.
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
}
}
- updateComponent中,先調(diào)用componentWillReceiveProps艺蝴,然后 merge state猬腰,然后調(diào)用shouldComponentUpdate判斷是否需要更新,可以看到猜敢,如果組件內(nèi)部沒有自定義姑荷,且用的是 PureComponent,會(huì)對 state 進(jìn)行淺比較缩擂,設(shè)置shouldUpdate鼠冕,最終調(diào)用_performComponentUpdate來進(jìn)行更新。而在_performComponentUpdate中胯盯,會(huì)先調(diào)用componentWillUpdate懈费,然后調(diào)用updateRenderedComponent進(jìn)行更新,最后調(diào)用componentDidUpdate(過程較簡單博脑,就不列代碼了)憎乙。下面看一下updateRenderedComponent的更新機(jī)制(依然在 ReactCompositeComponent.js 中)
_updateRenderedComponent: function (transaction, context) {
var prevComponentInstance = this._renderedComponent;
var prevRenderedElement = prevComponentInstance._currentElement;
var nextRenderedElement = this._renderValidatedComponent();
var debugID = 0;
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
ReactReconciler.receiveComponent(prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context));
} else {
var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
ReactReconciler.unmountComponent(prevComponentInstance, false);
var nodeType = ReactNodeTypes.getType(nextRenderedElement);
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(nextRenderedElement, nodeType !== ReactNodeTypes.EMPTY);
this._renderedComponent = child;
var nextMarkup = ReactReconciler.mountComponent(child, transaction, this._hostParent, this._hostContainerInfo, this._processChildContext(context), debugID);
this._replaceNodeWithMarkup(oldHostNode, nextMarkup, prevComponentInstance);
}
},
可以看到,如果需要更新叉趣,則調(diào)用ReactReconciler.receiveComponent泞边,會(huì)遞歸更新子組件,否則直接卸載然后掛載君账。所以繁堡,重點(diǎn)是在shouldUpdateReactComponent的判斷,React 為了簡化 diff乡数,所以有一個(gè)假設(shè):在組件層級椭蹄、type、key 不變的時(shí)候净赴,才進(jìn)行比較更新绳矩,否則先 unmount 然后重新 mount。來看shouldUpdateReactComponent(shouldUpdateReactComponent.js) :
function shouldUpdateReactComponent(prevElement, nextElement) {
var prevEmpty = prevElement === null || prevElement === false;
var nextEmpty = nextElement === null || nextElement === false;
if (prevEmpty || nextEmpty) {
return prevEmpty === nextEmpty;
}
var prevType = typeof prevElement;
var nextType = typeof nextElement;
// 如果前后兩次都為文本元素玖翅,則更新
if (prevType === 'string' || prevType === 'number') {
return nextType === 'string' || nextType === 'number';
} else {
// 如果為 ReactDomComponent 或 ReactCompositeComponent翼馆,則需要層級 type 和 key 相同割以,才進(jìn)行 update(層級在遞歸中保證相同)
return nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key;
}
}
接下來是重頭戲:setState,上文中已經(jīng)提到了此 api 為:
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
可以看到這里只是簡單的調(diào)用enqueueSetState放入隊(duì)列中应媚,而我們知道严沥,不可能這么簡單的。來看enqueueSetState(ReactUpdateQueue.js中)中姜,this.updater會(huì)在 mount 時(shí)候賦值為updateQueue
enqueueSetState: function (publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
// 獲取隊(duì)列消玄,如果為空則創(chuàng)建
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
// 將待 merge 的 state 放入隊(duì)列
queue.push(partialState);
// 將待更新的組件放入隊(duì)列
enqueueUpdate(internalInstance);
},
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
// 上文提到的以 element 為 key 存入 map,這里可以取到 component
var internalInstance = ReactInstanceMap.get(publicInstance);
if (!internalInstance) {
return null;
}
return internalInstance;
}
再來看enqueueUpdate(ReactUpdates.js):
function enqueueUpdate(component) {
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
- 可以看到丢胚,如果不處于isBatchingUpdates時(shí)翩瓜,則調(diào)用batchingStrategy.batchedUpdates,如果處于的話携龟,則將 component 放入 dirtyComponents 中等待以后處理兔跌。這樣保證了避免重復(fù) render,因?yàn)閙ountComponent和updateComponent 執(zhí)行的開始峡蟋,會(huì)將isBatchingUpdates 設(shè)置為true坟桅,之后以事務(wù)的方式處理,包括最后時(shí)候?qū)sBatchingUpdates置為false层亿。
- 大家一定對 batchingStrategy 和 dirtyComponents 的定義桦卒,batchingStrategy由ReactUpdates.injection 注入立美,而dirtyComponents 是定義在 ReactUpdates.js 中匿又,也就是說二者都為全局的
- 綜上,在特定生命周期中建蹄,如 getInitialState碌更,componentWillMount,render洞慎,componentWillUpdate 中調(diào)用setState痛单,并不會(huì)引起updateComponent(componentDidMount、componentDidUpdate 中會(huì))劲腿。來看batchedUpdates(ReactDefaultBatchingStrategy.js):
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
// 注意這里旭绒,上一個(gè)代碼塊中可以看到,當(dāng) isBatchingUpdates 為 false 時(shí)焦人,callback 為 enqueueUpdate 自身
// 所以即以事務(wù)的方式處理
return transaction.perform(callback, null, a, b, c, d, e);
}
}
var transaction = new ReactDefaultBatchingStrategyTransaction();
- 可以看到挥吵,當(dāng)以事務(wù)的方式調(diào)用進(jìn)入enqueueUpdate時(shí),isBatchingUpdates已經(jīng)為true花椭,所以執(zhí)行dirtyComponents.push(component);忽匈。
- 注意到callbakc其實(shí)就是自身enqueueUpdate,當(dāng)isBatchingUpdates為false時(shí)矿辽,也用transaction.perform調(diào)用enqueueUpdate丹允,使得結(jié)果一樣
- 詳細(xì)介紹事務(wù) transaction 的應(yīng)用郭厌,上文中提到過,事務(wù)可以利用wrapper封裝雕蔽,開始和結(jié)束時(shí)會(huì)調(diào)用所有 wrapper 的相應(yīng)方法折柠,來看這兩個(gè)wrapper: RESET_BATCHED_UPDATES FLUSH_BATCHED_UPDATES(ReactDefaultBatchingStrategy.js):
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
// flushBatchedUpdates 在 ReactUpdates.js 中
var flushBatchedUpdates = function () {
// ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
// asapEnqueued 為提前執(zhí)行回調(diào),暫不分析
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
}
}
};
- 但是批狐,仔細(xì)看上面的過程液走,把組件放入 dirtyComponents 后,事務(wù)結(jié)束馬上就執(zhí)行 close 方法進(jìn)行了處理了贾陷,和之前理解的流程好像不太一致缘眶?這時(shí)候再回頭看mountComponent和updateComponent,它們的參數(shù):@param {ReactReconcileTransaction} transaction髓废,也就是說整個(gè)過程都在ReactReconcileTransaction事務(wù)中(事件回調(diào)同理)巷懈,自然在其中的生命周期調(diào)用setState不用引起重復(fù) render,只會(huì)將 state 放入隊(duì)列和將組件放入 dirtyComponents 中慌洪,然后在結(jié)束后統(tǒng)一處理
- ReactReconcileTransaction中 initialize 用于清空回調(diào)隊(duì)列顶燕;close 用于觸發(fā)回調(diào)函數(shù) componentDidMount、componentDidUpdate 執(zhí)行
- 我開始一直比較疑惑的是ReactDefaultBatchingStrategy.batchedUpdates中的ReactDefaultBatchingStrategyTransaction和ReactReconcileTransaction到底是什么關(guān)系冈爹?我試圖找出兩個(gè) transaction 中 wrapper 是否有 merge 的情況涌攻,發(fā)現(xiàn)沒有。目前大概的理解和結(jié)論是這樣的:整個(gè)生命周期就是一個(gè) transaction频伤,即對應(yīng)ReactDefaultBatchingStrategy.batchedUpdates恳谎,而ReactReconcileTransaction粒度較小,負(fù)責(zé)單個(gè)組件(所以也能看到憋肖,前者直接 new因痛,而后者利用了對象池)。通過各自 wrapper 可以看到岸更,前者([FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES])負(fù)責(zé)了全部組件更新 和 callback鸵膏,后者([SELECTION_RESTORATION, EVENT_SUPPRESSION, ON_DOM_READY_QUEUEING)負(fù)責(zé)了各自組件自身的問題,如 focus 等怎炊。
- 例證:ReactDom 中調(diào)用render(插入過程)谭企,實(shí)際最終調(diào)用了 ReactMount 的_renderNewRootComponent,其中執(zhí)行了ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);(注意出現(xiàn)了batchedUpdates)评肆,而batchedMountComponentIntoNode中調(diào)用了ReactUpdates.ReactReconcileTransaction.getPooled债查,這樣,嵌套關(guān)系就聯(lián)系起來了
- 例證: ReactEventListener 的dispatchEvent糟港,會(huì)調(diào)用ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping); 和上述同理
- 熟悉 React 生命周期的同學(xué)一定對父子組件各生命周期的執(zhí)行順序很清晰(比如 componentWillMount 是從父到子)攀操,以上述的理論,是如何保證的么秸抚?上文中可以看到速和,F(xiàn)LUSH_BATCHED_UPDATES的 close方法利調(diào)用了runBatchedUpdates歹垫,來看這個(gè)方法(ReactUpdates.js):
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
// reconcile them before their children by sorting the array.
dirtyComponents.sort(mountOrderComparator);
// Any updates enqueued while reconciling must be performed after this entire
// batch. Otherwise, if dirtyComponents is [A, B] where A has children B and
// C, B could update twice in a single batch if C's render enqueues an update
// to B (since B would have already updated, we should skip it, and the only
// way we can know to do so is by checking the batch counter).
updateBatchNumber++;
for (var i = 0; i < len; i++) {
// If a component is unmounted before pending changes apply, it will still
// be here, but we assume that it has cleared its _pendingCallbacks and
// that was is a noop.
var component = dirtyComponents[i];
// If performUpdateIfNecessary happens to enqueue any new updates, we
// shouldn't execute the callbacks until the next render happens, so
// stash the callbacks first
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
}
}
}
}
function mountOrderComparator(c1, c2) {
return c1._mountOrder - c2._mountOrder;
}
- flushBatchedUpdates在事務(wù)ReactUpdatesFlushTransaction中,此事務(wù)是對ReactReconcileTransaction和CallbackQueue的封裝颠放,結(jié)束時(shí)置空 dirtyComponents 并通知回調(diào)
- performUpdateIfNecessary最終會(huì)調(diào)用updateComponent排惨,進(jìn)行更新
diff 算法
傳統(tǒng)對于樹的 diff 算法,時(shí)間復(fù)雜度要達(dá)到 o(n^3)碰凶,這對于用戶端顯然是不能接受的暮芭。而 React 基于幾個(gè)基礎(chǔ)假設(shè),將時(shí)間復(fù)雜度優(yōu)化為 o(n)
假設(shè)(策略)
Web UI 中 DOM 節(jié)點(diǎn)跨層級的移動(dòng)操作特別少欲低,可以忽略不計(jì)
擁有相同類的兩個(gè)組件將會(huì)生成相似的樹形結(jié)構(gòu)辕宏,擁有不同類的兩個(gè)組件將會(huì)生成不同的樹形結(jié)構(gòu)
對于同一層級的一組子節(jié)點(diǎn),它們可以通過唯一 id 進(jìn)行區(qū)分
場景
tree diff: 只對比同層級節(jié)點(diǎn)(注意前文中所有代碼中砾莱,都是只比較prevRenderedElement和nextRenderedElement)
component diff: 如果類型相同則繼續(xù)比較瑞筐,如果類型不同則直接卸載再掛載,即上文中提到的shouldUpdateReactComponent(雖然當(dāng)兩個(gè) component 是不同類型但結(jié)構(gòu)相似時(shí)腊瑟,React diff 會(huì)影響性能聚假,但正如 React 官方博客所言:不同類型的 component 是很少存在相似 DOM tree 的機(jī)會(huì),因此為這種極端情況而做太多比較是不值得的)
element diff: 當(dāng)一組節(jié)點(diǎn)處于同一層級時(shí)闰非,React 對于每個(gè)節(jié)點(diǎn)提供了三種操作膘格,分別為INSERT_MARKUP(插入)、 MOVE_EXISTING(移動(dòng))财松、 REMOVE_NODE(刪除)
上文的代碼中瘪贱,除了關(guān)心 type,還關(guān)心 key游岳,這也是 diff 算法的關(guān)鍵政敢,如圖
首先對新集合的節(jié)點(diǎn)進(jìn)行循環(huán)遍歷其徙,for (name in nextChildren)胚迫,如果存在相同節(jié)點(diǎn),則進(jìn)行操作唾那,是否移動(dòng)是通過比較 child._mountIndex < lastIndex访锻,符合則進(jìn)行節(jié)點(diǎn)移動(dòng)操作(即在老集合中的位置和 lastIndex 比較),lastIndex 表示訪問過的節(jié)點(diǎn)在老集合中最右的位置(即最大的位置)闹获。這是一種順序優(yōu)化手段期犬,lastIndex 一直在更新,表示訪問過的節(jié)點(diǎn)在老集合中最右的位置避诽,如果新集合中當(dāng)前訪問的節(jié)點(diǎn)比 lastIndex 大龟虎,說明當(dāng)前訪問節(jié)點(diǎn)在老集合中就比上一個(gè)節(jié)點(diǎn)位置靠后,則該節(jié)點(diǎn)不會(huì)影響其他節(jié)點(diǎn)的位置沙庐,因此不用添加到差異隊(duì)列中鲤妥,即不執(zhí)行移動(dòng)操作佳吞,只有當(dāng)訪問的節(jié)點(diǎn)比 lastIndex 小時(shí),才需要進(jìn)行移動(dòng)操作棉安。來看具體過程:
從新集合中取得 B底扳,判斷老集合中存在相同節(jié)點(diǎn) B,通過對比節(jié)點(diǎn)位置判斷是否進(jìn)行移動(dòng)操作贡耽,B 在老集合中的位置 B._mountIndex = 1衷模,此時(shí) lastIndex = 0,不滿足 child._mountIndex < lastIndex 的條件蒲赂,因此不對 B 進(jìn)行移動(dòng)操作阱冶;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),其中 prevChild._mountIndex 表示 B 在老集合中的位置滥嘴,則 lastIndex = 1熙揍,并將 B 的位置更新為新集合中的位置prevChild._mountIndex = nextIndex,此時(shí)新集合中 B._mountIndex = 0氏涩,nextIndex++ 進(jìn)入下一個(gè)節(jié)點(diǎn)的判斷
從新集合中取得 A届囚,判斷老集合中存在相同節(jié)點(diǎn) A,通過對比節(jié)點(diǎn)位置判斷是否進(jìn)行移動(dòng)操作是尖,A 在老集合中的位置 A._mountIndex = 0意系,此時(shí) lastIndex = 1,滿足 child._mountIndex < lastIndex的條件饺汹,因此對 A 進(jìn)行移動(dòng)操作 enqueueMove(this, child._mountIndex, toIndex)蛔添,其中 toIndex 其實(shí)就是 nextIndex,表示 A 需要移動(dòng)到的位置兜辞;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex)迎瞧,則 lastIndex = 1,并將 A 的位置更新為新集合中的位置 prevChild._mountIndex = nextIndex逸吵,此時(shí)新集合中A._mountIndex = 1凶硅,nextIndex++ 進(jìn)入下一個(gè)節(jié)點(diǎn)的判斷。
從新集合中取得 D扫皱,判斷老集合中存在相同節(jié)點(diǎn) D足绅,通過對比節(jié)點(diǎn)位置判斷是否進(jìn)行移動(dòng)操作,D 在老集合中的位置 D._mountIndex = 3韩脑,此時(shí) lastIndex = 1氢妈,不滿足 child._mountIndex < lastIndex的條件,因此不對 D 進(jìn)行移動(dòng)操作段多;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex)首量,則 lastIndex = 3,并將 D 的位置更新為新集合中的位置 prevChild._mountIndex = nextIndex,此時(shí)新集合中D._mountIndex = 2加缘,nextIndex++ 進(jìn)入下一個(gè)節(jié)點(diǎn)的判斷粥航。
從新集合中取得 C,判斷老集合中存在相同節(jié)點(diǎn) C生百,通過對比節(jié)點(diǎn)位置判斷是否進(jìn)行移動(dòng)操作递雀,C 在老集合中的位置 C._mountIndex = 2,此時(shí) lastIndex = 3蚀浆,滿足 child._mountIndex < lastIndex 的條件缀程,因此對 C 進(jìn)行移動(dòng)操作 enqueueMove(this, child._mountIndex, toIndex);更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex)市俊,則 lastIndex = 3杨凑,并將 C 的位置更新為新集合中的位置 prevChild._mountIndex = nextIndex,此時(shí)新集合中 C._mountIndex = 3摆昧,nextIndex++ 進(jìn)入下一個(gè)節(jié)點(diǎn)的判斷撩满,由于 C 已經(jīng)是最后一個(gè)節(jié)點(diǎn),因此 diff 到此完成绅你。
當(dāng)有新的 Component 插入時(shí)伺帘,邏輯一致,不做具體分析了
當(dāng)完成集合中所有節(jié)點(diǎn) diff忌锯,還需要遍歷老集合伪嫁,如果存在新集合中沒有但老集合中有的節(jié)點(diǎn),則刪除
代碼(ReactMultiChild.js)偶垮,針對 element diff(tree diff 和 component diff 在之前的代碼中已經(jīng)提到過):
_updateChildren: function (nextNestedChildrenElements, transaction, context) {
var prevChildren = this._renderedChildren;
var removedNodes = {};
var mountImages = [];
var nextChildren = this._reconcilerUpdateChildren(prevChildren, nextNestedChildrenElements, mountImages, removedNodes, transaction, context);
if (!nextChildren && !prevChildren) {
return;
}
var updates = null;
var name;
// `nextIndex` will increment for each child in `nextChildren`, but
// `lastIndex` will be the last index visited in `prevChildren`.
var nextIndex = 0;
var lastIndex = 0;
// `nextMountIndex` will increment for each newly mounted child.
var nextMountIndex = 0;
var lastPlacedNode = null;
for (name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {
continue;
}
var prevChild = prevChildren && prevChildren[name];
var nextChild = nextChildren[name];
if (prevChild === nextChild) {
updates = enqueue(updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex));
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
prevChild._mountIndex = nextIndex;
} else {
if (prevChild) {
// Update `lastIndex` before `_mountIndex` gets unset by unmounting.
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
// The `removedNodes` loop below will actually remove the child.
}
// The child must be instantiated before it's mounted.
updates = enqueue(updates, this._mountChildAtIndex(nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context));
nextMountIndex++;
}
nextIndex++;
lastPlacedNode = ReactReconciler.getHostNode(nextChild);
}
// Remove children that are no longer present.
for (name in removedNodes) {
if (removedNodes.hasOwnProperty(name)) {
updates = enqueue(updates, this._unmountChild(prevChildren[name], removedNodes[name]));
}
}
if (updates) {
processQueue(this, updates);
}
this._renderedChildren = nextChildren;
},
綜上张咳,在開發(fā)中,保持穩(wěn)定的結(jié)構(gòu)有助于性能提升似舵,當(dāng)有一組節(jié)點(diǎn)時(shí)脚猾,除了要設(shè)置 key,也要避免將靠后的節(jié)點(diǎn)移動(dòng)到靠前的位置
一些其他的點(diǎn)
interface(ReactClass.js)
var ReactClassInterface = {
mixins: 'DEFINE_MANY',
statics: 'DEFINE_MANY',
propTypes: 'DEFINE_MANY',
contextTypes: 'DEFINE_MANY',
childContextTypes: 'DEFINE_MANY',
// ==== Definition methods ====
getDefaultProps: 'DEFINE_MANY_MERGED',
getInitialState: 'DEFINE_MANY_MERGED',
getChildContext: 'DEFINE_MANY_MERGED',
render: 'DEFINE_ONCE',
// ==== Delegate methods ====
componentWillMount: 'DEFINE_MANY',
componentDidMount: 'DEFINE_MANY',
componentWillReceiveProps: 'DEFINE_MANY',
shouldComponentUpdate: 'DEFINE_ONCE',
componentWillUpdate: 'DEFINE_MANY',
componentDidUpdate: 'DEFINE_MANY',
componentWillUnmount: 'DEFINE_MANY',
// ==== Advanced methods ====
updateComponent: 'OVERRIDE_BASE'
};
function validateMethodOverride(isAlreadyDefined, name) {
var specPolicy = ReactClassInterface.hasOwnProperty(name) ? ReactClassInterface[name] : null;
// Disallow overriding of base class methods unless explicitly allowed.
if (ReactClassMixin.hasOwnProperty(name)) {
!(specPolicy === 'OVERRIDE_BASE') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to override `%s` from your class specification. Ensure that your method names do not overlap with React methods.', name) : _prodInvariant('73', name) : void 0;
}
// Disallow defining methods more than once unless explicitly allowed.
if (isAlreadyDefined) {
!(specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to define `%s` on your component more than once. This conflict may be due to a mixin.', name) : _prodInvariant('74', name) : void 0;
}
}
可以看到砚哗,和后端中interface(或是抽象類)還是有區(qū)別的龙助,但是可以起到規(guī)范和檢查的作用,實(shí)際項(xiàng)目中可以借鑒