WTF is JSX

May 19, 2018, 4 min read

這篇的切入點是給有學過或用過 React 的大家,接下來希望能提供一系列環繞 React 的教學文。

也是本人在深入學習 React 相關繼續的紀錄。

Overview

本篇會實做 React.createElement and ReactDOM.render 兩個 function 來了解 jsx。

大家在寫 React 一定脫離不了 Babel, 基本上就是直接使用 @babel/preset-env 包含所有的 Feature。

但是, 我們不一樣! 要用就要用最的,只拿我們用得到的。

$ yarn add @babel/cli @babel/core @babel/node @babel/plugin-transform-react-jsx

扒開 JSX 的皮吧! createElement

預設的 JSX transform 到底做了什麼?

先定義 .babelrc

// .babelrc
{
  "plugins": ["@babel/plugin-transform-react-jsx"]
}

我們來一步一步拆解,用一些簡單的 jsx 範例,會有什麼結果呢?

範例一

const HelloWorld = <div>Hello World!</div>;

執行 babel index.js 後, 會輸出

const HelloWorld = React.createElement("div", null, "Hello World!");

會發現其實就是轉成一個名為 React.createElement 的 Function 執行。 

範例二

const HelloWorld = <div id="hello-world" disable={true}>Hello World!</div>;

第 2 個變數就是用一個 object 表示 html attribute

const HelloWorld = React.createElement(
  "div",
  {
    id: "hello-world",
    disable: true
  },
  "Hello World!"
);

範例三

const HelloWorld = (
  <div>
    <div>Hello</div>
    <div>World</div>
    !
  </div>
);

第 3 個變數後,就是表示 elementchildren

const HelloWorld = React.createElement(
  "div",
  null,
  React.createElement("div", null, "Hello"),
  React.createElement("div", null, "World"),
  "!"
);

範例四

const HelloWorld = (
  <div id="id-parent">
    <div>Hello</div>
    <p class="text-center">
      <a href="/" target="_blank">World</a>
    </p>
    !
  </div>
);

那理所當然的輸出就是

const HelloWorld = React.createElement(
  "div",
  {
    id: "id-parent"
  },
  React.createElement("div",null, "Hello"),
  React.createElement(
    "p",
    {
      "class": "text-center"
    },
    React.createElement(
      "a",
      {
        href: "/",
        target: "_blank"
      },
      "World"
    )
  ),
  "!"
);

然而,這個 tranform 的結果,可以丟到 render() 取得最後的結果

$ yarn add react react-dom
const React = require('react');
const render = require('react-dom/server').renderToStaticMarkup;

const HelloWorld = (
  <div id="id-parent">
    <div>Hello</div>
    <p className="text-center">
      <a href="/" target="_blank">World</a>
    </p>
    !
  </div>
);

const staticString = render(HelloWorld);

console.log(staticString);

Exec: babel-node index.js Output:

<div id="id-parent"><div>Hello</div><p class="text-center"><a href="/" target="_blank">World</a></p>!</div>

那麼,知道這些能幹麻?

可以自己實做 React.createElement 以及 render !!

My JSX to JS

更改 .babelrcpragma 那麼之後的 React.createElement 就會變為 myCreateElement

{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "pragma": "myCreateElement" // default pragma is React.createElement
    }]
  ]
}

1. Implement myCreateElement

// dom.js
const myCreateElement = (nodeName, attrs, ...childrens) => ({
  nodeName,
  attrs,
  childrens
});

Sample Output:

{ nodeName: 'div',
  attrs: {
    id: 'id-parent'
  },
  childrens: [
    { nodeName: 'div', attrs: null, childrens: [Array] },
    { nodeName: 'p', attrs: [Object], childrens: [Array] },
    '!'
  ]
}

2. Implement render

// dom.js
const render = (domTree) => {
  if (typeof domTree === "string") return domTree;

  const { nodeName, attrs, childrens } = domTree;

  const attrsStr = Object
    .entries(attrs || {})
    .map(([key, val]) => ` ${key}="${val}"`)
    .join('');

  const childrenStrs = (childrens || []).map(c => render(c)).join('');

  return `<${nodeName}${attrsStr}>${childrenStrs}</${nodeName}>`;
}

完整的 dom.js

const myCreateElement = (nodeName, attrs, ...childrens) => ({
  nodeName,
  attrs,
  childrens
});

const render = (domTree) => {
  if (typeof domTree === "string") return domTree;

  const { nodeName, attrs, childrens } = domTree;

  const attrsStr = Object
    .entries(attrs || {})
    .map(([key, val]) => ` ${key}="${val}"`)
    .join('');

  const childrenStrs = (childrens || []).map(c => render(c)).join('');

  return `<${nodeName}${attrsStr}>${childrenStrs}</${nodeName}>`;
}

module.exports = {
  myCreateElement,
  render
}

然而丟回剛剛上面那段 code

-const React = require('react');
-const render = require('react-dom/server').renderToStaticMarkup;
+const { myCreateElement, render } = require('./dom');

const HelloWorld = (
  <div id="id-parent">
    <div>Hello</div>
    <p className="text-center">
      <a href="/" target="_blank">World</a>
    </p>
    !
  </div>
);

const staticString = render(HelloWorld);

console.log(staticString);

Output:

<div id="id-parent"><div>Hello</div><p class="text-center"><a href="/" target="_blank">World</a></p>!</div>

登登! 有沒有忽然覺得自己很像稍微了解 JSX 跟 React 之間的互動了呢?

希望本篇能給需要的人幫助 :)

Summary

實做簡單版的 React.createElement 以及 render 來理解 JSX。

希望各位喜歡 :3

Reference

Category react

Tags babel, react, js

Comments