>  기사  >  웹 프론트엔드  >  React의 setState 소스 코드에 대한 자세한 설명

React의 setState 소스 코드에 대한 자세한 설명

小云云
小云云원래의
2018-01-18 11:41:001576검색

이 글은 주로 React의 setState 소스 코드에 대한 심층적인 연구를 소개합니다. 편집자는 꽤 좋다고 생각하므로 지금 공유하고 참고용으로 제공하겠습니다. 편집자를 따라 살펴보겠습니다. 모두에게 도움이 되기를 바랍니다.

프론트엔드 프레임워크인 React는 MVVM의 View 부분에만 중점을 두지만 여전히 View와 모델의 바인딩을 구현합니다. 데이터를 수정하는 동안 보기를 새로 고칠 수 있습니다. 이는 우리의 논리를 크게 단순화하고 데이터 흐름의 변경에만 관심을 가지면 되며 코드의 양도 줄어들고 나중에 유지 관리가 더 편리해집니다. 이 기능은 setState() 메소드 덕분입니다. React는 대기열 메커니즘을 사용하여 상태를 관리하므로 반복되는 View 새로 고침을 방지합니다. 소스 코드 관점에서 setState 메커니즘을 살펴보겠습니다.

1 무엇이 전달되고, 어떤 객체인지, React의 컨텍스트 업데이터가 어떻게 전달되는지는 아래

react 소스 코드 분석 시리즈 기사를 참조하세요.


여기서 직접 결과에 대해 이야기해 보겠습니다. 실제로 ReactUpdateQueue.js에 노출된 ReactUpdateQueue 객체입니다.

2 이제 setState 이후에 실행되는 작업을 찾았으므로 단계별로 진행하겠습니다

class App extends Component {
  //只在组件重新加载的时候执行一次
  constructor(props) {
    super(props);
   //..
  }
   //other methods
}
//ReactBaseClasses.js中如下:这里就是setState函数的来源;
//super其实就是下面这个函数
function ReactComponent(props, context, updater) {
 this.props = props;
 this.context = context;
 this.refs = emptyObject;
 // We initialize the default updater but the real one gets injected by the
 // renderer.
 this.updater = updater || ReactNoopUpdateQueue;
}
ReactComponent.prototype.setState = function (partialState, callback) {
 this.updater.enqueueSetState(this, partialState);
 if (callback) {
  this.updater.enqueueCallback(this, callback, 'setState');
 }
};

ReactUpdateQueue.js


class Root extends React.Component {
 constructor(props) {
  super(props);
  this.state = {
   count: 0
  };
 }
 componentDidMount() {
  let me = this;
  me.setState({
   count: me.state.count + 1
  });
  console.log(me.state.count);  // 打印出0
  me.setState({
   count: me.state.count + 1
  });
  console.log(me.state.count);  // 打印出0
  setTimeout(function(){
   me.setState({
    count: me.state.count + 1
   });
   console.log(me.state.count);  // 打印出2
  }, 0);
  setTimeout(function(){
   me.setState({
    count: me.state.count + 1
   });
   console.log(me.state.count);  // 打印出3
  }, 0);
 }
 render() {
  return (
   <h1>{this.state.count}</h1>
  )
 }
}

