React與Preact差別之 -- setState

Preact是React的輕量級實現,是React比較好的替代者之一,有着體積小的優勢,固然與React之間必定會存在實現上的差別,本文介紹了在 setState 方面的差別之處。react

源碼分析

首先來分析下React以及Preact在setState部分的具體實現。antd

(太長不看想偷懶,能夠直接下翻看結論)函數

React

關鍵代碼:源碼分析

setState 階段:

// ReactUpdateQueue.js
enqueueSetState: function(publicInstance, partialState) {
  ...

  var queue =
    internalInstance._pendingStateQueue ||
    (internalInstance._pendingStateQueue = []);
  queue.push(partialState);

  enqueueUpdate(internalInstance);
}

能夠看到React在 setState 的時候不會作任何處理,會把變動直接放到一個專門處理 state 的隊列裏供組件更新時使用。性能

更新階段:

// ReactCompositeComponent.js
updateComponent: function(
  transaction,
  prevParentElement,
  nextParentElement,
  prevUnmaskedContext,
  nextUnmaskedContext,
) {
  var inst = this._instance;
  ...

  var willReceive = false;
  var nextContext;

  if (this._context === nextUnmaskedContext) {
    nextContext = inst.context;
  } else {
    nextContext = this._processContext(nextUnmaskedContext);
    willReceive = true;
  }

  var prevProps = prevParentElement.props;
  var nextProps = nextParentElement.props;

  if (prevParentElement !== nextParentElement) {
    willReceive = true;
  }

  if (willReceive && inst.componentWillReceiveProps) {
    ...
    inst.componentWillReceiveProps(nextProps, nextContext);
  }
  
  // 在此處才計算 nextState
  var nextState = this._processPendingState(nextProps, nextContext); // 此處傳入了 nextProps
  var shouldUpdate = true;

  if (!this._pendingForceUpdate) {
    if (inst.shouldComponentUpdate) {
      ...
      shouldUpdate = inst.shouldComponentUpdate(
        nextProps,
        nextState,
        nextContext,
      );
    } else {
      if (this._compositeType === CompositeTypes.PureClass) { // 敲黑板,知識點 —— 若是你的組件沒實現shouldComponentUpdate,那麼把React.Component 換成 React.PureComponent 能夠得到基礎版優化,提升性能。
        shouldUpdate =
          !shallowEqual(prevProps, nextProps) ||
          !shallowEqual(inst.state, nextState); // 淺比較,能夠抄去本身改爲屬性黑/白名單版
      }
    }
  }
  ...
}

// ReactCompositeComponent.js
_processPendingState: function(props, context) { // props: nextProps
  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 = Object.assign({}, replace ? queue[0] : inst.state);
  for (var i = replace ? 1 : 0; i < queue.length; i++) {
    var partial = queue[i];
    Object.assign(
      nextState,
      typeof partial === 'function'
        ? partial.call(inst, nextState, props, context) // nextProps
        : partial,
    );
  }

  return nextState;
}

經過上面組件更新的流程代碼能夠看到:優化

  • 在 updateComponent 中,在 componentWillReceiveProps 以後纔會計算 nextState,因此在 componentWillReceiveProps 中 setState 是能夠在當次更新中生效的。
  • 在 _processPendingState 會對隊列裏的 state 進行疊加,若是修改是函數方式,此處傳入的state參數是 nextState,props 是 nextProps。

Preact

關鍵代碼:this

setState 階段:

// component.js
setState(state, callback) {
  let s = this.state;
  if (!this.prevState) this.prevState = extend({}, s);
  extend(s, typeof state==='function' ? state(s, this.props) : state);
  if (callback) (this._renderCallbacks = (this._renderCallbacks || [])).push(callback);
  enqueueRender(this);
}

實現的簡單粗暴,在 setState 的時候就進行了合併,會當即改寫 this.state,在第一次 setState 時會保留 state 狀態到 prevState。因爲是當即合併state,若是入參state是函數,props 將只是當前 this.props。code

更新階段:

export function renderComponent(component, opts, mountAll, isChild) {
  ...
  previousProps = component.prevProps || props,
  previousState = component.prevState || state,
  previousContext = component.prevContext || context,
  ...

  // if updating
  if (isUpdate) {
    component.props = previousProps;
    component.state = previousState;
    component.context = previousContext;
    if (opts!==FORCE_RENDER
      && component.shouldComponentUpdate
      && component.shouldComponentUpdate(props, state, context) === false) {
      skip = true;
    }
    else if (component.componentWillUpdate) {
      component.componentWillUpdate(props, state, context);
    }
    component.props = props;
    component.state = state;
    component.context = context;
  }
  ...
}

在更新流程前提取了舊 state,shouldComponentUpdate、componentWillUpdate 以後還原回新值,因此在 shouldComponentUpdate 生命週期中,this.props 將獲取的是 prevProps,這裏與 React 的邏輯並不一致。component

劃重點

相同點:生命週期

  • 在 componentWillReceiveProps 中 setState 都會應用到 nextState。
  • 在 shouldComponentUpdate 中 setState 都會應用到 nextState,可是能夠直接操做傳入的 nextState。

不一樣點:

  • React下 setState 的值不會當即生效,會一直積累到 componentWillReceiveProps,在此以後會進行合併,並提供給後續生命週期。而Preact下 setState 會當即反映到 this.state,可是,在更新組件的生命週期到 render 前(eg: shouldComponentUpdate), this.state 將會是 prevState。
  • shouldComponentUpdate 階段 setState 雖然不會影響到最終 state 的值,可是Preact下會影響 this.state 的值,好比以後 componentWillUpdate 中的 this.state, 總之此階段不要 setState 反正也沒用。
  • setState 若是使用函數修改,Preact下傳入的 props 將會是 prevProps,而React中是 nextProps,在 componentWillReceiveProps 中 setState 時要注意。

總結

若是你寫的工程須要同時兼容React及Preact的話:

  • 不要利用React下 setState 在同一次組件更新執行前 state 不當即更新的特性,注意多個 setState 之間是否影響,必要時手動保存舊值。
  • 在組件更新生命週期內,除 componentWillReceiveProps 以外不要使用 setState,提供了 nextState 的生命週期,能夠直接修改 nextState。
  • 儘可能避免使用 setState 函數修改方式,在 componentWillReceiveProps 中使用時,使用生命週期中的 prevProps(this.props) 和 nextProps。

p.s: antd-mobile 2.0正式版已發佈,同時兼容react、preact,輕量、快速、易用的移動端組件庫,等你來用~ 【傳送門】

相關文章
相關標籤/搜索