【React源碼解讀】- 組件的實現

前言

react使用也有一段時間了,你們對這個框架褒獎有加,可是它究竟好在哪裏呢?
讓咱們結合它的源碼,探究一二!(當前源碼爲react16,讀者要對react有必定的瞭解)css

15397566862932

回到最初

根據react官網上的例子,快速構建react項目html

npx create-react-app my-app

cd my-app

npm start

打開項目並跑起來之後,暫不關心項目結構及語法糖,看到App.js裏,這是一個基本的react組件<App/> 咱們console一下,看看有什麼結果。前端

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
        </header>
      </div>
    );
  }
}

export default App;

console.log(<App/>)

15397572879758

能夠看到,<App/>組件實際上是一個JS對象,並非一個真實的dom。node

ES6 引入了一種新的原始數據類型Symbol,表示獨一無二的值。有興趣的同窗能夠去阮一峯老師的ES6入門詳細瞭解一下

上面有咱們很熟悉的props,ref,key,咱們稍微修改一下console,看看有什麼變化。react

console.log(<App key={1} abc={2}><div>你好,這裏是App組件</div></App>)

15397577334580

能夠看到,props,key都發生了變化,值就是咱們賦予的值,props中嵌套了children屬性。但是爲何咱們嵌入的是div,實際上倒是一個對象呢?git

打開源碼

/node_modules/react

15397580720896

首先打開index.jsgithub

'use strict';

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

能夠知道目前用上的是./cjs/react.development.js,直接打開文件。
根據最初的代碼,咱們組件<App/>用到了React.Component。找到React暴露的接口:npm

15397617558881

接着找到Component: 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 = {};

Component.prototype.setState = function (partialState, callback) {
  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

Component.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

上面就是一些簡單的構造函數,也能夠看到,咱們經常使用的setState是定義在原型上的2個方法。babel

至此,一個<App/>組件已經有一個大概的雛形:

15397595217487

到此爲止了嗎?這看了等於沒看啊,究竟組件是怎麼變成div的?render嗎?
但是全局搜索,也沒有一個function是render啊。

原來,咱們的jsx語法會被babel編譯的。

15397600724075

這下清楚了,還用到了React.createElement

createElement: createElementWithValidation,

經過createElementWithValidation,

function createElementWithValidation(type, props, children) {
······

  var element = createElement.apply(this, arguments);


  return element;
}

能夠看到,return了一個element,這個element又是繼承自createElement,接着往下找:

function createElement(type, config, children) {
  var propName = void 0;

  // Reserved names are extracted
  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 = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner
  };

······
  return element;
};

誒,這裏好像返回的就是element對象,再看咱們最初的<App/>的結構,是否是很像

15397606651880驗證一下咱們的探索究竟對不對,再每個方法上咱們都打上console,(注意,將App裏的子元素所有刪空,利於咱們觀察)

15397611759810

React.createElement 、 createElementWithValidation 、 createElement 、 ReactElement,經過這些方法,咱們用class聲明的React組件在變成真實dom以前都是ReactElement類型的js對象

createElementWithValidation:

  • 首先校驗type是不是合法的

15397657382603

  • 校驗了props是否符合設置的proptypes

15397667118968

  • 校驗了子節點的key,確保每一個數組中的元素都有惟一的key

15397667422295

createElement

  • type是你要建立的元素的類型,能夠是html的div或者span,也能夠是其餘的react組件,注意大小寫
  • config中包含了props、key、ref、self、source等

15397667913454

  • 向props加入children,若是是一個就放一個對象,若是是多個就放入一個數組。

15397668352993

  • 那若是type.defaultProps有默認的props時,而且對應的props裏面的值是undefined,把默認值賦值到props中

15397668766904

  • 也會對key和ref進行校驗

15397669476655

ReactElement

ReactElement就比較簡單了,建立一個element對象,參數裏的type、key、ref、props、等放進去,而後return了。最後調用Object.freeze使對象不可再改變。

組件的掛載

咱們上面只是簡單的探究了<App/>的結構和原理,那它到底是怎麼變成真實dom的呢

15397616989193

ReactDOM.render(<App />, document.getElementById('root'));

咱們接着用babel編譯一下:

15397619877496

原來ReactDOM.render調用的是render方法,同樣,找暴露出來的接口。

var ReactDOM = {
······
  render: function (element, container, callback) {
    return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
  },
······
};

它返回的是一個legacyRenderSubtreeIntoContainer方法,此次咱們直接打上console.log

15397629379495

這是打印出來的結果,

15397633591876

legacyRenderSubtreeIntoContainer
這個方法除主要作了兩件事:

  • 清除dom容器元素的子元素
while (rootSibling = container.lastChild) {
      {
        if (!warned && rootSibling.nodeType === ELEMENT_NODE && rootSibling.hasAttribute(ROOT_ATTRIBUTE_NAME)) {
          warned = true;
        }
      }
      container.removeChild(rootSibling);
    }
  • 建立ReactRoot對象

15397648731115

源碼暫時只讀到了這裏,關於React16.1~3的新功能,以及新的生命週期的使用和原理、Fiber到底是什麼,咱們將在後續文章接着介紹。

廣而告之

本文發佈於薄荷前端週刊,歡迎Watch & Star ★,轉載請註明出處。

歡迎討論,點個贊再走吧 。◕‿◕。 ~

相關文章
相關標籤/搜索