ReactComponent.prototype.setState = function (partialState, callback) {
 this.updater.enqueueSetState(this, partialState);
 if (callback) {
  this.updater.enqueueCallback(this, callback, &#39;setState&#39;);
 }
};

enqueueSetState enqueueCallback은 결국 enqueueUpdate;


var ReactUpdates = require(&#39;./ReactUpdates&#39;);

function enqueueUpdate(internalInstance) {
 ReactUpdates.enqueueUpdate(internalInstance);
};
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
 //在ReactCompositeComponent.js中有这样一行代码,这就是其来源;
 // Store a reference from the instance back to the internal representation
  //ReactInstanceMap.set(inst, this);
 var internalInstance = ReactInstanceMap.get(publicInstance);
 //返回的是在ReactCompositeComponent.js中construct函数返回的对象;ReactInstance实例对象并不是简单的new 我们写的组件的实例对象,而是经过instantiateReactComponent.js中ReactCompositeComponentWrapper函数包装的对象;详见 创建React组件方式以及源码分析.md
 return internalInstance;
};
var ReactUpdateQueue = {
//。。。。省略其他代码
 enqueueCallback: function (publicInstance, callback, callerName) {
  ReactUpdateQueue.validateCallback(callback, callerName);
  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
  if (!internalInstance) {
   return null;
  }
//这里将callback放入组件实例的_pendingCallbacks数组中;
  if (internalInstance._pendingCallbacks) {
   internalInstance._pendingCallbacks.push(callback);
  } else {
   internalInstance._pendingCallbacks = [callback];
  }
  // TODO: The callback here is ignored when setState is called from
  // componentWillMount. Either fix it or disallow doing so completely in
  // favor of getInitialState. Alternatively, we can disallow
  // componentWillMount during server-side rendering.
  enqueueUpdate(internalInstance);
 },

 enqueueSetState: function (publicInstance, partialState) {
  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, &#39;setState&#39;);
  if (!internalInstance) {
   return;
  }
  //这里,初始化queue变量,同时初始化 internalInstance._pendingStateQueue = [ ] ;
  //对于 || 的短路运算还是要多梳理下
  //queue数组(模拟队列)中存放着setState放进来的对象;
  var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
  //这里将partialState放入queue数组中,也就是internalInstance._pendingStateQueue 数组中,此时,每次setState的partialState,都放进了React组件实例对象上的_pendingStateQueue属性中,成为一个数组;
  queue.push(partialState);

  enqueueUpdate(internalInstance);
 },
};

module.exports = ReactUpdateQueue;

ReactUpdates.js


function enqueueUpdate(internalInstance) {
 ReactUpdates.enqueueUpdate(internalInstance);
}

ReactDefaultBatchingStrategy.js


var dirtyComponents = [];
var updateBatchNumber = 0;
var asapCallbackQueue = CallbackQueue.getPooled();
var asapEnqueued = false;
//这里声明batchingStrategy为null,后期通过注册给其赋值;
var batchingStrategy = null;
//这里的component参数是js中ReactCompositeComponentWrapper函数包装的后的React组件实例对象;
function enqueueUpdate(component) {
 ensureInjected();
//第一次执行setState的时候,可以进入if语句,遇到里面的return语句,终止执行
 //如果不是正处于创建或更新组件阶段,则处理update事务

 if (!batchingStrategy.isBatchingUpdates) {
  //batchedUpdates就是ReactDefaultBatchingStrategy.js中声明的
  batchingStrategy.batchedUpdates(enqueueUpdate, component);
  return;
 }
//第二次执行setState的时候,进入不了if语句,将组件放入dirtyComponents
 //如果正在创建或更新组件,则暂且先不处理update,只是将组件放在dirtyComponents数组中

 dirtyComponents.push(component);
 if (component._updateBatchNumber == null) {
  component._updateBatchNumber = updateBatchNumber + 1;
 }
};
//enqueueUpdate包含了React避免重复render的逻辑。mountComponent和updateComponent方法在执行的最开始,会调用到batchedUpdates进行批处理更新,此时会将isBatchingUpdates设置为true,也就是将状态标记为现在正处于更新阶段了。之后React以事务的方式处理组件update,事务处理完后会调用wrapper.close(), 而TRANSACTION_WRAPPERS中包含了RESET_BATCHED_UPDATES这个wrapper,故最终会调用RESET_BATCHED_UPDATES.close(), 它最终会将isBatchingUpdates设置为false。

다음으로 트랜잭션 처리 메커니즘을 살펴보겠습니다. 반응이 작동합니다.

트랜잭션. js


//RESET_BATCHED_UPDATES用来管理isBatchingUpdates的状态
var RESET_BATCHED_UPDATES = {
 initialize: emptyFunction,
 close: function () {
  // 事务批更新处理结束时,将isBatchingUpdates设为了false
  ReactDefaultBatchingStrategy.isBatchingUpdates = false;
 }
};
//FLUSH_BATCHED_UPDATES会在一个transaction的close阶段运行runBatchedUpdates,从而执行update。
//因为close的执行顺序是FLUSH_BATCHED_UPDATES.close ==> 然后RESET_BATCHED_UPDATES.close
var FLUSH_BATCHED_UPDATES = {
 initialize: emptyFunction,
 close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

function ReactDefaultBatchingStrategyTransaction() {
 this.reinitializeTransaction();
}

_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
 getTransactionWrappers: function () {
  return TRANSACTION_WRAPPERS;
 }
});
//这个transition就是下面ReactDefaultBatchingStrategy对象中使用的transaction变量
var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
 isBatchingUpdates: false,

 /**
  * Call the provided function in a context within which calls to `setState`
  * and friends are batched such that components aren&#39;t updated unnecessarily.
  */
 batchedUpdates: function (callback, a, b, c, d, e) {
  var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
// 批处理最开始时,将isBatchingUpdates设为true,表明正在更新
  ReactDefaultBatchingStrategy.isBatchingUpdates = true;

  // The code is written this way to avoid extra allocations
  if (alreadyBatchingUpdates) {
   return callback(a, b, c, d, e);
  } else {
   //transition在上面已经声明; // 以事务的方式处理updates,后面详细分析transaction
   return transaction.perform(callback, null, a, b, c, d, e);
  }
 }
};

