從源碼看React.PureComponent

本文源碼是2018年9月12日拉取的React倉庫master分支上的代碼html

React.PureComponent 官方文檔:reactjs.org/docs/react-…react

Component 與 PureComponent 的區別

React.PureComponent is similar to React.Component. The difference between them is that React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.git

React.PureComponent 和 React.Component 幾乎相同,區別在於 React.PureComponent 會 淺比較 props、state是否發生變化從而決定是否更新組件(這裏的淺比較在後面的源碼分析中會提到)github

使用 React.PureComponent 也是React應用優化的一種方式,固然也能使用 React.Component 定義shouldComponentUpdate生命週期函數來實現同樣的功能,可是直接使用 React.PureComponent 能更加直觀和簡便api

看一個簡單的例子:數據結構

使用React.Component架構

class CounterButton extends React.Component {
    state = {
        count: 1
    }
    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.color !== nextProps.color) {
            return true;
        }
        if (this.state.count !== nextState.count) {
            return true;
        }
        return false;
    }
    render() {
        return (
            <button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button>
        );
    }
}
複製代碼

使用React.PureComponentapp

class CounterButton extends React.PureComponent {
    state = {
        count: 1
    }
    render() {
        return (
            <button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button>
        );
    }
}
複製代碼

上面兩段代碼都能避免沒必要要的組件更新,優化性能函數

源碼

Component & PureComponent 定義

ReactBaseClasses.jsoop

const emptyObject = {};
/** * Base class helpers for the updating state of a component. */
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.prototype.isReactComponent = {};

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

/** * Convenience component with default shallow equality check for sCU. */
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;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;

export {Component, PureComponent};
複製代碼

從源碼來看,Component 和 PureComponent 基本同樣,惟一區別在於 PureComponent 定義了 isPureReactComponenttrue,這是爲了方便在React應用運行過程當中區分 Component 和 PureComponent

在分析後續的源碼以前,建議小夥伴去看下個人文章:React16源碼之React Fiber架構,這篇文章分析了React應用總體的執行流程

本文重點分析 reconciliation階段 beginWork函數中的 updateClassComponent函數的調用(這一部分在 React16源碼之React Fiber架構 中重點分析了)

beginWork函數主要有兩部分工做:

一、對Context進行處理

二、根據Fiber節點的tag類型,調用對應的update方法

而tag類型爲ClassComponent的Fiber節點會調用updateClassComponent函數,咱們來看看updateClassComponent函數的核心源碼

function updateClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, nextProps, renderExpirationTime: ExpirationTime, ) {
  ...
  let shouldUpdate;
  if (current === null) {
    if (workInProgress.stateNode === null) {
      // In the initial pass we might need to construct the instance.
      constructClassInstance(
        workInProgress,
        Component,
        nextProps,
        renderExpirationTime,
      );
      mountClassInstance(
        workInProgress,
        Component,
        nextProps,
        renderExpirationTime,
      );
      shouldUpdate = true;
    } else {
      // In a resume, we'll already have an instance we can reuse.
      shouldUpdate = resumeMountClassInstance(
        workInProgress,
        Component,
        nextProps,
        renderExpirationTime,
      );
    }
  } else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  }
  return finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderExpirationTime,
  );
}
複製代碼

執行流程以下:

current爲null,表示當前組件第一次渲染

判斷當前組件是否須要初始化

  • workInProgress.stateNode === null表示須要初始化,調用constructClassInstancemountClassInstance兩個函數
  • 不然,表示組件已初始化,則調用resumeMountClassInstance函數複用初始化過的實例

(React源碼也在不斷更新,因此這塊邏輯比React16源碼之React Fiber架構講的邏輯多了一個複用邏輯)

current不爲null,調用updateClassInstance

constructClassInstancemountClassInstance作的工做:

  • constructClassInstance主要是初始化組件實例,即調用constructor構造函數,並注入classComponentUpdater
  • mountClassInstance則是調用getDerivedStateFromProps生命週期函數(v16)及UNSAFE_componentWillMount生命週期函數

從上面的源碼能夠看到,resumeMountClassInstance函數和updateClassInstance函數都會將返回值賦值給shouldUpdate變量,而shouldUpdate變量是布爾類型,在後面的流程中,決定是否執行render函數

這裏以updateClassInstance函數爲例來看看源碼

function updateClassInstance( current: Fiber, workInProgress: Fiber, ctor: any, newProps: any, renderExpirationTime: ExpirationTime, ): boolean {
  // 若是新老props不一致,則會調用 UNSAFE_componentWillReceiveProps 生命週期函數
  ...
  let updateQueue = workInProgress.updateQueue;
  if (updateQueue !== null) {
    processUpdateQueue(
      workInProgress,
      updateQueue,
      newProps,
      instance,
      renderExpirationTime,
    );
    newState = workInProgress.memoizedState;
  }
  // 執行 getDerivedStateFromProps 生命週期函數
  ...
  const shouldUpdate =
    checkHasForceUpdateAfterProcessing() ||
    checkShouldComponentUpdate(
      workInProgress,
      ctor,
      oldProps,
      newProps,
      oldState,
      newState,
      nextLegacyContext,
    );

  if (shouldUpdate) {
    ...
  } else {
    ...
  }
  ...
  return shouldUpdate;
}
複製代碼

