平成のjQueryに令和のJSXを

-- Views

June 15, 26

スライド概要

React等フレームワークを使わず、自力でJSXをDOMに変換してみます。

profile-image

プログラミングが好きで、LTやってます

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

平成のjQueryに令和の JSXを $('ul').append(<li>追加リスト</li>) 2026/06/15 俺たちの勉強会 #6 #orestudy

2.

自己紹介: murasuke murasuke(むらすけ) 株式会社ツールラボ 開発部 部長 SE/プログラマーとして20年、多種多様なシステムを構築 プログラムが書きたいので転職しました (現職) 1年前、人生初のイベント参加(PHPカンファレンス名古屋)を きっかけにして、フロカン名古屋で初登壇 してきました!

3.

はじめに ● 皆さん、 jQuery使ってますか? ● JSX書いてますか? ● 両方同時に使ったことある (酔狂な)人いますか!!?? ご存じの通り、ReactとjQueryは相性が良くありません。 ReactはVirtual DOMを管理し、jQueryは直接DOMを操作するためです。 render()のタイミングでDOMが再生成され、jQueryのコードは消えてしまいます。

4.

JSXはVirtual DOMを作るための仕組みではない JSXは単なる関数呼び出しなので、 その関数が Virtual DOM を返すか、 実DOMを返すかは実装次第です。 そこで今回は jQueryから操作可能な、実 DOMを生成する JSX という、「いいとこどり?」をやってみます! React等フレームワーク は使いません。 実DOMを返す「jsx-runtime」を自作して、JSXを自らの手 に取り戻しましょう!

5.
[beta]
JSX 基礎
JSXは「HTMLっぽい見た目をした、関数呼び出し(シンタックスシュガー)」です。
JSX はJavaScriptやブラウザの機能ではありません。
TypeScriptやBabel によって、ただの「関数呼び出し 」に変換されます。
例えば、このような感じです。
<!-- JSX -->

h(

<div className="box">

"div",

<h1>Hello</h1>

{ className: "box" },

</div>

h("h1", null, "Hello")
);

続いて「h(tag, props, childlen)」でDOMを生成して返す処理を実装していきます。

6.
[beta]
JSX 基礎
旧方式( classic runtime)
jsxFactoryで指定した関数に変換されます(cf. React.createElement())
h(

{
"jsx": "react",

"div",

"jsxFactory": "h"

{ className: "box" },

}

h("h1", null, "Hello")
);

新方式( automatic runtime)
TypeScriptがjsx-runtimeのimportを自動で挿入します
import { jsx as _jsx } from ./runtime/jsx-runtime";

{
"jsx": "react-jsx",
"jsxImportSource": "./runtime"
}

_jsx("div", {
className: "box",
children: _jsx("h1", {children: "Hello"})
});

多少の違いはありますが、本質的には変わりありません

7.
[beta]
JSXから実DOMへの変換
それでは関数 「h(tag, props, children)」 を定義し、
実DOMを生成する処理を実装して行きましょう!
<Border>
{counter}
<button style={{ color: 'blue', margin: '5px' }} onclick={handleInc}>Increment</button>
</Border>

① JSX ⇒ 関数への変換(トランスパイル)
② h(tag, props, children) 関数を実行して DOMを生成する
<div style="border: 1px solid black; padding: 5px; display: inline-block;">
1
<button style="color: blue; margin: 5px;" onclick="handleInc()">Increment</button>
</div>

8.
[beta]
1. タグ名が関数なら実行
大文字で始まるタグはFunctionに変換されるので、引数(props, children)をつけてそのま
ま呼び出します。
/* function h(tag: any, props: any, ...children: any[]) { */
if (typeof tag === 'function') {
return tag({
...(props || {}),
children,
});
}

※大文字で始まる「タグ名」に対応する「関数コンポーネント」は別途
個別に実装します。

9.

2. 普通のタグなら Element 作成 関数コンポーネント以外は、そのままElementを作成します。 /* function h(tag: any, props: any, ...children: any[]) { */ // elementを作成 const elm = document.createElement(tag);

10.
[beta]
3. props を属性へ変換
`on`から始まり、値が関数 の場合addEventListener()でイベントに紐づけます
if (key.startsWith('on') && typeof value === 'function') {
const eventName = key.slice(2).toLowerCase();
elm.addEventListener(eventName, value);
}

`style`の場合は、elementのstyleにマージ、`className`なら「class」として追加
if (key === 'style' && typeof value === 'object') {
Object.assign(elm.style, value);
} else if (key === 'className') {
elm.setAttribute('class', value);
} else if (value != null && value !== false) {
// 上記以外の属性を追加
elm.setAttribute(key, String(value));
}

