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

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

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


React Elements

React中主要的類型是ReactElement,它有四種屬性: typepropskeyref。從以下源碼可以看出它就是一個普通的對象,通過$$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來創建這些對象。

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中可以包含保留屬性refkey__self__source
  2. React Element的props屬性包含了除保留屬性外的所有config的屬性值,children和type(通過React.createClass創建的類或稱構造函數)的defaultProps的所有屬性值
  3. ReactElement.createElement可以傳不止3個參數,第3個以及后面的參數都作為child傳進來,如:
var root = React.createElement('ul', { className: 'my-list' }, child1, child2);

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

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

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

可以通過傳遞一個屬性對象給第2個參數為組件的DOM element增加屬性,可以通過傳遞子React element對象給第3個參數為組件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'));

看累了嗎?小休息一下再接著看唄!!!


Factories

ReactElement-factory是一個可以生成特定type的ReactElement對象的工廠函數。React內置了有用的創建工廠函數的方法

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

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

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

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

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

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

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

React本身也內置了很多針對一般的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)來表示子元素,從而創建了一個樹結構的組件


React Components

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

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

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

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

除了測試,不要自己去調用構造函數,交給React來處理這事就好。
將ReactComponent Class傳給createElement方法,返回一個ReactElement

var element = React.createElement(MyComponent);

或者使用JSX:

var element = <MyComponent />;

當這個element傳給ReactDom.render方法時,React會幫你調用構造函數并返回ReactComponent

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

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

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屬性是否相等

這就是為什么不讓你自己創建ReactComponent的原因,因為React作了優化的處理,可不是隨隨便便就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--

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容

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