Svelte でコンポーネントを作り React で糊付けする(ペライチ React で Signals 編)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187200/9e766690-5e30-417d-b1c1-b321f60397e9.png)

# はじめに

[前々々回](https://qiita.com/uniho/items/bf00c2eb5b24e4212a2f)、[前々回](https://qiita.com/uniho/items/c3dc6ace0990306a7afe) 、[前回](https://qiita.com/uniho/items/4ee166f1f0ee4ca7ba88) と、誰もついてこられない、実用性とは程遠いコードを示して自己満足していましたが、引き続き屍の山を築いていきたい所存です。

Svelte を使うと、いわゆる Signals が簡単に実現できます。というか読み進めていただければわかるのですがあっさり自然に実現できますので、Signals が標準搭載されているといってもよいのかもしれません。

ペライチ React なサンプルコードを示し、これまで解説していない部分についてのみ解説します。

# サンプルコード

``` index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="initial-scale=1, width=device-width" />
  </head>
  <body>
    <div id="app"></div>

    <script type="importmap">
      {
        "imports": {
          "svelte/": "https://esm.sh/svelte@5/"
        }
      }
    </script>

    <script type="module">

import React from "https://esm.sh/react@19?dev";
import ReactDOM from "https://esm.sh/react-dom@19?dev";
import ReactDOMClient from "https://esm.sh/react-dom@19/client?dev";

import {compile} from 'svelte/compiler';
import {mount, unmount} from 'svelte/';
import {proxy} from 'svelte/internal/client';

//
const sv = compile(`
  <script>
    const {name, count, setCount} = $props();
  <\/script>   <!-- ペライチの場合、\ を付けないと script タグの閉じと誤認識されてしまう? -->

  <button onclick={() => setCount(count()+1)}>
    {name}: {count()}
  </button>
`, {});

console.log(sv.js.code);

//
const createSignal = value => {
  // ⇓ const signal = $state({value});
  const signal = proxy({value}); 
  return [() => signal.value, next => signal.value = next];
}

//
const [count, setCount] = createSignal(0);
const [countOthers, setCountOthers] = createSignal(0);

//
const App = props => {

  const refDiv = {react: React.useRef(), svelte: React.useRef(), others: React.useRef()};

  React.useEffect(() => {
    const controller = new AbortController();

    (async() => {
      const SVC = await import(`data:text/javascript,${sv.js.code}`);

      const svc1 = mount(SVC.default, {target: refDiv.react.current, props: {name: 'React', count, setCount}});
      const svc2 = mount(SVC.default, {target: refDiv.svelte.current, props: {name: 'Svelte', count, setCount}});
      const svc3 = mount(SVC.default, {target: refDiv.others.current, props: {name: 'Others', count: countOthers, setCount: setCountOthers}});

      controller.signal.addEventListener("abort", () => {
        unmount(svc1);
        unmount(svc2);
        unmount(svc3);
      });
    })();  

    return () => {
      controller.abort();
    };
  }, []);

  return React.createElement(React.Fragment, {}, [
    React.createElement('p', {key: 'Q'}, 'Which would you rather use?'),
    React.createElement('div', {key: 'react', ref: refDiv.react}),
    React.createElement('div', {key: 'svelte', ref: refDiv.svelte}),
    React.createElement('div', {key: 'others', ref: refDiv.others}),
  ]);
}

const root = ReactDOMClient.createRoot(document.getElementById("app"))
root.render(React.createElement(App))

    </script>

  </body>
</html>
```

実行すると次のような感じの描画になるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187200/aed4d1ec-e60e-4e3b-9729-fa643071b564.png)
React ボタンと Svelte ボタンは連動してカウントされ、 Others ボタンは独立してカウントされます。

# 解説

``` js
import {proxy} from 'svelte/internal/client';
```
`svelte/internal/client` モジュールは、本来は直接使用してはいけないものなのかもしれませんが使用します。心配な方は、後述する `createSignal()` を Svelte ソースからコンパイルして作成してください。

``` js
const sv = compile(`・・・
```
今回も Svelte コンポーネントがどのようなものになったかを確認してください。

``` js
const createSignal = value => {
  // ⇓ const signal = $state({value});
  const signal = proxy({value}); 
  return [() => signal.value, next => signal.value = next];
}
```
Signals を作成します。[SolidJS っぽいもの](https://docs.solidjs.com/concepts/signals) にしてみました。Signals のインターフェイスをどのようなものにするかは好みの問題と思われますが、もしお薦めがあればコメントで教えてください。
`proxy()` は、`$state()` が Svelte Compiler によって変換された後に置き換えられる関数です。直接利用が心配な方は、`createSignal()` を Svelte ソースからコンパイルして export してください。

``` js
const [count, setCount] = createSignal(0);
const [countOthers, setCountOthers] = createSignal(0);
```
Signals を作成しています。

``` js
      const svc1 = mount(SVC.default, {target: refDiv.react.current, props: {name: 'React', count, setCount}});
      const svc2 = mount(SVC.default, {target: refDiv.svelte.current, props: {name: 'Svelte', count, setCount}});
      const svc3 = mount(SVC.default, {target: refDiv.others.current, props: {name: 'Others', count: countOthers, setCount: setCountOthers}});

      controller.signal.addEventListener("abort", () => {
        unmount(svc1);
        unmount(svc2);
        unmount(svc3);
      });
```
３つ Svelte コンポーネントをマウントしています。others ボタンのみ別の signal を渡しています。
なお、`AbortController` にも `signal` プロパティがありますが別物なので混同しないでください。

``` js
  return React.createElement(React.Fragment, {}, [
    React.createElement('p', {key: 'Q'}, 'Which would you rather use?'),
    React.createElement('div', {key: 'react', ref: refDiv.react}),
    React.createElement('div', {key: 'svelte', ref: refDiv.svelte}),
    React.createElement('div', {key: 'others', ref: refDiv.others}),
  ]);
```
React コンポーネントを描画します。
JSX で表すと次のようになります。

``` jsx
return (<>
  <p key="Q">Which would you rather use?</p>
  <div key="react" ref={refDiv.react}></div>
  <div key="svelte" ref={refDiv.svelte}></div>
  <div key="others" ref={refDiv.others}></div>
</>);
```

# 最後に
そろそろ React & Svelte ハイブリッド、いいかもとなってきましたか？なってませんよね。ではまた。
Let’s have fun with React and Svelte!