React源碼解析之React.Component()/PureComponent()

1、React.Component() html

GitHub:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react/src/ReactBaseClasses.jsreact

用法:git

class A extends React.Component {
  constructor(props){
    super(props)
    this.state={ }
  } 

  componentWillMount(){ }
  
  render() {
    return { }
  }  
}

源碼:github

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import invariant from 'shared/invariant';
import lowPriorityWarning from 'shared/lowPriorityWarning';

import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';

const emptyObject = {};
if (__DEV__) {
  Object.freeze(emptyObject);
}

/**
 * Base class helpers for the updating state of a component.
 */
//幫助更新組件狀態的基類
function Component(props, context, updater) {
  this.props = props;
  //我在工做中沒用到context,能夠參考下這個:
  //https://www.cnblogs.com/mengff/p/9511419.html
  //是React封裝的全局變量API
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  //若是在組件中用了 ref="stringa" 的話,用另外一個obj賦值
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  //雖然給updater賦了默認值,但真正的updater是在renderer中註冊的
  this.updater = updater || ReactNoopUpdateQueue;
}
//原型上賦了一個flag
Component.prototype.isReactComponent = {};

/** 使用setState來改變Component內部的變量

 * Sets a subset of the state. Always use this to mutate
 * state. You should treat `this.state` as immutable.

 * this.state並非當即更新的,因此在調用this.setState後可能 不能 拿到新值

 * There is no guarantee that `this.state` will be immediately updated, so
 * accessing `this.state` after calling this method may return the old value.
 *
 * 不能保證this.state是同步的(它也不是異步的),使用回調獲取最新值
 *
 * There is no guarantee that calls to `setState` will run synchronously,
 * as they may eventually be batched together.  You can provide an optional
 * callback that will be executed when the call to setState is actually
 * completed.
 *
 * When a function is provided to setState, it will be called at some point in
 * the future (not synchronously). It will be called with the up to date
 * component arguments (state, props, context). These values can be different
 * from this.* because your function may be called after receiveProps but before
 * shouldComponentUpdate, and this new state, props, and context will not yet be
 * assigned to this.
 *
 * @param {object|function} partialState Next partial state or function to
 *        produce next partial state to be merged with current state.
 * @param {?function} callback Called after state is updated.
 * @final
 * @protected
 */


// 更新Component內部變量的API,
// 也是開發中很是經常使用且重要的API

// https://www.jianshu.com/p/7ab07f8c954c
// https://www.jianshu.com/p/c19e259870a5

//partialState:要更新的state,能夠是Object/Function
//callback: setState({xxx},callback)
Component.prototype.setState = function(partialState, callback) {
  // 判斷setState中的partialState是否符合條件,
  // 若是不符合則拋出Error
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  //重要!state的更新機制
  //在react-dom中實現,不在react中實現
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

/**
 * Forces an update. This should only be invoked when it is known with
 * certainty that we are **not** in a DOM transaction.
 *
 * 在Component的深層次改變但未調用setState時,使用該方法
 *
 * You may want to call this when you know that some deeper aspect of the
 * component's state has changed but `setState` was not called.
 *
 * forceUpdate不調用shouldComponentUpdate方法,
 * 但會調用componentWillUpdate和componentDidUpdate方法
 *
 * This will not invoke `shouldComponentUpdate`, but it will invoke
 * `componentWillUpdate` and `componentDidUpdate`.
 *
 * @param {?function} callback Called after update is complete.
 * @final
 * @protected
 */
//強制Component更新一次,不管props/state是否更新
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

解析:
(1)Component()本質是一個類:dom

class Component {
  constructor(props, context, updater){
    this.props = props
    this.context = context
    this.refs = emptyObject
    this.updater = updater || ReactNoopUpdateQueue
  }
}

(2)setState()是 Component 原型上的方法,其本質是調用ReactNoopUpdateQueue.js中的enqueueSetState()方法,以後的文章會分析enqueueSetState()的,不要急異步

(3)forceUpdate()(2)ide

(4)我覺得React.Component()裏面實現componentWillMount()render()等內部方法,其實並無函數

React.Component()只涉及了props /context /refs /updater /isReactComponent /setState /forceUpdate ,其餘均沒有本身實現。oop


2、PureComponent優化

GitHub:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react/src/ReactBaseClasses.js

什麼是 PureComponent:
能夠看下這篇文章的第一點:小知識11點(2018.9.4 )

複用性強的組件:若是一個組件的渲染只依賴於外界傳進去的 props 和本身的 state,而並不依賴於其餘的外界的任何數據,也就是說像純函數同樣,給它什麼,它就吐出(渲染)什麼出來。這種組件的複用性是最強的。即 Pure Component 或稱 Dumb Component。

用法:

class A extends React.PureComponent { 
  //同React.Component() 
}

源碼:

function ComponentDummy() {}

//ComponentDummy的原型 繼承 Component的原型
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;
}

