资讯专栏INFORMATION COLUMN

Component,PureComponent源码解析

luqiuwen / 2407人阅读

摘要:首先是创建了一个构造函数,他的原型指到的原型然后创建了一个加上了和一样的属性这里为啥不用。的原型指向的实例修改原型的属性使其正确指向的构造函数,并挂一个的属性。

每次都信誓旦旦的给自己立下要好好学习react源码的flag,结果都是因为某个地方卡住了,或是其他原因没看多少就放弃了。这次又给自己立个flag-坚持看完react源码。为了敦促自己,特开设这样一个专栏来记录自己的学习历程,这意味着这个专栏的文章质量并不高,你可以拿来参考参考,切莫全信,我不想误人子弟,后面要是学有所成再考虑产出些好点的文章。 要是发现文章中有什么不当之处,欢迎批评交流。我看的源码版本是16.8.2。我是用在源码加注释的方法学习的,放在github上。

为了看react源码,我查找了不少资料,这里推荐两个参考资料,个人觉得写得不错。

慕课网一个课的电子书,他有个源码解析的视频教程,应该不错,不过我没买。

一个知乎专栏,写得很清晰,只不过是15.6.2的, 在react16里面一些方法找不到了。

Component, PureComponent是我们最常用的东西,我们经常继承他们来创建组件。因此,我选择从这几个最最常用的东西入手开始欣赏React源码。他们都位于packages/react目录下,入口在index.js,index.js里边导出的其实是src下的React.js里的东西,在React.js中可以看到React暴露的API。在React.js中可以找到上面说述的Component,PureComponent和ReactElement相关线索。

Component

Component和PureComponent都位于/packages/react/src/ReactBaseClasses.js。

这两个东西都是构造函数,或者称为类。

Component的构造函数长成如下这样:

/**
 * 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.

  // 这个new的时候需要注意updater是哪里来的, 这个updater与setState应该有很大关系
  this.updater = updater || ReactNoopUpdateQueue;
}

这并没有什么神奇的,他接收三个参数,挂到this上。具体是这三个参数是啥,我目前也是不清楚的,因为我们平时使用都是extends他而并没有new他,new的过程应该是框架去做的,这个得到后面再做分析。后面分析时需要注意updater,感觉这里会是一个重点,他有一个默认值,ReactNoopUpdateQueue,去看了下他的代码,他是一个对象,挂了一些方法,这里也就不展开了,我也没太细看。

Component的原型上挂了一些方法和属性,isReactComponent属性,setState方法,forceUpdate方法,代码如下:

// 通常isXxx都是boolean类型的,这里比较奇怪,后面需要关注下
Component.prototype.isReactComponent = {};

/**
 * ...这里有很多说明,可以直接去看
 *
 * @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
 */

 // 原来我们平时调用的setState就这么几行啊,但是看他是调用的updater的enqueueSetState,
 // 相关实现应该在那里边了, 可以updater这个东西很厉害
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");
};

/**
 * ...这里有很多说明,可以直接去看
 *
 * @param {?function} callback Called after update is complete.
 * @final
 * @protected
 */

 // 很少用到这个方法啊, 但他和setState一样都是Component原型上的方法
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
};

其实Component的原型上挂载的东西也没什么神奇的,其中非常重要的是updater的enqueueSetState,enqueueForceUpdate方法,进一步说明了updater是后面分析的重点。

接下来的一段代码是用来在开发模式下标记废弃的api的,在开发模式下回给写提示,代码如下:

 // 这里是标识一些废弃的api, 开发模式会报出来提醒开发这注意
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]);
    }
  }
}

__DEV__这个东西我没找到是在哪里挂到全局的(知道的同学可以留言指点下),但是看变量名可以推测他是开发模式标识,这个提示我们在做一些给别人用的东西时,接口协议约定十分重要,一旦约定就不能轻易变更,确实需要变更时需要通知调用方调整。回头来,这里标识废弃了isMounted,replaceState两个方法,其实他们被挪到了updater里边。

PureComponent

