JSXのファクトリ関数を自作する方法と、Reactと全然違う挙動をさせるサンプル

## ReactとJSX

---

```jsx
import React from "react";

const Component = () => <div />;
                        ^
                       JSX
```

このJSXはJSに変換するとどうなるんだろう？

---

BabelはこのプラグインでJSXをJSに変換している

```json:.babelrc
"plugins": ["@babel/plugin-transform-react-jsx"]
```

---

```jsx:1.js
import React from "react";

const Component = () => <div />;
```

変換してみます

```sh
$ npx babel 1.js
```

```js
import React from "react";

const Component = () => React.createElement("div", null);
```

---

つまりこういう風に変換される

```jsx
<div />
```

↓

```js
React.createElement("div", null)
```

なるほど。
色々なパターンを試してみましょう

---

属性を付けてみる

```jsx
<div id="container" />
```

↓

```js
React.createElement("div", {
  id: "container"
})
```

第二引数にObjectで渡される

---

タグの中身を入れてみる

```jsx
<div id="container">Content</div>
```

↓

```js
React.createElement("div", {
  id: "container"
}, "Content")
```

第三引数に文字列で渡される

---

JSXを入れ子にしてみる

```jsx
<div id="container">
  <h1>Heading</h1>
</div>
```

↓

```js
React.createElement("div", {
  id: "container"
}, React.createElement("h1", null, "Heading"))
```

文字列でなくReact.createElementになる

---

中身を複数入れてみる

```jsx
<div id="container">
  <h1>Heading</h1>
  <p>Paragraph</p>
</div>
```

↓

```js
React.createElement(
  "div",
  {
    id: "container"
  },
  React.createElement("h1", null, "Heading"),
  React.createElement("p", null, "Paragraph")
)
```

可変長引数だった

---

{}を使ってみる

```jsx
<div id={id}>
  <h1>Heading</h1>
  <p>Paragraph</p>
  {id}
</div>
```

↓

```js
React.createElement(
  "div",
  {
    id: id
  },
  React.createElement("h1", null, "Heading"),
  React.createElement("p", null, "Paragraph"),
  id
)
```

式がそのまま渡される

---

作ったコンポーネントを使ってみる

```jsx
const Child = ({ id }) => (
  <div id={id}>
    <h1>Heading</h1>
    <p>Paragraph</p>
    {id}
  </div>
);

const Parent = () => <Child id="child" />;
```

↓

```js
const Parent = () => React.createElement(Child, { id: "child" });
```

キャピタライズされたタグ名だと文字列でなくなる。
だからコンポーネントはクラスでも関数でもキャピタライズするんですね。

---

## 他のライブラリとJSX

---

PreactやHyperappを使うときは、pragmaプロパティにファクトリ関数`h`を指定する

```json:.babelrc
"plugins": [[
  "@babel/plugin-transform-react-jsx",
  { "pragma": "h" }
]]
```

そうすると、

```jsx
<div id="container">Content</div>
```

↓

```js
h("div", { id: "container" }, "Content")
```

未設定だと`React.createElement`に置き換えられるところが、`h`に置き換わる

---

React

```js
React.createElement("div", {
  id: "container"
}, "Content")
```

Preact / Hyperapp

```js
h("div", {
  id: "container"
}, "Content")
```

---

つまり、

- 第一引数に文字列または何らかのオブジェクト
- 第二引数にObject
- 以降可変長引数を受け取る

っていう関数を作って、transform-react-jsxプラグインのpragmaに設定すれば、オリジナルなJSXの挙動を作れる。

---

## ⚠ ここから先はグロテスクな表現が含まれています

---

こんなファクトリ関数を作ってみる

```js
const pragma = (func, _, ...args) =>
  func.apply(null, args);
```

第一引数に第三引数を渡してapplyする関数

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

---

こんなJSXを書いてみる

```jsx
<console.log>Hello World!</console.log>
```

変換するとこうなる

```
pragma(console.log, null, "Hello World!")
```

pragmaは第一引数に第三引数を渡してapplyする関数なので、こういうことですね

