React(虛擬)DOM術(shù)語[一源看世界][之React]

閱讀源碼前,提前了解下作者對一些術(shù)語的定義,有助于更好地理解源碼。以下為根據(jù)官方文檔關(guān)于React (Virtual) DOM Terminology描述進行翻譯并配上源碼,以更深入地了解這些概念

在React的名詞術(shù)語中,有五種主要的類型需要加以區(qū)別
. ReactElement / ReactElement Factory
. ReactNode
. ReactComponent / ReactComponent Class


React Elements

React中主要的類型是ReactElement,它有四種屬性: type,props,keyref。從以下源碼可以看出它就是一個普通的對象,通過$$typeof屬性來識別是否是React Element,值為Symbol.for('react.element’)或60103

var ReactElement = function(type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allow 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;
};

你可以通過React.createElement來創(chuàng)建這些對象。

var root = React.createElement('div');

我們來看下React.createElement的源碼,看看它做了啥?

ReactElement.createElement = function(type, config, children) {
  ...

  if (config != null) {
    ...

    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    for (propName in config) {
      if (hasOwnProperty.call(config, propName) &&
          !RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  var childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    var childArray = Array(childrenLength);
    for (var i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  // Resolve default props
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  ...
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props
  );
};

從源碼中我們可以看到:

  1. config中可以包含保留屬性ref,key__self,__source
  2. React Element的props屬性包含了除保留屬性外的所有config的屬性值,children和type(通過React.createClass創(chuàng)建的類或稱構(gòu)造函數(shù))的defaultProps的所有屬性值
  3. ReactElement.createElement可以傳不止3個參數(shù),第3個以及后面的參數(shù)都作為child傳進來,如:
var root = React.createElement('ul', { className: 'my-list' }, child1, child2);

你可以創(chuàng)建多個ReactElement,它們之間的父子包含關(guān)系形成了一個樹結(jié)構(gòu),將最top的ReactElement和它將所在的容器(一個普通的DOM element,可以是HTMLElement 或者 SVGElement)傳給ReactDOM.render方法,這樣就可以在頁面上渲染出這個組件及其所包含的子組件

ReactElement不是DOM element,它是DOM element的一個輕量的,無狀態(tài)的,不可變的虛擬表示,就是傳說中的虛擬DOM

ReactDOM.render(root, document.getElementById('example'));

可以通過傳遞一個屬性對象給第2個參數(shù)為組件的DOM element增加屬性,可以通過傳遞子React element對象給第3個參數(shù)為組件DOM element增加子的DOM element

var child = React.createElement('li', null, 'Text Content');
var root = React.createElement('ul', { className: 'my-list' }, child);
ReactDOM.render(root, document.getElementById('example'));

如果你用JSX,那么以下代碼跟上面的代碼是等價,JSX編譯器會將以下JSX語法解析成上面的JS代碼

var root = <ul className="my-list">
             <li>Text Content</li>
           </ul>;
ReactDOM.render(root, document.getElementById('example'));

看累了嗎?小休息一下再接著看唄?。。?/p>


Factories

ReactElement-factory是一個可以生成特定type的ReactElement對象的工廠函數(shù)。React內(nèi)置了有用的創(chuàng)建工廠函數(shù)的方法

ReactElement.createFactory = function(type) {
  var factory = ReactElement.createElement.bind(null, type);
  ...
  return factory;
};

這里小普及一下Function.prototype.bind函數(shù),它是用來創(chuàng)建一個新的函數(shù),新的函數(shù)的this關(guān)鍵字指向第1個參數(shù)(如果為null則保持不變),第2, ...個參數(shù)的值作為參數(shù)值按順序傳給原來的函數(shù)。舉個栗子吧

var foo = function(p1, p2) { console.log(this, p1, p2); }

var barContext = {};
var bar = foo.bind(barContext, 'hello'); // bar函數(shù)的this關(guān)鍵字指向了barContext,傳給foo函數(shù)的p1參數(shù)的值為'hello'

bar('daniel') // 相當于 foo('hello', 'daniel'),但兩個函數(shù)this關(guān)鍵字指向不同

有了Factory工廠函數(shù)你就不用每次都敲打React.createElement('div')了。- Well done,程序員就應該能懶則懶

var div = React.createFactory('div');
var root = div({ className: 'my-div' });
ReactDOM.render(root, document.getElementById('example'));

React本身也內(nèi)置了很多針對一般的HTML標簽的Factory方法

var root = React.DOM.ul({ className: 'my-list' },
             React.DOM.li(null, 'Text Content')
           );

React Nodes

一個React Node可以是以下:
. React Element
. string (亦稱ReactText)
. number (亦稱ReactText)
. 一組React Node (亦稱ReactFragment)
它們已作為其它React Element的屬性的形式(props.children)來表示子元素,從而創(chuàng)建了一個樹結(jié)構(gòu)的組件


React Components

一個ReactComponent Class就是一個javascript類(或稱為構(gòu)造函數(shù)),它同時也稱為某個ReactElement的類型(type)

var MyComponent = React.createClass({
  render: function() {
    ...
  }
});

當這個構(gòu)造函數(shù)被調(diào)用時,返回的是一個至少有render方法的對象,這個對象被稱為ReactComponent(即ReactComponent Class的實例)

var component = new MyComponent(props); // 千萬不要這樣去創(chuàng)建ReactComponent

除了測試,不要自己去調(diào)用構(gòu)造函數(shù),交給React來處理這事就好。
將ReactComponent Class傳給createElement方法,返回一個ReactElement

var element = React.createElement(MyComponent);

或者使用JSX:

var element = <MyComponent />;

當這個element傳給ReactDom.render方法時,React會幫你調(diào)用構(gòu)造函數(shù)并返回ReactComponent

var component = ReactDOM.render(element, document.getElementById('example'));

如果你傳入相同類型的ReactElement和相同的DOM容器繼續(xù)調(diào)用ReactDOM.render,它會一直返回同一個實例。這個實例是有狀態(tài)的

var componentA = ReactDOM.render(<MyComponent />, document.getElementById('example'));
var componentB = ReactDOM.render(<MyComponent />, document.getElementById('example'));
componentA === componentB; // true

從源碼中src/renderers/dom/client/ReactMount.js來驗證這個邏輯。

 _renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
    ...

    var prevComponent = getTopLevelWrapperInContainer(container);

    if (prevComponent) {
      var prevWrappedElement = prevComponent._currentElement;
      var prevElement = prevWrappedElement.props;
      if (shouldUpdateReactComponent(prevElement, nextElement)) {
        var publicInst = prevComponent._renderedComponent.getPublicInstance();
        var updatedCallback = callback && function() {
          callback.call(publicInst);
        };
        ReactMount._updateRootComponent(
          prevComponent,
          nextWrappedElement,
          container,
          updatedCallback
        );
        return publicInst;
      } else {
        ...
      }
    }
    ...
  },

可以看出是通過shouldUpdateReactComponent方法來判斷是否只更新并返回原來的實例publicInst,而shouldUpdateReactComponent針對ReactElement會判斷typekey屬性是否相等

這就是為什么不讓你自己創(chuàng)建ReactComponent的原因,因為React作了優(yōu)化的處理,可不是隨隨便便就New New New哦。

ReactComponentrender方法(就是你在聲明一個組件時定義的render方法)會返回另外一個ReactElement,所以這些Component可以進行組合。最終會解析成DOM element實例并插入到document中


正式的類型定義

入口

ReactDOM.render = (ReactElement, HTMLElement | SVGElement) => ReactComponent;

Nodes 和 Elements

type ReactNode = ReactElement | ReactFragment | ReactText;

type ReactElement = ReactComponentElement | ReactDOMElement;

type ReactDOMElement = {
  type : string,
  props : {
    children : ReactNodeList,
    className : string,
    etc.
  },
  key : string | boolean | number | null,
  ref : string | null
};

type ReactComponentElement<TProps> = {
  type : ReactClass<TProps> | ReactFunctionalComponent<TProps>,
  props : TProps,
  key : string | boolean | number | null,
  ref : string | null
};

type ReactFragment = Array<ReactNode | ReactEmpty>;

type ReactNodeList = ReactNode | ReactEmpty;

type ReactText = string | number;

type ReactEmpty = null | undefined | boolean;

Classes and Components

type ReactClass<TProps> = (TProps) => ReactComponent<TProps>;

type ReactComponent<TProps> = {
  props : TProps,
  render : () => ReactElement
};

type ReactFunctionalComponent<TProps> = (TProps) => ReactElement;

最后,期待吐槽,期待指教?。?!

--EOF--

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 原教程內(nèi)容詳見精益 React 學習指南,這只是我在學習過程中的一些閱讀筆記,個人覺得該教程講解深入淺出,比目前大...
    leonaxiong閱讀 2,954評論 1 18
  • 以下內(nèi)容是我在學習和研究React時,對React的特性、重點和注意事項的提取、精練和總結(jié),可以做為React特性...
    科研者閱讀 8,424評論 2 21
  • 本筆記基于React官方文檔,當前React版本號為15.4.0。 1. 安裝 1.1 嘗試 開始之前可以先去co...
    Awey閱讀 7,936評論 14 128
  • GUIDS 第一章 為什么使用React? React 一個提供了用戶接口的JavaScript庫。 誕生于Fac...
    jplyue閱讀 3,716評論 1 11
  • 無端的指責 莫名的猜忌 無理的懷疑 陷入了僵局 我不再解釋 你沒有追問 好似陌生人 緣分就此斷裂 彼此沒了留戀 從...
    花少顏閱讀 173評論 0 4

友情鏈接更多精彩內(nèi)容