143. Virtual DOM IV - JSX 1

少し工夫が必要  - accepted / - tried

Reactを使っているなら、JSXは馴染みがあるでしょうか。

JSX syntaxをサポートすることで、transpilerはJavaScriptの中でもこんなスタンダードではない文法を理解することができる。

<p> this is <button className="button">button</button> </p>

理解してスタンダードのJavaScript functionに変換することができる。

React.createElement("p", null,
  " this is ",
  React.createElement("button", { className: "button" }, "button"),
  " ");

TypeScript Playgroundで試してみよう

Transpilerはどう動いているのか、まずシンプルなサンプルコードから見てみよう。

<a>bfe.dev</a>

まず、上記のコードからAST(Abstract Syntax Tree)が作られる。

AST Explorerを開いて、 上記のコードを打てば、右にASTが見れる、概ねこんな構造:

expression: JSXElement {
  openingElement: JSXOpeningElement {
    name: JSXIdentifier {
      name: "a"
    }
  }
  closingElement: JSXClosingElement {
    name: JSXIdentifier {
      name: "a"
    }
  }
  children: [
    JSXText {
      value: "bfe.dev"
    }
  ]
}

わかりやすいですね。 JSX Specそのままになっている:

JSXElement:
  JSXOpeningElement JSXChildren? JSXClosingElement

JSXOpeningElement:
  < JSXElementName JSXAttributes? >

JSXChildren:
  JSXChild JSXChildren?

JSXClosingElement:
  < / JSXElementName >

JSXChild:
  JSXText
  JSXElement
  { JSXChildExpression? }

上記のASTを持って、JavaScriptコードを生成するのは割と簡単、React.createElementを適切な場所に入れれば終わり。

React.createElement("p", null,
  " this is ",
  React.createElement("button", { className: "button" }, "button"),
  " ");

ちなみに、Reactのメソッドじゃなく、140. Virtual DOM III - Functional Componenth()を使うのも大丈夫。

h("p", null,
  " this is ",
  h("button", { className: "button" }, "button"),
  " ");

さあ、JSX Elementの文字列を処理できるparse() と generate()を実装してください。.

  1. React.createElementじゃなく、h()を使ってください。h() はテスト時バンドルされる。
  2. parserを再実装する目的ではないので、下記のミニマムのスペックを満たせば良い:
JSXElement:
  JSXOpeningElement JSXChildren? JSXClosingElement

JSXOpeningElement:
  < JSXElementName >

JSXChildren:
  JSXChild

JSXClosingElement:
  < / JSXElementName >

JSXChild:
  JSXText
  • 上記のnamingは従わなくてよい
  • 渡す文字列には改行がない、the whitespace rulesは無視して良い
  • HTML tag nameだけサポートすれば良い
  1. 便宜のため、単独でparse()はテストされることなく、parse()generate()は一緒にテストされる:
const result = eval(generate(parse('<a>bfe.dev</a>')))
expect(result).toEqual(h('a', null, 'bfe.dev'))
  1. 無効のJSXElement文字列が渡されたらエラーを吐くベキ、例えばJSXOpeningElement と JSXClosingElement がペアになっていない場合など。

    テストケースは全てのエラーケースを網羅する目的ではないので、一部だけカバーしている

考えを喋りながら。

(3)
(20)