module.exports = ReactDefaultBatchingStrategy;

그러면 ReactUp이 실행됩니다.dates.js

ReactUpdates.js의


var _prodInvariant = require(&#39;./reactProdInvariant&#39;);
var invariant = require(&#39;fbjs/lib/invariant&#39;);
var OBSERVED_ERROR = {};
var TransactionImpl = {
 reinitializeTransaction: function () {
  //getTransactionWrappers这个函数ReactDefaultBatchingStrategy.js中声明的,上面有;返回一个数组;
  this.transactionWrappers = this.getTransactionWrappers();
  if (this.wrapperInitData) {
   this.wrapperInitData.length = 0;
  } else {
   this.wrapperInitData = [];
  }
  this._isInTransaction = false;
 },

 _isInTransaction: false,
 getTransactionWrappers: null,
 isInTransaction: function () {
  return !!this._isInTransaction;
 },
 perform: function (method, scope, a, b, c, d, e, f) {
  var errorThrown;
  var ret;
  try {
   this._isInTransaction = true;
   errorThrown = true;
   //var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
   //1 这里会先执行所有的TRANSACTION_WRAPPERS中成员的initialize方法,上面声明的其都是emptyFunction
   this.initializeAll(0);
   //2 这里其实还是执行的 enqueueUpdate 函数
   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&#39;t throw, we don&#39;t want to silence the exception
     // here.
     //3 执行TRANSACTION_WRAPPERS对象中成员的所有close方法;
     this.closeAll(0);
    }
   } finally {
    this._isInTransaction = false;
   }
  }
  return ret;
 },

 initializeAll: function (startIndex) {
  var transactionWrappers = this.transactionWrappers;
  for (var i = startIndex; i < transactionWrappers.length; i++) {
   var wrapper = transactionWrappers[i];
   try {
    
    this.wrapperInitData[i] = OBSERVED_ERROR;
    this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
   } finally {
    if (this.wrapperInitData[i] === OBSERVED_ERROR) {
     
     try {
      this.initializeAll(i + 1);
     } catch (err) {}
    }
   }
  }
 },
 closeAll: function (startIndex) {
  var transactionWrappers = this.transactionWrappers;
  for (var i = startIndex; i < transactionWrappers.length; i++) {
   var wrapper = transactionWrappers[i];
   var initData = this.wrapperInitData[i];
   var errorThrown;
   try {
  
    errorThrown = true;
    if (initData !== OBSERVED_ERROR && wrapper.close) {
     wrapper.close.call(this, initData);
    }
    errorThrown = false;
   } finally {
    if (errorThrown) {
    
     try {
      this.closeAll(i + 1);
     } catch (e) {}
    }
   }
  }
  this.wrapperInitData.length = 0;
 }
};