```
console.log.apply(null, "Hello World!")
```

---

つまり実行すると

```sh
$ npx babel-node main.js
Hello World!
```

うまく動きました。
では色々な関数を実行してみます。

---

```jsx
<console.log>
  <Math.sqrt>{2}</Math.sqrt>
  <Math.pow>
    {2}
    {4}
  </Math.pow>
  <Math.ceil>
    <Math.random />
  </Math.ceil>
</console.log>;
```

```sh
$ npx babel-node main.js
1.4142135623730951 16 1
```

面白いですね〜

---

私の中の悪いスイッチが入ってしまいまして、JSXでWebバックエンドも書きたくなってきました。

```js
const express = require("express");
const app = express();
const port = 3000;

app.get("/", (req, res) => res.send("Hello World!"));

app.listen(port, () => console.log(`Example app listening on port ${port}!`));
```

これを書き換えてみます。

---

```jsx
const express = <require>express</require>;
const app = <express />;
const port = 3000;

<app.get>
  /{(req, res) => <res.send>Hello World!</res.send>}
</app.get>;

<app.listen>
  {port}
  {() => <console.log>Example app listening on port {port}!</console.log>}
</app.listen>;
```

requireタグに戦慄を感じるのは私だけではないはずです

```sh
$ npx babel-node 13.js
13.js:1
const pragma = (func, _, ...args) => func.apply(null, args);
                                          ^
TypeError: func.apply is not a function
```

requireが"require"になってしまうので失敗します。

---

evalしちゃう。

```js
const pragma = (func, _, ...args) => {
  if (typeof func === "string") {
    func = eval(func);
  }
  return func.apply(null, args);
};
```

改めて

```sh
$ npx babel-node 14.js
node_modules/express/lib/application.js:479
    this.lazyrouter();
         ^
TypeError: Cannot read property 'lazyrouter' of null
```

`func.apply(null, args)`としててthisを正しく渡していないので、`<app.get>`などでエラーがでる

---

`<app.get this={app}>`という感じで渡すようにした

```js
const pragma = (func, props, ...args) => {
  if (typeof func === "string") {
    func = eval(func);
  }

  if (props && props.this) {
    return func.apply(props.this, args);
  } else {
    return func.apply(null, args);
  }
};
```

```jsx
const express = <require>express</require>;
const app = <express />;
const port = 3000;

<app.get this={app}>
  /{(req, res) => <res.send this={res}>Hello World!</res.send>}
</app.get>;

<app.listen this={app}>
  {port}
  {() => <console.log>Example app listening on port {port}!</console.log>}
</app.listen>;
```

---

```sh
$ npx babel-node 15.js
Example app listening on port  3000 !
```

起動した

```sh
$ curl http://localhost:3000
Hello World!
```

ちゃんと動いた
こうなるとエスカレートが止まりません

---

もっとJSX感がほしい

```jsx
<express callback={app => (
  <app.get this={app} />
) />
```

とできるようにファクトリを変更する

```js
const pragma = (func, props, ...args) => {
  if (typeof func === "string") {
    func = eval(func);
  }

  let returnValue;
  if (props && props.this) {
    returnValue = func.apply(props.this, args);
  } else {
    returnValue = func.apply(null, args);
  }

  if (props && props.callback) {
    props.callback(returnValue);
  }

  return returnValue;
};
```

---

溢れ出すJSX感

```jsx
const port = 3000;

<require
  callback={Express => (
    <Express
      callback={app => (
        <>
          <app.get this={app}>
            /{(req, res) => <res.send this={res}>Hello World!</res.send>}
          </app.get>
          <app.listen this={app}>
            {port}
            {() => (
              <console.log>Example app listening on port {port}!</console.log>
            )}
          </app.listen>
        </>
      )}
    />
  )}
>
  express
</require>;
```

---

コンポーネントぽく整理するとよりJSX感UP!

