基於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博客】

相關文章
相關標籤/搜索