module.exports = TransactionImpl

//3 执行TRANSACTION_WRAPPERS对象中成员的所有close方法;
var FLUSH_BATCHED_UPDATES = {
 initialize: emptyFunction,
 close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};

ReactReconciler.js의


var flushBatchedUpdates = function () {
 
 while (dirtyComponents.length || asapEnqueued) {
  if (dirtyComponents.length) {
   var transaction = ReactUpdatesFlushTransaction.getPooled();
   //这里执行runBatchedUpdates函数;
   transaction.perform(runBatchedUpdates, null, transaction);
   ReactUpdatesFlushTransaction.release(transaction);
  }

  if (asapEnqueued) {
   asapEnqueued = false;
   var queue = asapCallbackQueue;
   asapCallbackQueue = CallbackQueue.getPooled();
   queue.notifyAll();
   CallbackQueue.release(queue);
  }
 }
};
function runBatchedUpdates(transaction) {
 var len = transaction.dirtyComponentsLength;
 
 dirtyComponents.sort(mountOrderComparator);

 updateBatchNumber++;

 for (var i = 0; i < len; i++) {
 
  var component = dirtyComponents[i];
  var callbacks = component._pendingCallbacks;
  component._pendingCallbacks = null;

  var markerName;
  if (ReactFeatureFlags.logTopLevelRenders) {
   var namedComponent = component;
   // Duck type TopLevelWrapper. This is probably always true.
   if (component._currentElement.type.isReactTopLevelWrapper) {
    namedComponent = component._renderedComponent;
   }
   markerName = &#39;React update: &#39; + namedComponent.getName();
   console.time(markerName);
  }
//这里才是真正的开始更新组件
  ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);

  if (markerName) {
   console.timeEnd(markerName);
  }

  if (callbacks) {
   for (var j = 0; j < callbacks.length; j++) {
    transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
   }
  }
 }
}

ReactCompositeComponent .js


performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) {
  if (internalInstance._updateBatchNumber !== updateBatchNumber) {
   // The component&#39;s enqueued batch number should always be the current
   // batch or the following one.
   return;
  }
 //这里执行React组件实例对象的更新;internalInstance上的performUpdateIfNecessary在ReactCompositeComponent.js中的;
  internalInstance.performUpdateIfNecessary(transaction);
  if (process.env.NODE_ENV !== &#39;production&#39;) {
   if (internalInstance._debugID !== 0) {
    ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID);
   }
  }
 }

this.state가 업데이트됩니다. _processPendingState가 실행된 후 실행됩니다. 따라서 setState를 두 번 수행하여 얻은 this.state.count의 초기값은 0이 되는데, 이는 이전 현상을 설명합니다. 사실 이것도 상태 의존성을 해결하기 위한 React의 솔루션이지만 상태가 제때에 업데이트되지 않기 때문에 이를 사용할 때는 실제 상황에 따라 매개변수를 전달하는 데 어떤 방법을 사용할지 모두가 판단해야 합니다. 직관적인 느낌을 얻기 위해 작은 예를 살펴보겠습니다


performUpdateIfNecessary: function (transaction) {
 if (this._pendingElement != null) {
  // receiveComponent会最终调用到updateComponent,从而刷新View
  ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
 } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
  // 执行updateComponent,从而刷新View。
  this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
 } else {
  this._updateBatchNumber = null;
 }
},

 //执行更新React组件的props. state。context函数

 updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
  var inst = this._instance;
  var willReceive = false;
  var nextContext;
  // 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) {
   if (process.env.NODE_ENV !== &#39;production&#39;) {
    measureLifeCyclePerf(function () {
     return inst.componentWillReceiveProps(nextProps, nextContext);
    }, this._debugID, &#39;componentWillReceiveProps&#39;);
   } else {
    inst.componentWillReceiveProps(nextProps, nextContext);
   }
  }