```jsx
const HelloHandler = () => (req, res) => (
  <res.send this={res}>Hello World!</res.send>
);

const ListenHandler = port => () => (
  <console.log>Example app listening on port {port}!</console.log>
);

const Config = port => app => (
  <>
    <app.get this={app}>
      /<HelloHandler />
    </app.get>
    <app.listen this={app}>
      {port}
      <ListenHandler>{port}</ListenHandler>
    </app.listen>
  </>
);

const App = port => Express => <Express callback={Config(port)} />;

<require callback={App(port)}>express</require>;
```

---

```sh
$ npx babel-node 17.js
Example app listening on port  3000 !
```

```sh
$ curl http://localhost:3000
Hello World!
```

動きますね
気を良くしたのでDBもつないじゃいます

---

元のDB処理サンプル

```js
const sqlite3 = require("sqlite3").verbose();

let db = new sqlite3.Database("./app.db", err => {
  if (err) {
    console.error(err.message);
  }
  console.log("Connected to the database.");
});

db.serialize(() => {
  db.run(
    `CREATE TABLE IF NOT EXISTS members (
      id integer PRIMARY KEY AUTOINCREMENT,
      name text NOT NULL
    )`,
    err => err && console.error(err.message)
  );

  db.run(`INSERT INTO members (name) VALUES ("Yamamoto")`, function(err) {
    if (err) {
      return console.error(err.message);
    }
    console.log(this.lastID);
  });

  db.all("SELECT * FROM members", (err, rows) => {
    if (err) {
      return console.error(err.message);
    }
    console.log(rows);
  });
});

db.close(err => {
  if (err) {
    console.error(err.message);
  }
  console.log("Close the database connection.");
});
```

---

newが出てきた。こういう感じで仕上げたい

```jsx
<sqlite3.Database new callback={Process}>
  ./app.db
  <DoneHandler>Connected to the database.</DoneHandler>
</sqlite3.Database>
```

new属性に対応する

```js
const pragma = (func, props, ...args) => {
  if (typeof func === "string") {
    func = eval(func);
  }

  let returnValue;
  if (props && props.this) {
    returnValue = func.apply(props.this, args);
  } else if (props && props.new) {
    returnValue = new (Function.prototype.bind.apply(func, [null, ...args]))();
  } else {
    returnValue = func.apply(null, args);
  }

  if (props && props.callback) {
    props.callback(returnValue);
  }

  return returnValue;
};
```

---

こんな感じになりました。
Markup LanguageとQuery Languageの絶妙な融合に酔いしれましょう。

```jsx
const Process = db => (
  <>
    <db.serialize this={db}>
      {() => (
        <>
          <db.run this={db}>
            CREATE TABLE IF NOT EXISTS members ( id integer PRIMARY KEY
            AUTOINCREMENT, name text NOT NULL )
            <DoneHandler>Created the table.</DoneHandler>
          </db.run>

          <db.run this={db}>
            INSERT INTO members (name) VALUES ("Yamamoto")
            <InsertHandler />
          </db.run>

          <db.all this={db}>
            SELECT * FROM members
            <AllHandler />
          </db.all>
        </>
      )}
    </db.serialize>

    <db.close this={db}>
      <DoneHandler>Close the database connection.</DoneHandler>
    </db.close>
  </>
);

const App = sqlite3 => (
  <sqlite3.Database new callback={Process}>
    ./app.db
    <DoneHandler>Connected to the database.</DoneHandler>
  </sqlite3.Database>
);

<require
  callback={sqlite3 => <sqlite3.verbose this={sqlite3} callback={App} />}
>
  sqlite3
</require>;

const DoneHandler = msg => err =>
  err ? (
    <console.error>{err.message}</console.error>
  ) : (
    <console.log>{msg}</console.log>
  );

const InsertHandler = () =>
  function(err) {
    return err ? (
      <console.error>{err.message}</console.error>
    ) : (
      <console.log>{this.lastID}</console.log>
    );
  };

const AllHandler = () => (err, rows) =>
  err ? (
    <console.error>{err.message}</console.error>
  ) : (
    <console.log>{rows}</console.log>
  );
```

---

そしてCRUDのREST APIにDB処理を組み込む

