React 源码浅析之 - ReactBaseClasses

引入的模块

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 才会更新完毕,那就需要从其他的模块里来寻找答案了。

相关文章