React 源码浅析之 - ReactBaseClasses

Create at 2017 09 218 min read技术ReactSourceCode


引入的模块

var ReactNoopUpdateQueue = require("ReactNoopUpdateQueue")

var emptyObject = require("fbjs/lib/emptyObject")
var invariant = require("fbjs/lib/invariant")
var lowPriorityWarning = require("lowPriorityWarning")

其中, ReactNoopUpdateQueue 是默认的 updater ,用来提供 update, replaceState, setState 的入队操作,但可能是由于是默认 updater 的原因,只提供了 API 和对入参的校验,但没有提供实际的功能。比如:

enqueueSetState: function(
  // 需要 render 的实例
  publicInstance,
  // 接下来要 merge 的 state
  partialState,
  // 可选参数,setState 组件 update 后的回调
  callback,
  // 可选参数,调用函数的的名字
  callerName,
) {
  warnNoop(publicInstance, 'setState');
},

其余几个模块是一些通用辅助模块,就不细说了。

Export 的对象

module.exports = {
  Component: ReactComponent,
  PureComponent: ReactPureComponent,
  AsyncComponent: ReactAsyncComponent,
}

我们依次来看这几个 Component .

ReactComponent

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

用来创建基础组件的构造函数,其中 refs 默认是一个空对象,而 updater 默认就是我们上面说的 ReactNoopUpdateQueue.

setState

ReactComponent.prototype.setState = function (partialState, callback) {
  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."
  )
  this.updater.enqueueSetState(this, partialState, callback, "setState")
}

给它添加 setState 方法,实际操作就是把下一个要设置的 state 放入到更新队列里面。注释中提到:

  • 永远使用 setState 这个方法去更改 state ,你应该认为 this.state 是不可变的
  • 不保证 this.state 会及时更新,也就是说调用 this.state 可能拿到的还是旧的 state
  • 不保证 setState 会同步调用,有可能会把几个 setState 的调用一次性批量更新掉,一定要在某个 setState 的调用完成后执行-操作,可以提供可选的 callback 函数。
  • 当一个 callback 函数传入给 setState 的时候,它会在未来的某个时机调用。会用最新的组件参数 (state, props, context). 而这些参数和此时组件本身的 this.* 上的参数值可能会不一样,应为 callback 函数有可能在 receiveProps 之后 shouldComponentUpdate 之前调用,而此时这个新的 state, props 和 context 还没有赋值给 this.

其核心思想就是说, setState 的调用是异步的,从代码 this.updater.enqueueSetState(this, partialState, callback, 'setState'); 中可以看到,只是把要更新的 state 放入到了更新队列里面,而不是直接进行 state 的更新。

forceUpdate

ReactComponent.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this, callback, "forceUpdate")
}

一个强制更新的方法,这个方法不会触发 shoudleComponentUpdate ,但是会正常调用 componentWillUpdatecomponentDidUpdate . 要确保调用这个方法的时候,所有的 DOM 事务操作已经完成。 只有在当你知道一个组件的深层次的 state 变化了,但是并没有调用 setState 的时候才需要调用这个方法。

不推荐的方法

/**
 * Deprecated APIs. These APIs used to exist on classic React classes but since
 * we would like to deprecate them, we're not going to move them over to this
 * modern base class. Instead, we define a getter that warns if it's accessed.
 */
if (__DEV__) {
  var deprecatedAPIs = {
    isMounted: [
      "isMounted",
      "Instead, make sure to clean up subscriptions and pending requests in " +
        "componentWillUnmount to prevent memory leaks.",
    ],
    replaceState: [
      "replaceState",
      "Refactor your code to use setState instead (see " +
        "https://github.com/facebook/react/issues/3236).",
    ],
  }
  var defineDeprecationWarning = function (methodName, info) {
    Object.defineProperty(ReactComponent.prototype, methodName, {
      get: function () {
        lowPriorityWarning(
          false,
          "%s(...) is deprecated in plain JavaScript React classes. %s",
          info[0],
          info[1]
        )
        return undefined
      },
    })
  }
  for (var fnName in deprecatedAPIs) {
    if (deprecatedAPIs.hasOwnProperty(fnName)) {
      defineDeprecationWarning(fnName, deprecatedAPIs[fnName])
    }
  }
}