//这里可以知道为什么setState可以接受函数,主要就是_processPendingState函数;
  //这里仅仅是将每次setState放入到_pendingStateQueue队列中的值,合并到nextState,并没有真正的更新state的值;真正更新组件的state的值是在下面;
  var nextState = this._processPendingState(nextProps, nextContext);
  var shouldUpdate = true;

  if (!this._pendingForceUpdate) {
   if (inst.shouldComponentUpdate) {
    if (process.env.NODE_ENV !== &#39;production&#39;) {
     shouldUpdate = measureLifeCyclePerf(function () {
      return inst.shouldComponentUpdate(nextProps, nextState, nextContext);
     }, this._debugID, &#39;shouldComponentUpdate&#39;);
    } else {
     shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
    }
   } else {
    if (this._compositeType === CompositeTypes.PureClass) {
     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&#39;s determined that a component should not update, we still want
   // to set props and state but we shortcut the rest of the update.
   //诺:在这里更新组件的state. props 等值;
   this._currentElement = nextParentElement;
   this._context = nextUnmaskedContext;
   inst.props = nextProps;
   inst.state = nextState;
   inst.context = nextContext;
  }
 },


_processPendingState: function (props, context) {
 var inst = this._instance;
 var queue = this._pendingStateQueue;
 var replace = this._pendingReplaceState;
 this._pendingReplaceState = false;
 this._pendingStateQueue = null;

 if (!queue) {
  return inst.state;
 }

 if (replace && queue.length === 1) {
  return queue[0];
 }

 var nextState = _assign({}, replace ? queue[0] : inst.state);
 for (var i = replace ? 1 : 0; i < queue.length; i++) {
  var partial = queue[i];
  //如果是setState的参数是一个函数,那么该函数接受三个参数,分别是state props context
  _assign(nextState, typeof partial === &#39;function&#39; ? partial.call(inst, nextState, props, context) : partial);
 }

 return nextState;
},

setState 프로세스는 여전히 매우 복잡하고 디자인도 매우 정교하여 불필요하게 반복되는 구성 요소 새로 고침을 방지합니다. 주요 프로세스는 다음과 같습니다


enqueueSetState는 상태를 대기열에 넣고 enqueueUpdate를 호출하여 업데이트할 구성 요소를 처리합니다.

    구성 요소가 현재 업데이트 트랜잭션에 있는 경우 먼저 dirtyComponent에 구성 요소를 저장합니다. 그렇지 않으면 일괄 업데이트 처리를 호출하세요.
  1. batchedUpdates는 transaction.perform() transaction을 시작합니다
  2. 트랜잭션 초기화 시작, 실행 및 3단계 종료
  3. 초기화: 트랜잭션 초기화 단계에 등록된 메서드가 없으므로 메서드가 없습니다. toexecute

    1. Run: setSate를 실행할 때 전달되는 콜백 메소드는 일반적으로 콜백 매개변수를 전달하지 않습니다
    2. End: isBatchingUpdates를 false로 업데이트하고 FLUSH_BATCHED_UPDATES 래퍼에서 close 메소드를 실행합니다
    3. FLUSH_BATCHED_UPDATE S는 것입니다 닫기 단계의 루프 모든 dirtyComponents를 탐색하고 updateComponent를 호출하여 구성 요소를 새로 고친 다음 setState에 설정된 콜백인 보류 중인Callbacks를 실행합니다.

    관련 권장 사항:
  4. React와 Preact의 setState 차이점


React 상위 수준 구성 요소 입력 예시 공유

JavaScript 프레임워크 Angular와 React의 차이점에 대한 대규모 분석

위 내용은 React의 setState 소스 코드에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.