开始用React时老大Rewview我的代码时经常写评论,“你这个Component可以改成PureComponent”,当时一直不懂PureComponent与Component的区别(现在也没全懂),只是听人说PureComponent更新的时候是浅比较,而Component是深比较。今天看了这部分,其实也没懂,不过感觉后面再看看应该就懂了。要搞清这里的PureComponet需要了解下js中继承的实现,大家可以参考《JavaScript高级程序设计》相关介绍,也可以看看理解js继承的6种方式, 笔者看到这个PureComponet也是先复习了下才看的。不管你看没看, 代码先贴出来:

// PureComponent

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


// 发现PureComponnet的构造方法和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;

我画了个图来理解这个继承。

首先是创建了一个ComponentDummy构造函数,他的原型指到Component的原型;然后创建了一个PureComponent, 加上了和Component一样的属性(这里为啥不用call)。PureComponent的原型指向ComponentDummy的实例;修改PureComponent原型的constructor属性使其正确指向PureComponent的构造函数,并挂一个isPureReactComponent的属性。为了减少向上去查找原型链次数,用了一个assign直接将Component原型的东西拷贝到PureComponent的原型上(这里还是考虑的比较精细的)。

首先这个实现没有啥问题,但是我有个疑问,大家可以留言指点下:

为什么要用继承,注意到PureComponent的构造函数和Component是一样的,然后还有一个拷贝Component的原型到PureComponent的原型的操作,那这里有继承的必要吗?不都是重写的吗,感觉多此一举。

下一篇预告 ReactElement源码解析

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103351.html

相关文章

  • React源码解析之React.Component()/PureComponent()

    摘要:只涉及了,其他均没有自己实现。这种组件的复用性是最强的。所以会新建,只继承的原型,不包括,以此来节省内存。 showImg(https://segmentfault.com/img/remote/1460000019783989); 一、React.Component() GitHub:https://github.com/AttackXiaoJinJin/reactExplain/...

    Cristalven 评论0 收藏0
  • React PureComponent 源码解析

    摘要:自身的源码也很简单,节选如下上面的就是暴露给外部使用的。关于的源码分析就到这里。这是内部使用的一个工具集。不过也不是万能的,特定情况下自己实现可能更高效。 TL;DR React 15.3.0 新增了一个 PureComponent 类,以 ES2015 class 的方式方便地定义纯组件 (pure component)。这篇文章分析了一下源码实现,并衍生探讨了下 shallowCo...

    ZHAO_ 评论0 收藏0
  • 基于React版本16.4的源码解析(一)

    摘要:本次分析的源码采用的是的版本核心接口提供了处理的工具集我们先来看看做了什么事情即当为空时,返回不为时调用,最终返回一个数组这里说一下,可以通过传入的对所有子组件进行操作,具体使用方法看下图参数通过配合的例子把父组件的赋值给每个子组件我们先不 本次分析的源码采用的是16.4.1的版本 核心接口 showImg(https://segmentfault.com/img/bVbeT9f?w=...

    joywek 评论0 收藏0
  • 如何优化你的超大型React应用 【原创精读】

    摘要:往往纯的单页面应用一般不会太复杂,所以这里不引入和等等,在后面复杂的跨平台应用中我会将那些技术一拥而上。构建极度复杂,超大数据的应用。 showImg(https://segmentfault.com/img/bVbvphv?w=1328&h=768); React为了大型应用而生,Electron和React-native赋予了它构建移动端跨平台App和桌面应用的能力,Taro则赋...

    cfanr 评论0 收藏0
  • 如何优化你的超大型React应用 【原创精读】

    摘要:往往纯的单页面应用一般不会太复杂,所以这里不引入和等等,在后面复杂的跨平台应用中我会将那些技术一拥而上。构建极度复杂,超大数据的应用。 showImg(https://segmentfault.com/img/bVbvphv?w=1328&h=768); React为了大型应用而生,Electron和React-native赋予了它构建移动端跨平台App和桌面应用的能力,Taro则赋...

    codecook 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<