重點關注checkShouldComponentUpdate函數

function checkShouldComponentUpdate( workInProgress, ctor, oldProps, newProps, oldState, newState, nextLegacyContext, ) {
  const instance = workInProgress.stateNode;
  if (typeof instance.shouldComponentUpdate === 'function') {
    startPhaseTimer(workInProgress, 'shouldComponentUpdate');
    const shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextLegacyContext,
    );
    stopPhaseTimer();

    return shouldUpdate;
  }

  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }

  return true;
}
複製代碼

執行流程以下:

一、是否有shouldComponentUpdate生命週期函數,有則調用今生命週期函數並返回結果(shouldUpdate)

二、判斷此組件是否爲PureComponent,是則執行shallowEqual對新老props、新老state進行淺比較,並返回比較結果

三、默認返回true

shallowEqual函數:

const hasOwnProperty = Object.prototype.hasOwnProperty;
function is(x, y) {
  // SameValue algorithm
  if (x === y) {
    // Steps 1-5, 7-10
    // Steps 6.b-6.e: +0 != -0
    // Added the nonzero y check to make Flow happy, but it is redundant
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  } else {
    // Step 6.a: NaN == NaN
    return x !== x && y !== y;
  }
}
/** * Performs equality by iterating through keys on an object and returning false * when any key has values which are not strictly equal between the arguments. * Returns true when the values of all keys are strictly equal. */
function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) {
    return true;
  }
  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);
  if (keysA.length !== keysB.length) {
    return false;
  }
  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    if (
      !hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true;
}

export default shallowEqual;
複製代碼

能夠看到,shallowEqual真的就是淺比較,因此對於props、state是複雜數據結構若是使用 PureComponent 每每會致使更新問題

當props、state是簡單數據結構的組件適合使用 PureComponent,或者使用 forceUpdate() 來更新複雜數據結構,或者考慮結合 immutable objects 使用,或者直接使用 Component,自定義shouldComponentUpdate生命週期函數

說到 forceUpdate()能夠順便看下源碼,首先看看 forceUpdate函數定義,在前面也說過在給組件初始化時,會給組件實例注入classComponentUpdater,而調用forceUpdate其實就是調用classComponentUpdater.enqueueForceUpdate,來看看定義

const classComponentUpdater = {
  ...
  enqueueForceUpdate(inst, callback) {
    ...
    const update = createUpdate(expirationTime);
    // !!!
    update.tag = ForceUpdate;

    if (callback !== undefined && callback !== null) {
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
};
複製代碼

能夠看到,在將update放入隊列以前,執行了update.tag = ForceUpdate;,這個標記將在後面用於標識更新是否爲ForceUpdate,後面的流程與正常更新流程一直,能夠參考React16源碼之React Fiber架構

咱們再回到updateClassInstance函數,在執行checkShouldComponentUpdate函數以前,執行了processUpdateQueue函數及進行了checkHasForceUpdateAfterProcessing函數判斷

processUpdateQueue函數主要是遍歷updateQueue,調用getStateFromUpdate函數

getStateFromUpdate函數源碼以下:

function getStateFromUpdate<State>( workInProgress: Fiber, queue: UpdateQueue<State>, update: Update<State>, prevState: State, nextProps: any, instance: any, ): any {
  switch (update.tag) {
    case ReplaceState: {
      ...
    }
    case CaptureUpdate: {
      ...
    }
    // Intentional fallthrough
    case UpdateState: {
      ...
    }
    case ForceUpdate: {
      hasForceUpdate = true;
      return prevState;
    }
  }
  return prevState;
}
複製代碼

咱們能夠看到,此函數是判斷update的tag類型,對於ForceUpdate類型會將hasForceUpdate變量設置爲true

checkHasForceUpdateAfterProcessing函數則是返回hasForceUpdate變量,代碼以下:

export function checkHasForceUpdateAfterProcessing(): boolean {
  return hasForceUpdate;
}
複製代碼

當調用了forceUpdate函數,不管是否存在shouldComponentUpdate生命週期函數,不管此組件是否爲 PureComponent,都會強制更新,因此應該謹慎使用

寫在最後

以上就是我對React PureComponent的源碼的分享,但願能對有須要的小夥伴有幫助~~~

喜歡個人文章小夥伴能夠去 個人我的博客 點star ⭐️

相關文章
相關標籤/搜索