余談:JSX的には「className」ではなく「class」でも大丈夫です (Reactの慣例に従っている )

11.

4. children を append JSXでは`children`に下記を含むことができます ● Node、 文字列、 配列、null、 boolean <div> <div>hello123</div> hello {[1,2,3]} </div> そのため種類ごとに処理を分ける必要があります 配列:再帰的に`appendChildren()`を適用 boolean:処理をスキップ(追加しない) Node:そのまま`appendChild()` 文字列:createTextNode()でNodeを作成してから追加

12.
[beta]
4. children を append
function appendChildren(parent: Node, children: any) {
if (Array.isArray(children)) {
children.forEach((child) => appendChildren(parent, child));
return;
}
if (children == null || children === false || children === true) {
return;
}
if (children instanceof Node) {
parent.appendChild(children);
return;
}
parent.appendChild(document.createTextNode(String(children)));
}

種類毎に処理を分けて、追加していきます

13.
[beta]
ランタイム全体像
export function h(tag: any, props: any, ...children: any[]) {

} else if (key === 'style' && typeof value === 'object') {

if (typeof tag === 'function') {

// styleの追加

// 大文字で始まるタグ(関数コンポーネント)を実行

Object.assign(elm.style, value);

return tag({

} else if (key === 'className') {

...(props || {}),

// classの追加

children,

elm.setAttribute('class', value);

});

} else if (value != null && value !== false) {

}

// 上記以外の属性を追加

// elementを作成

elm.setAttribute(key, String(value));

const elm = document.createElement(tag);

}
}

// 属性を追加

}

if (props) {
for (const [key, value] of Object.entries(props)) {

// 子要素の追加

if (key.startsWith('on') && typeof value === 'function') {

appendChildren(elm, children);

// イベントハンドラの追加

return elm;

const eventName = key.slice(2).toLowerCase();
elm.addEventListener(eventName, value);

}

14.

jQuery と組み合わせる ここでようやく本題です。 この「 h()」が返すのは「本物のDOM (HTMLElement)」です。 なので普通に 「jQuery」に渡せます。 $('ul').append(<li>追加リスト</li>); ついに「 jQueryとJSX」が、手を繋ぎあってくれました!

15.

型安全の恩恵も受けられる 例えば、jQueryでは単に文字列なのでタイプミスを検出できませんが、 でも、JSXならTypeScriptがエラー(タイプミス)を検出してくれます。 $('#app').append(<div clasName="box"> as any); さらにこのあたりも利用できるようになります。 ● 補完 ● lint ただし、jQueryはTypeScriptの型定義と相性が悪く、エラーだらけになるので jQuery側は「any」を多用することになります。 余談:jQueryなのにビルドが必要という時点で、あまり実用的ではないですよね・・・

16.
[beta]
動作サンプル
「Increment」ボタンをクリックすると+1するカウンターコンポーネント
export type Child = | Node | string | number | boolean
| null | undefined | Child[];
export type Props = {
style?: Partial<CSSStyleDeclaration>;
children?: Child;

型定義
Child:Node、文字、自身の配列
Props:css定義、Child以外は任意

[key: string]: unknown;
};
function Border({ children, ...props }: Props) {
return (
<div {...props}
style={{
border: '1px solid black', padding: '5px',
display: 'inline-block',
}}
> {children} </div>
);
}

Border
childrenに枠をつけるコンポーネント

17.
[beta]
動作サンプル
「Increment」ボタンをクリックすると+1するカウンターコンポーネント
let count = 0;
const counter = (<div>Count: {count}</div>) as HTMLDivElement;
const handleInc = () => {
counter.innerText = `Count: ${++count}`;
};
const elements = (
<Border>
{counter}
<button id="increment-button"
onclick={handleInc}>Increment</button>
</Border>
);
$('#app').append(elements as any);
$('#increment-button').css('color', 'blue').css('margin', '5px');

●

<Border>による枠の追加

●

jQueryによるDOM追加

●

onclickによるイベントハンドラ

●

jQueryによるスタイル定義

ともに想定した通りに動きました!

18.

まとめ 今回は 「jsx-runtime を自作し、 jQuery と JSX を共存させる」 と言う話をしました。 ● JSX はただの関数呼び出し ● コンポーネントは関数 ● JSX は自分で作れる ● 実DOMを返せば jQuery と共存可能 自作「 jsx-runtime」を通して、ReactやJSXの仕組みを「チョットダケ 」 理解できたのではないでしょうか?