//PureComponent是繼承自Component的,下面三行就是在繼承Component

//將Component的方法拷貝到pureComponentPrototype上
// 用ComponentDummy的緣由是爲了避免直接實例化一個Component實例,能夠減小一些內存使用
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());

//PureComponent.prototype.constructor = PureComponent
pureComponentPrototype.constructor = PureComponent;

// Avoid an extra prototype jump for these methods.
//避免多一次原型鏈查找,由於上面兩句已經讓PureComponent繼承了Component
//下面多寫了一句Object.assign(),是爲了不多一次原型鏈查找

// Object.assign是淺拷貝,
// 將Component.prototype上的方法都複製到PureComponent.prototype上
// 也就是pureComponent的原型上
// 詳細請參考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Object.assign(pureComponentPrototype, Component.prototype);

// 惟一的區別就是在原型上添加了isPureReactComponent屬性去表示該Component是PureComponent
pureComponentPrototype.isPureReactComponent = true;

export {Component, PureComponent};

解析:
(1)重點看最後三行作了什麼:(減小內存消耗,減小原型鏈查找次數)

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy())
新建了空方法ComponentDummy ,並繼承Component的原型;
PureComponent.prototype等於ComponentDummy的實例

這樣作的目的是:

若是讓PureComponent.prototype直接等於Component的實例對象的話(繼承原型),會多繼承Componentconstructor,可是PureComponent已經有本身的constructor了,這樣就會多消耗一些內存。

因此會新建ComponentDummy,只繼承Component的原型,不包括constructor,以此來節省內存。


pureComponentPrototype.constructor = PureComponent

原型的constructor等於自身,覆蓋掉Component.prototypeconstructor(Component)

①、② 就是讓PureComponent繼承Component,那麼爲何還要多寫一句Object.assign(pureComponentPrototype, Component.prototype)呢?


PureComponentprototype淺拷貝Componentprototype的全部屬性

不寫 ③ 的話:

pureComponentPrototype.__proto__=== ComponentDummy.prototype //true
//也就是
PureComponent.prototype.__proto__=== Component.prototype //true

這樣就多了一層隱式原型的查找,爲了減小一次原型鏈查找,因此寫了

Object.assign(pureComponentPrototype, Component.prototype)

這樣的話:
Component.prototype中的方法在PureComponent.prototype中都有,無需再從__proto__上查找了。


(2)pureComponentPrototype.isPureReactComponent = true

ReactFiberClassComponent.js中,有對isPureReactComponent的判斷:

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

注意:(重要)

(1)整個React中判斷 Component類 是否須要更新,只有兩個地方:
一 是看有沒有shouldComponentUpdate方法

二 就是ReactFiberClassComponent.js中的checkShouldComponentUpdate()中對PureComponent的判斷

(2)PureComponentComponent惟一的區別:
PureComponent是自帶了一個簡單的shouldComponentUpdate來優化更新機制的。


(完)

相關文章
相關標籤/搜索