概述
- Component 就是组件, 这个概念依托于最直观的在react上的一个表现,那就是
React.Component
- 我们写的组件大都是继承于
React.Component
这个baseClass 而写的类 - 这个类代表着我们使用 react 去实现的一个组件
- 那么在react当中不仅仅只有
Component
这一个baseClass,还有另外一个叫PureComponent
PureComponent
跟Component
唯一区别- 提供了简便的
shouldComponentUpdate
的一个实现 - 保证组件在 props 没有任何变化的情况下减少不必要的更新
- 提供了简便的
源码分析
1 ) API 位置定位
- 在入口文件 React.js里面,可看到
import {Component, PureComponent} from './ReactBaseClasses';
- 之后,我们定位到 ReactBaseClasses.js 里面
ReactBaseClasses.js 源码
/*** 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;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 = {};/*** Sets a subset of the state. Always use this to mutate* state. You should treat `this.state` as immutable.** There is no guarantee that `this.state` will be immediately updated, so* accessing `this.state` after calling this method may return the old value.** 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.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');
};/*** Forces an update. This should only be invoked when it is known with* certainty that we are **not** in a DOM transaction.** 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.** This will not invoke `shouldComponentUpdate`, but it will invoke* `componentWillUpdate` and `componentDidUpdate`.** @param {?function} callback Called after update is complete.* @final* @protected*/
Component.prototype.forceUpdate = function(callback) {this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};/*** 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__) {const 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).',],};const defineDeprecationWarning = function(methodName, info) {Object.defineProperty(Component.prototype, methodName, {get: function() {lowPriorityWarning(false,'%s(...) is deprecated in plain JavaScript React classes. %s',info[0],info[1],);return undefined;},});};for (const fnName in deprecatedAPIs) {if (deprecatedAPIs.hasOwnProperty(fnName)) {defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);}}
}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};
2 )源码分析
2.1 Component
-
从上面看到,Component 是一个function,它是一个使用function去进行类的声明的一个方式
-
它接收三个参数,一个是
props
,一个是context
,还有一个是updater
-
关于 props 和 context 可以发现, 在这里面进行赋值
this.props = props
this.context = context
- 就是我们在组件内部去使用的时候,可以直接去用
-
然后再声明了一个 refs,它是一个 emptyObject
- 参考 stringRef, 最终会把获取的节点的实例挂载在 this.refs 上面
- 就是我们 stringRef 使用的 key 挂载在上面
-
关于 updater 出场率并不高,先跳过
-
往下看,找到
Component.prototype.setState
Component.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'); };
- 这个就是我们使用的最多的一个API, 它是用来更新我们组件的状态
- 它接收的两个参数
* 一个是partialState
, 就是我们要更新的一个新的state,可以是一个对象,也可以是一个方法
* 第二个是callback
, 那么callback就是在我们state真正的更新完之后才会执行这个callback - 这个方法里面第一段代码是一个提醒
- 就是它判断我们的对象是不是object或者是function, 或者是不等于null
- 如果都不满足,它就会给出一个错误提醒
* 这个其实不太重要代码
- 下面一段才是重点
this.updater.enqueueSetState
- 调用 setState,其实在 Component 对象里面, 什么事情都没有做
- 它只是调用了初始化Component时, 传入的这个update对象上面的一个 enqueueSetState 方法
- 这个方法是在 react-dom 里面去实现的,跟react的代码是没有任何关系的
- 这样做的原因是,不同的平台,比如说react-dom和react-native
- 它们用的react的核心是一模一样的,也就是说,Component 这个API是一模一样的
- 但是,具体的涉及到更新state之后如何进行渲染,而这个渲染的流程在跟平台有关的
- 所以这里是一种类似于适配器或外观模式的一种设计模式,我更倾向于它是一个适配器
- react-dom平台跟react-native平台,它们的实现渲染的方式肯定是不一样的
- updater 它作为一个参数,让不同的平台传参进来,定制它自己的一个实现方式
- 所以,这就是为什么要封装传参
-
接下来,进入到
Component.prototype.forceUpdate
Component.prototype.forceUpdate = function(callback) {this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };
- 这个跟 setState 其实是一样的,它是调用了
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
这个方法 - 这个API不是很常用,它是强制 react Componet 更新一遍,即便你的state没有进行更新
- 这个跟 setState 其实是一样的,它是调用了
-
接下来,在 DEV 环境的判断下,有两个即将被废弃的API
- 一个是
isMounted
- 另外一个是
replaceState
- 一个是
-
到这里为止,可以看到 Component 的定义已经结束了, 它只有这么一点东西
-
这个 Component 的定义,只是用来帮助我们去承载一些信息的
-
没有任何的其他含义,也没有任何关于生命周期相关的一些方法, 就是这么简洁
2.2 PureComponent
-
其实可以认为它是继承于 Component
-
其实也没多少可以继承的东西,它们接收的东西也是一模一样的
/*** 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};
- 使用这个 ComponentDummy 实现了一个类似于简单的继承的方式
- 它 new了一个 ComponentDummy,它是一个空的类
- 它的 constructor 指向 PureComponent 自己, 这其实就是一个实继继承的过程
- 之后把 Componen.prototype 上面的属性拷贝到 pureComponentPrototype 上面
- 唯一的一个区别是
pureComponentPrototype.isPureReactComponent = true;
- 通过这个属性来标识,继承之这个类的组件,它是一个 PureComponent
- 在后续更新的过程中,react-dom 主动的去判断它是不是一个 PureComponent
- 然后去根据 props 是否更新来判断这个组件是否需要更新