基于React版本16.2.0的源码解析(一):组件实现(小白也可读)

我在学习过程当中喜欢作记录,分享的是我在前端之路上的一些积累和思考,也但愿能跟你们一块儿交流与进步。 这是个人 github博客,欢迎一块儿学习,欢迎star


本次分析的源码采用的是16.2.0的版本  目前网上现有的react源码分析文章基于的都是版本16之前的源码,入口和核心构造器不同了,以下图所示前端



本想借鉴前人的源码分析成果,奈何彻底对不上号,只好本身慢慢摸索
node

水平有限,若是有错误和疏忽的地方,还请指正。react


最快捷开始分析源码的办法

mkdir analyze-react@16.2.0
cd analyze-react@16.2.0
npm init -y
npm i react --save
复制代码

而后打开项目,进入node_nodules => react  先看入口文件ndex.js
git

'use strict';

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}
复制代码

看开发环境下的版本便可,压缩版本是打包到生产环境用的
程序员

打开图中文件github


核心接口

分析源码先找对外的暴露接口,固然就是react了,直接拉到最下面
算法

var React = {
  Children: {
    map: mapChildren,
    forEach: forEachChildren,
    count: countChildren,
    toArray: toArray,
    only: onlyChild
  },

  Component: Component,
  PureComponent: PureComponent,
  unstable_AsyncComponent: AsyncComponent,

  Fragment: REACT_FRAGMENT_TYPE,

  createElement: createElementWithValidation,
  cloneElement: cloneElementWithValidation,
  createFactory: createFactoryWithValidation,
  isValidElement: isValidElement,

  version: ReactVersion,
};
复制代码


ReactChildren

ReactChildren提供了处理 this.props.children 的工具集,跟旧版本的同样
npm

Children: {
    map: mapChildren,
    forEach: forEachChildren,
    count: countChildren,
    toArray: toArray,
    only: onlyChild
  },
复制代码


组件 

旧版本只有ReactComponent一种编程

新版本定义了三种不一样类型的组件基类ComponentPureComponent ,unstable_AsyncComponentbash

Component: Component,
PureComponent: PureComponent,
unstable_AsyncComponent: AsyncComponent,
复制代码

等下再具体看都是什么


生成组件

createElement: createElementWithValidation,
cloneElement: cloneElementWithValidation,
createFactory: createFactoryWithValidation,
复制代码


判断组件:isValidElement

校验是不是合法元素,只须要校验类型,重点是判断.$$typeof属性

function isValidElement(object) {
  return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
}
复制代码


 _assign

实际上是object-assign,但文中有关键地方用到它,下文会讲

var _assign = require('object-assign');
复制代码



React组件的本质 


组件本质是对象

不急着看代码,先经过例子看看组件是什么样子的 用creact-react-app生成一个最简单的react项目 在App.js文件加点东西,而后打印组件A看一下是什么


npm start复制代码

启动项目看看 


其实就是个对象,有不少属性,注意到props里面, 没有内容 


如今给组件A里面加一点内容

componentDidMount() {
    console.log('组件A',<A><span>加点内容看看</span></A>)
  }
复制代码



能够看到,props.children里面开始嵌套内容了 

以咱们聪明的程序员的逻辑思惟能力来推理一下,其实不断的页面嵌套,就是不断的给这个对象嵌套props而已 

不信再看一下 

componentDidMount() {
    console.log('组件A',<A><span>加点内容看看<a>不信再加多一点</a></span></A>)
  }
复制代码


虚拟DOM概念

因此到目前为止,咱们知道了react的组件只是对象,而咱们都知道真正的页面是由一个一个的DOM节点组成的,在比较原生的jQuery年代,经过JS来操纵DOM元素,并且都是真实的DOM元素,并且咱们都知道复杂或频繁的DOM操做一般是性能瓶颈产生的缘由, 因此React引入了虚拟DOM(Virtual DOM)的概念

总的提及来,不管多复杂的操做,都只是先进行虚拟DOM的JS计算,把这个组件对象计算好了之后,再一次性的经过Diff算法进行渲染或者更新,而不是每次都要直接操做真实的DOM。

在即时编译的时代,调用DOM的开销是很大的。而Virtual DOM的执行彻底都在Javascript 引擎中,彻底不会有这个开销。


组件的本源

知道了什么是虚拟DOM以及组件的本质后,咱们仍是来看一下代码吧 

先从生成组件开始切入,由于要生成组件就确定会去找组件是什么 

createElement: createElementWithValidation
复制代码

摘取一些核心概念出来看就好

function createElementWithValidation(type, props, children) {
  var element = createElement.apply(this, arguments);
  return element;
}
复制代码

