react——jsx源碼解析

背景

爲了讓你們深入理解 JSX 的含義。有必要簡單介紹了一下 JSX 稍微底層的運做原理,這樣你們能夠更加深入理解 JSX 究竟是什麼東西,爲何要有這種語法,它是通過怎麼樣的轉化變成頁面的元素的。css

思考一個問題:如何用 JavaScript 對象來表現一個 DOM 元素的結構,舉個例子:react

<div class='box' id='content'>
  <div class='title'>Hello</div>
  <button>Click</button>
</div>
複製代碼

每一個 DOM 元素的結構均可以用 JavaScript 的對象來表示。你會發現一個 DOM 元素包含的信息其實只有三個:標籤名,屬性,子元素。git

因此其實上面這個 HTML 全部的信息咱們均可以用合法的 JavaScript 對象來表示:github

{
  tag: 'div',
  attrs: { className: 'box', id: 'content'},
  children: [
    {
      tag: 'div',
      arrts: { className: 'title' },
      children: ['Hello']
    },
    {
      tag: 'button',
      attrs: null,
      children: ['Click']
    }
  ]
}
複製代碼

你會發現,HTML 的信息和 JavaScript 所包含的結構和信息實際上是同樣的,咱們能夠用 JavaScript 對象來描述全部能用 HTML 表示的 UI 信息。可是用 JavaScript 寫起來太長了,結構看起來又不清晰,用 HTML 的方式寫起來就方便不少了。算法

因而 React.js 就把 JavaScript 的語法擴展了一下,讓 JavaScript 語言可以支持這種直接在 JavaScript 代碼裏面編寫相似 HTML 標籤結構的語法,這樣寫起來就方便不少了。編譯的過程會把相似 HTML 的 JSX 結構轉換成 JavaScript 的對象結構。canvas

編譯前代碼數組

import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import './index.css'

class Header extends Component {
  render () {
    return (
      <div>
        <h1 className='title'>React 小書</h1>
      </div>
    )
  }
}

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

複製代碼

通過編譯之後會變成:瀏覽器

import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import './index.css'

class Header extends Component {
  render () {
    return (
     React.createElement(
        "div",
        null,
        React.createElement(
          "h1",
          { className: 'title' },
          "React 小書"
        )
      )
    )
  }
}

ReactDOM.render(
  React.createElement(Header, null), 
  document.getElementById('root')
);
複製代碼

React.createElement 會構建一個 JavaScript 對象來描述你 HTML 結構的信息,包括標籤名、屬性、還有子元素等。這樣的代碼就是合法的 JavaScript 代碼了。因此使用 React 和 JSX 的時候必定要通過編譯的過程。bash

這裏再重複一遍:所謂的 JSX 其實就是 JavaScript 對象。每當在 JavaScript 代碼中看到這種 JSX 結構的時候,腦子裏面就能夠自動作轉化,這樣對你理解 React.js 的組件寫法頗有好處。app

實現

注意到的是咱們的JSX最終轉化成爲的是React.createElement這個方法:

第一個參數是字符串類型或者組件或者symbol,

表明的是標籤元素, 如div, span
classComponent或者是functional Component,
原生提供的Fragment, AsyncMode等, 會被特殊處理
複製代碼

第二個參數是一個對象類型, 表明標籤的屬性, id, class

其他的參數表明的是children,不涉及grand-children,當子節點中有孫節點的時候, 再遞歸使用React.createElement方法

const App = () => {
  return <div id="app" key="key">
    <section>
    	<img />
    </section>
    <span>span</span>
  </div>
}

"use strict";

var App = function App() {
  return React.createElement(
      "div", 
      {id: "app",key: "key"}, 
      React.createElement("section", null, 
              React.createElement("img", null)
          ), 
      React.createElement("span", null, "span"));
};

複製代碼

當第一個參數是組件的時候,第一個參數是做爲變量傳入的, 能夠想像的是, React.createElement內部有一個簡單的判斷, 若是傳入的是組件的話, 內部還會調用React.createElement方法

const Child = () => {
	return <div>Child</div>
}
const App = () => {
  return <div id="app">
    <Child />
  </div>
}

"use strict";

var Child = function Child() {
  return React.createElement("div", null, "Child");
};

var App = function App() {
  return React.createElement(
      "div", 
      {id: "app"}, 
      React.createElement(Child, null)); //這裏
}
}
複製代碼

須要注意的是若是組件開頭是一個小寫的話, 會被解析成簡單的字符串,在運行的時候就會報錯

咱們的createElement方法定義在packages/src/ReactElement.js

export function createElement(type, config, children) {
  let propName;
  const props = {};
  
  let key = null;
  let ref = null;
  let self = null;
  let source = null;
   
   // ref和key和其餘props不一樣, 進行單獨處理
  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;
    //將屬性名掛載到props上
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
  //第三個及以上的參數都被看做是子節點
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    //當數組長度肯定時,這種方式比push要節省內存  
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray; 
  // merge defaultProps
  if (type && type.defaultProps) {
    const 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,
  );
}



複製代碼

ReactElement定義以下

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // 每一個react element的$$typeof屬性都是REACT_ELEMENT_TYPE
    $$typeof: REACT_ELEMENT_TYPE, // react element的內置屬性
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner, //建立該元素的component
  };
  return element;
};
複製代碼

總的來講就是返回一個react element, 該element帶有props, refs, type

因此能夠總結一下從 JSX 到頁面到底通過了什麼樣的過程:

第一個緣由是,當咱們拿到一個表示 UI 的結構和信息的對象之後,不必定會把元素渲染到瀏覽器的普通頁面上,咱們有可能把這個結構渲染到 canvas 上,或者是手機 App 上。因此這也是爲何會要把 react-dom 單獨抽離出來的緣由,能夠想象有一個叫 react-canvas 能夠幫咱們把 UI 渲染到 canvas 上,或者是有一個叫 react-app 能夠幫咱們把它轉換成原生的 App(實際上這玩意叫 ReactNative)。

第二個緣由是,有了這樣一個對象。當數據變化,須要更新組件的時候,就能夠用比較快的算法操做這個 JavaScript 對象,而不用直接操做頁面上的 DOM,這樣能夠儘可能少的減小瀏覽器重排,極大地優化性能。這個在之後的章節中咱們會提到。

JSX的特色

  • 類XML語法 ,容易接受
  • 加強JS語義
  • 結構清晰
  • 抽象程度高
  • 代碼模塊化

未完待續 源碼實現1 源碼實現2

相關文章
相關標籤/搜索