```jsx
const Config = (port, bodyParser, Cors, sqlite3) => app => (
  <>
    <app.use this={app}>
      <Cors />
    </app.use>
    <app.use this={app}>
      <bodyParser.json />
    </app.use>
    <app.get this={app}>
      /<HelloHandler />
    </app.get>
    <app.post this={app}>
      /members
      <CreateMemberHandler>{sqlite3}</CreateMemberHandler>
    </app.post>
    <app.get this={app}>
      /members
      <ListMembersHandler>{sqlite3}</ListMembersHandler>
    </app.get>
    <app.get this={app}>
      /members/:id
      <RetrieveMemberHandler>{sqlite3}</RetrieveMemberHandler>
    </app.get>
    <app.put this={app}>
      /members/:id
      <UpdateMemberHandler>{sqlite3}</UpdateMemberHandler>
    </app.put>
    <app.delete this={app}>
      /members/:id
      <DeleteMemberHandler>{sqlite3}</DeleteMemberHandler>
    </app.delete>
    <app.listen this={app}>
      {port}
      <ListenHandler>{port}</ListenHandler>
    </app.listen>
  </>
);

const App = (port, bodyParser, cors, Express, sqlite3) => (
  <>
    <QueryDatabase>
      {sqlite3}
      {db => (
        <db.run this={db}>
          CREATE TABLE IF NOT EXISTS members ( id integer PRIMARY KEY
          AUTOINCREMENT, name text NOT NULL )
          <DoneHandler>Created the table.</DoneHandler>
        </db.run>
      )}
    </QueryDatabase>
    <Express callback={Config(port, bodyParser, cors, sqlite3)} />
  </>
);

const HelloHandler = () => (req, res) => (
  <res.send this={res}>Hello World!</res.send>
);

const DoneHandler = msg => err =>
  err ? (
    <console.error>{err.message}</console.error>
  ) : (
    <console.log>{msg}</console.log>
  );

const QueryDatabase = (sqlite3, process) => (
  <sqlite3.Database
    new
    callback={db => (
      <>
        {process(db)}
        <db.close this={db}>
          <DoneHandler>Close the database connection.</DoneHandler>
        </db.close>
      </>
    )}
  >
    ./app.db
    <DoneHandler>Connected to the database.</DoneHandler>
  </sqlite3.Database>
);

const CreateMemberResponse = (req, res) =>
  function(err) {
    err ? (
      <>
        <console.error>{err.message}</console.error>
        <res.status
          this={res}
          callback={res => <res.send this={res}>{err.message}</res.send>}
        >
          {500}
        </res.status>
      </>
    ) : (
      <>
        <console.log>{this.lastID}</console.log>
        <res.json this={res}>
          {{ id: this.lastID, name: req.body.name }}
        </res.json>
      </>
    );
  };

const CreateMemberHandler = sqlite3 => (req, res) => (
  <QueryDatabase>
    {sqlite3}
    {db => (
      <db.run this={db}>
        INSERT INTO members(name) VALUES(?)
        {[req.body.name]}
        <CreateMemberResponse>
          {req}
          {res}
        </CreateMemberResponse>
      </db.run>
    )}
  </QueryDatabase>
);

const ListMembersResponse = res => (err, rows) =>
  err ? (
    <>
      <console.error>{err.message}</console.error>
      <res.status
        this={res}
        callback={res => <res.send this={res}>{err.message}</res.send>}
      >
        {500}
      </res.status>
    </>
  ) : (
    <res.json this={res}>{rows}</res.json>
  );

const ListMembersHandler = sqlite3 => (req, res) => (
  <QueryDatabase>
    {sqlite3}
    {db => (
      <db.all this={db}>
        SELECT * FROM members
        <ListMembersResponse>{res}</ListMembersResponse>
      </db.all>
    )}
  </QueryDatabase>
);

const RetrieveMemberResponse = res => (err, row) =>
  err ? (
    <>
      <console.error>{err.message}</console.error>
      <res.status
        this={res}
        callback={res => <res.send this={res}>{err.message}</res.send>}
      >
        {500}
      </res.status>
    </>
  ) : (
    <res.json this={res}>{row}</res.json>
  );

const RetrieveMemberHandler = sqlite3 => (req, res) => (
  <QueryDatabase>
    {sqlite3}
    {db => (
      <db.get this={db}>
        SELECT * FROM members WHERE id = ?{[req.params.id]}
        <ListMembersResponse>{res}</ListMembersResponse>
      </db.get>
    )}
  </QueryDatabase>
);

const UpdateMemberResponse = (req, res) =>
  function(err) {
    err ? (
      <>
        <console.error>{err.message}</console.error>
        <res.status
          this={res}
          callback={res => <res.send this={res}>{err.message}</res.send>}
        >
          {500}
        </res.status>
      </>
    ) : (
      <>
        <console.log>{this.changes}</console.log>
        <res.json this={res}>
          {{ id: req.params.id, name: req.body.name }}
        </res.json>
      </>
    );
  };

const UpdateMemberHandler = sqlite3 => (req, res) => (
  <QueryDatabase>
    {sqlite3}
    {db => (
      <db.run this={db}>
        UPDATE members SET name = ? WHERE id = ?{[req.body.name, req.params.id]}
        <UpdateMemberResponse>
          {req}
          {res}
        </UpdateMemberResponse>
      </db.run>
    )}
  </QueryDatabase>
);

const DeleteMemberResponse = (req, res) =>
  function(err) {
    err ? (
      <>
        <console.error>{err.message}</console.error>
        <res.status
          this={res}
          callback={res => <res.send this={res}>{err.message}</res.send>}
        >
          {500}
        </res.status>
      </>
    ) : (
      <>
        <console.log>{this.changes}</console.log>
        <res.status this={res} callback={res => <res.send this={res} />}>
          {204}
        </res.status>
      </>
    );
  };

const DeleteMemberHandler = sqlite3 => (req, res) => (
  <QueryDatabase>
    {sqlite3}
    {db => (
      <db.run this={db}>
        DELETE from members WHERE id = ?{[req.params.id]}
        <DeleteMemberResponse>
          {req}
          {res}
        </DeleteMemberResponse>
      </db.run>
    )}
  </QueryDatabase>
);

const ListenHandler = port => () => (
  <console.log>Example app listening on port {port}!</console.log>

// 美しくない！！
<require
  callback={bodyParser => (
    <require
      callback={cors => (
        <require
          callback={express => (
            <require
              callback={sqlite3 => (
                <sqlite3.verbose
                  this={sqlite3}
                  callback={sqlite3 =>
                    App(port, bodyParser, cors, express, sqlite3)
                  }
                />
              )}
            >
              sqlite3
            </require>
          )}
        >
          express
        </require>
      )}
    >
      cors
    </require>
  )}
>
  body-parser
</require>;
```

---

```sh
$ npx babel-node 21.js
Example app listening on port  3000 !
```

見事動きました

```sh
$ curl http://localhost:3000/members
[{"id":1,"name":"Yamamoto"}]

$ curl http://localhost:3000/members/1
{"id":1,"name":"Yamamoto"}

$ curl http://localhost:3000/members -X POST -H "Content-Type: application/json" -d '{"name": "Takahashi"}'
{"id":2,"name":"Takahashi"}

$ curl http://localhost:3000/members
[{"id":1,"name":"Yamamoto"},{"id":2,"name":"Takahashi"}]

$ curl http://localhost:3000/members/1 -X PUT -H "Content-Type: application/json" -d '{"name": "Suzuki"}'
{"id":"1","name":"Suzuki"}

$ curl http://localhost:3000/members
[{"id":1,"name":"Suzuki"},{"id":2,"name":"Takahashi"}]

$ curl http://localhost:3000/members/1 -X DELETE

$ curl http://localhost:3000/members
[{"id":2,"name":"Takahashi"}]
```

---

以上です。

コードはこちらです。
https://github.com/boiyaa/dont-use-jsx-like-this