能够看到,返回了一个element,这个元素又是由createElement方法生成的,顺着往下找

function createElement(type, config, children) {
  var props = {};
  var key = null;
  var ref = null;
  var self = null;
  var source = null;

  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
复制代码

返回的是ReactElement方法,感受已经很近了

var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner
  };
  return element;
};
复制代码

bingo,返回了一个对象,再看这个对象,是否是跟上面打印出来的对象格式很像?再看一眼

这就是组件的本源


组件三种基类

前面说了,版本16.2.0中,封装了三种组件基类:分别是组件、纯组件、异步组件

Component: Component,
PureComponent: PureComponent,
unstable_AsyncComponent: AsyncComponent,
复制代码

一个个去看一下区别在哪里,先看Component

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}
复制代码

很简单,一个构造函数,经过它构造的实例对象有四个私有属性,refs 则是个emptyObject,看名字就知道是空对象 这个emptyObject也是引入的插件

var emptyObject = require('fbjs/lib/emptyObject');复制代码

再去看PureComponentAsyncComponent,定义的时候竟然跟Component 是同样的


区别

区别呢?

这里就须要用到原型链方面的知识了 

虽然原型和继承在平常项目和工做中用的很少

但,那是由于咱们平时很大部分在面向过程编程,特别是业务代码,但想要进阶,就要去读别人的源码,去本身封装组件,这时它们就派上用场了,这就是为何它们很重要的缘由。


核心的方法,和属性,以及这三种组件直接的关系都是经过原型链的知识联系起来的,关键代码以下,我画了个简图,但愿能对看文章的各位有所帮助,若是有画错的,但愿能指正我

先上核心代码,一些细枝末节的代码暂时忽略

setStateforceUpdate这两个方法挂载在Component(组件构造器)的原型上

Component.prototype.setState = function (partialState, callback) {
  ...
};

Component.prototype.forceUpdate = function (callback) {
  ...
};
复制代码


接下来定义一个ComponentDummy,其实也是一个构造器,按照名字来理解就是“假组件”😂,它是当作辅助用的

ComponentDummy的原型指向Component的原型,这样它也能访问原型上面的共有方法和属性了,好比setStateforceUpdate

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
复制代码


下面这句话,假组件构造器ComponentDummy实例化出来一个对象pureComponentPrototype,而后把这个对象的constructor属性又指向了PureComponent,所以PureComponent也成为了一个构造器,也就是上面的第二种组件基类

var pureComponentPrototype = PureComponent.prototype = new ComponentDummy();
pureComponentPrototype.constructor = PureComponent;
复制代码


AsyncComponent基类也是同样

var asyncComponentPrototype = AsyncComponent.prototype = new ComponentDummy();
asyncComponentPrototype.constructor = AsyncComponent;
复制代码

可是AsyncComponent的原型多了一个方法render,看到了吗,妈妈呀,这就是render的出处

asyncComponentPrototype.render = function () {
  return this.props.children;
};
复制代码


因此到目前为止,能够得出一个原型图 



可是,有个问题来了,render方法挂载在AsyncComponent的原型上,那经过Component构造器构造出来的实例岂不是读不到render方法,那为何平常组件是这样写的?


其实还有两句代码,上面作了个小剧透的_assign

_assign(pureComponentPrototype, Component.prototype);
复制代码

_assign(asyncComponentPrototype, Component.prototype);
复制代码

每句话上面还特地有个注释,Avoid an extra prototype jump for these methods.,避免这些方法额外的原型跳转,先无论它,先看_assign作了什么

把Component的原型跟AsyncComponent的原型合并, 

那么到这里,答案就呼之欲出了,如此一来,AsyncComponent上面的render方法,不就至关于挂载到Component上面了吗?

以此类推,三种基类构造器最后都是基于同一个原型,共享因此方法,包括rendersetStateforceUpdate等等,最后的原型图应该就变成了这样


到这里,有个问题要思考的是:

既然最后三个基类共用同一个原型,那为何要分开来写? 中间还经过一个假组件构造器ComponentDummy来辅助构建两个实例  

源码还没读完,这个地方我目前还没弄明白,应该是后面三个基类又分别挂载了不同的方法,但愿有大佬能提早回答一下。


后话

感谢您耐心看到这里,但愿有所收获!

若是不是很忙的话,麻烦点个star⭐【Github博客传送门】,举手之劳,倒是对做者莫大的鼓励。

我在学习过程当中喜欢作记录,分享的是本身在前端之路上的一些积累和思考,但愿能跟你们一块儿交流与进步,更多文章请看【amandakelake的Github博客】

相关文章
相关标签/搜索