这里可以看到 isMountedreplaceState 这两个方法,已经被官方认为将要废弃的 API ,在开发过程中应该尽量避免使用它们。

PureComponent

function ReactPureComponent(props, context, updater) {
  // Duplicated from ReactComponent.
  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
}

function ComponentDummy() {}
ComponentDummy.prototype = ReactComponent.prototype
var pureComponentPrototype = (ReactPureComponent.prototype =
  new ComponentDummy())
pureComponentPrototype.constructor = ReactPureComponent
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, ReactComponent.prototype)
pureComponentPrototype.isPureReactComponent = true

一个典型 js 实现继承的方式,可以看到 PureComponent 继承自 ReactComponent ,PureComponent 拥有 ReactComponent 的所有属性和方法,并在此基础上多了一个属性:

pureComponentPrototype.isPureReactComponent = true

后面在组件更新部分会用到这个属性。

AsyncComponent

这个组件在我实际开发过程中还没有用到过,我们来看一下:

function ReactAsyncComponent(props, context, updater) {
  // Duplicated from ReactComponent.
  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
}

var asyncComponentPrototype = (ReactAsyncComponent.prototype =
  new ComponentDummy())
asyncComponentPrototype.constructor = ReactAsyncComponent
// Avoid an extra prototype jump for these methods.
Object.assign(asyncComponentPrototype, ReactComponent.prototype)
asyncComponentPrototype.unstable_isAsyncReactComponent = true
asyncComponentPrototype.render = function () {
  return this.props.children
}

和 PureComponent 一样,同样继承自 ReactComponent. 再次基础上添加了 unstable_isAsyncReactComponent 属性,并设置了 render 方法为直接返回它的 children. 从新添加的属性名可以看到,这是一个还没有稳定下来的特性组件。从名字和内容初步判断是用来创建异步的组件,先用一个 React 组件占位,当 props 传入 children 的时候,在进行渲染,从 render 方法直接 return 可以猜到这样可以实现无多余嵌套 html 标签的组件。

总结

这个模块定义了 React 中会用到的三个基础组件类,需要注意的是: 1. State 应该被看作是 immutable 的,所有的对 state 的修改只能通过 setState 来完成,不能使用 this.state = ** 来进行 state 的更新 2. setState 是一个异步的操作,所以在调用 setState 后从 this.state 上直接取值可能不是你期望的结果,读取的结果有可能依旧是老的 state 而组件内部的 updater 逻辑,这个模块里面并没有定义具体的执行逻辑。比如调用 setState 后会对要更新的 state 进行入队操作,那么这个操作过程中发生了什么,究竟是如何进行批量的更新的,以及什么时候 state 才会更新完毕,那就需要从其他的模块里来寻找答案了。

相关文章

  • {% post_link react-source-code-analyze-1 React 源码浅析之 - 入口文件 %}
  • {% post_link react-source-code-analyze-2 React 源码浅析之 - ReactBaseClasses %}
  • {% post_link react-source-code-analyze-3 React 源码浅析之 - ReactChildren %}
  • {% post_link react-source-code-analyze-4 React 源码浅析之 - ReactElement %}
  • {% post_link react-source-code-analyze-5 React 源码浅析之 - onlyChildren %}

本文章遵循: CC BY-NC-ND 4.0Creative CommonsAttributionNonCommercialNoDerivatives

非商业转载请注明作者及出处,商业转载请联系 作者本人

本文标题为:React 源码浅析之 - ReactBaseClasses

本文链接为:https://blog.kisnows.com/2017/09/21/react-source-code-analyze-2