React源码解析
前言
基础概念ReactElement
ReactComponent
代码(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;
}
ReactClass这个比较特殊,对比 ES5 写法: 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,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,'forceUpdate');
}
};
对象池
代码(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);
事务机制
示意图(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,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;
},// 执行所有 wrapper 中的 initialize 方法
initializeAll: function (startIndex) {
},// 执行所有 wrapper 中的 close 方法
closeAll: function (startIndex) {
}
};
module.exports = TransactionImpl;
可以看到和后端的事务是有差异的(有点类似AOP),虽然都叫 事件分发
生命周期整体流程:
分析 ReactCompositeComponent.js 中的mountComponent,发现输出是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);
// 最终会调用 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 {
// 大家经常在用户端用到的 PureComponent,会对 state 进行浅比较然后决定是否执行 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 中,之后会用到
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,transaction,context);
} else {
markup = this.performInitialMount(renderedElement,context);
}
if (inst.componentDidMount) {
transaction.getReactMountReady().enqueue(inst.componentDidMount,inst);
}
return markup;
}
function shouldConstruct(Component) {
return !!(Component.prototype && Component.prototype.isReactComponent);
}
可以看到,mountComponet 先做实例对象的初始化(props,state 等),然后调用performInitialMount挂载(performInitialMountWithErrorHandling最终也会调用performInitialMount,只是多了错误处理),然后调用componentDidMount
transaction.getReactMountReady()会得到CallbackQueue,所以只是加入到队列中,后续执行
我们来看performInitialMount(依然在 ReactCompositeComponent.js 中)
performInitialMount: function (renderedElement,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,this._processChildContext(context),debugID);
return markup;
}
updateComponent: function (transaction,prevParentElement,nextParentElement,prevUnmaskedContext,nextUnmaskedContext) {
var inst = this._instance;
var willReceive = false;
var nextContext;
// context 相关,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,nextContext,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;
}
}
_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,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,this._hostParent,this._hostContainerInfo,debugID);
this._replaceNodeWithMarkup(oldHostNode,nextMarkup,prevComponentInstance);
}
},
可以看到,如果需要更新,则调用 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 相同,才进行 update(层级在递归中保证相同)
return nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key;
}
}
接下来是重头戏: ReactComponent.prototype.setState = function (partialState,'setState');
}
};
可以看到这里只是简单的调用enqueueSetState放入队列中,而我们知道,不可能这么简单的。来看enqueueSetState(ReactUpdateQueue.js中),this.updater会在 mount 时候赋值为 enqueueSetState: function (publicInstance,partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance,'setState');
if (!internalInstance) {
return;
}
// 获取队列,如果为空则创建
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
// 将待 merge 的 state 放入队列
queue.push(partialState);
// 将待更新的组件放入队列
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;
}
}
batchedUpdates: function (callback,e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
return callback(a,e);
} else {
// 注意这里,上一个代码块中可以看到,当 isBatchingUpdates 为 false 时,callback 为 enqueueUpdate 自身
// 所以即以事务的方式处理
return transaction.perform(callback,null,e);
}
}
var transaction = new ReactDefaultBatchingStrategyTransaction();
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 为提前执行回调,暂不分析
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(,transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
}
}
};
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;
}
diff 算法
场景
代码(ReactMultiChild.js),针对 element diff(tree diff 和 component diff 在之前的代码中已经提到过): _updateChildren: function (nextNestedChildrenElements,context) {
var prevChildren = this._renderedChildren;
var removedNodes = {};
var mountImages = [];
var nextChildren = this._reconcilerUpdateChildren(prevChildren,nextNestedChildrenElements,mountImages,removedNodes,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],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;
},
综上,在开发中,保持稳定的结构有助于性能提升,当有一组节点时,除了要设置 key,也要避免将靠后的节点移动到靠前的位置 一些其他的点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;
}
}
可以看到,和后端中 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |