1. 程式人生 > >React系列文章:無狀態組件生成真實DOM結點

React系列文章:無狀態組件生成真實DOM結點

src str gree 其他屬性 引用 數據結構 alt lse 運行程序

在上一篇文章中,我們總結並模擬了JSX生成真實DOM結點的過程,今天接著來介紹一下無狀態組件的生成過程。

先以下面一段簡單的代碼舉例:

const Greeting = function ({name}) {
  return <div>{`hello ${name}`}</div>;
};

const App = <Greeting name="scott"/>;

console.log(App);

ReactDOM.render(App, document.getElementById(‘root‘));

可以看出,Greeting是一個無狀態組件,我們來看看編譯過後的可執行代碼:

var Greeting = function Greeting(_ref) {
  var name = _ref.name;

  return React.createElement(
    "div",
    null,
    "hello " + name
  );
};

var App = React.createElement(Greeting, { name: "scott" });

console.log(App);

ReactDOM.render(App, document.getElementById(‘root‘));

我們看到,調用Greeting組件時傳入的name

屬性,出現在React.createElement()方法的第二個參數中,這和前面介紹的JSX是一致的,不同的是,React.createElement()方法的第一個參數不再是一個標簽名,而是一個函數引用,指向了我們聲明的Greeting組件,而name屬性也作為參數的成員出現在組件內部,這個參數名為_ref,實則是我們熟知的props

下圖是我們運行上面代碼之後,打印出的App數據結構,即虛擬DOM結構:

技術分享圖片

我們再來看一個稍微復雜些的例子:

const Greeting = function ({name}) {
  return (
    <div>{`hello ${name}`}</div>
  );
};

const Container = function ({children}) {
  return (
    <div className="container">
      {children}
    </div>
  );
};

const App = (
  <Container>
    <Greeting name="scott"/>
    <Greeting name="jack"/>
    <Greeting name="john"/>
  </Container>
);

console.log(App);

ReactDOM.render(App, document.getElementById(‘root‘));

在上面代碼中,我們定義了兩個無狀態組件,其中Container用來作為外層的容器,Greeting則用來顯示實際的業務視圖。

現在再來看看編譯後的代碼結構:

var Greeting = function Greeting(_ref) {
  var name = _ref.name;

  return React.createElement(
    "div",
    null,
    "hello " + name
  );
};

var Container = function Container(_ref2) {
  var children = _ref2.children;

  return React.createElement(
    "div",
    { className: "container" },
    children
  );
};

var App = React.createElement(
  Container,
  null,
  React.createElement(Greeting, { name: "scott" }),
  React.createElement(Greeting, { name: "jack" }),
  React.createElement(Greeting, { name: "john" })
);

console.log(App);

ReactDOM.render(App, document.getElementById(‘root‘));

這次我們主要觀察Container的結構,它實際上是將React.createElement()方法的第三個參數作為props.children傳遞到了組件內部,而這個children是一個Greeting,最終是將Greeting渲染在Container組件內部。

接下來,我們要改進一下之前實現的React.createElement()ReactDOM.render()方法,使它們支持組件的形式,模擬生成虛擬DOM和真實DOM。

先來看看React.createElement()方法:

const React = {
  // 創建DOM描述對象 即虛擬DOM
  createElement(type, props, ...children) {
    let propsChildren = children;

    // 組件參數的props.children本身是數組
    // 所以調用組件函數時這裏需要特殊處理
    if (Array.isArray(children[0])) {
      propsChildren = children[0];
    }

    // 結點
    let vnode = {
      type,
      props: {
        ...props,
        children: propsChildren,
      }
    };

    // 掛載組件函數體的虛擬DOM
    if (typeof type === ‘function‘) {
      vnode.body = type({
        ...props,
        children,
      });
    }

    return vnode;
  }
};

上面的代碼主要對組件做了特殊處理。如果當前處理對象是組件,則對應的type就是函數的引用,我們會調用這個組件函數,然後將函數體的結果作為body屬性掛載到該結點上。需要註意的是,我們在上面方法參數中使用了可變參數的形式,如果直接引用這個children,它本身就是一個變參數組,如果組件體內使用了props.children,那麽在調用React.createElement()時,變參數組的形式將會是[[...]],所以我們需要特殊處理一下。

現在,我們運行程序,看看上面代碼生成的虛擬DOM結構:

技術分享圖片

最後,再來看看ReactDOM.render()方法:

const ReactDOM = {
  // 渲染真實DOM
  render(vnode, container) {
    let realDOM = this.generateDOM(vnode);
    container.appendChild(realDOM);
  },
  // 獲取真實DOM
  generateDOM(vnode) {
    if (typeof vnode.type === ‘function‘) {
      // 將組件函數體的虛擬DOM生成真實DOM
      return this.generateDOM(vnode.body);
    }

    let elem = document.createElement(vnode.type);
    // 特殊key值映射
    let specialKeyMap = {
      className: ‘class‘,
      fontSize: ‘font-size‘,
    };
    let {props} = vnode;

    // 設置DOM屬性
    props && Object.keys(props).forEach(key => {
      if (key === ‘children‘) {
        // 處理子節點
        props.children.forEach(child => {
          if (typeof child === ‘string‘) {
            // 純內容節點
            elem.appendChild(document.createTextNode(child));
          } else {
            // DOM節點
            elem.appendChild(this.generateDOM(child));
          }
        });
      } else if (key === ‘style‘) {
        // 設置樣式屬性
        let styleObj = props.style;
        let styleItems = [];

        Object.keys(styleObj).forEach(styleKey => {
          styleItems.push(`${specialKeyMap[styleKey] || styleKey}:${styleObj[styleKey]}`);
        });

        elem.setAttribute(‘style‘, styleItems.join(‘;‘));
      } else {
        // 設置其他屬性
        elem.setAttribute(specialKeyMap[key] || key, props[key]);
      }
    });

    return elem;
  }
};

上面代碼中改動較小,我們只添加了幾行針對組件的處理邏輯,如果是組件函數,則將函數體的虛擬DOM生成真實DOM。

最後,我們來看看最終生成的DOM結構:

技術分享圖片

React系列文章:無狀態組件生成真實DOM結點