空雲 Blog

Eye catchRemix 3 を実際に動かしてみる

publication: 2025/10/13
update:2025/10/14

Remix3 とは

Remix 3 は、React から脱却し、Web 標準に基づいた新しいフルスタック Web フレームワークとして再設計されました。Remix 3 は鋭意開発中ですが、この記事では React 代替に相当する箇所を部分的に動かして、実際に動作を検証してみます。

サンプルコードのリポジトリ

https://github.com/SoraKumo001/remix3-sample01

環境の準備

SPA アプリケーションとして動作させるための環境を用意します。今回はビルドに rolldown を使用します。

rolldown の設定

普通にビルドすれば依存パッケージのバンドルが行われるのですが、react/jsx-runtimeに関しては@remix-run/dom/jsx-runtimeに変換する設定が必要になります。

  • rolldown.config.ts

import { defineConfig } from "rolldown"; export default defineConfig({ input: "src/index.tsx", output: { file: "public/bundle.js", }, resolve: { alias: { "react/jsx-runtime": "@remix-run/dom/jsx-runtime", "react/jsx-dev-runtime": "@remix-run/dom/jsx-dev-runtime", }, }, });

typescript の設定

Remix3 の jsx の形式を有効にするため、jsxImportSource@remix-run/domにする必要があります。

  • tsconfig.json

{ "compilerOptions": { "strict": true, "lib": ["ES2024", "DOM", "DOM.Iterable"], "module": "ES2022", "moduleResolution": "Bundler", "target": "ESNext", "allowImportingTsExtensions": true, "rewriteRelativeImportExtensions": true, "verbatimModuleSyntax": true, "skipLibCheck": true, "jsx": "react-jsx", "jsxImportSource": "@remix-run/dom" } }

サンプルプログラム

カウントを回すだけ

まずはボタンを押して、カウントを表示するプログラムです。
これを見るとわかりますが、React との違いはステートが存在しないということです。React を初期から使っている人なら気がつくと思いますが、クラスコンポーネントのような動作を関数で行っています。再レンダリングの指示はthis.render()で能動的に呼び出します。
そして Remix3 ではイベントはすべてonに集約されており、そこでイベントを受け取ることになります。pressという、あらかじめ定義されているものを使っていますが、自分で定義することも可能です。

import { createRoot, type Remix } from "@remix-run/dom"; import { press } from "@remix-run/events/press"; function App(this: Remix.Handle) { let count = 0; return () => ( <button on={press(() => { count++; this.render(); })} > Count: {count} </button> ); } createRoot(document.body).render(<App />);

マウント、アンマウントの使い方

動作確認用に Test というコンポーネントを作って、ボタンを押したら消滅するようにします。
これを動かすと、マウント時にconnectが、アンマウント時にdisconnectが呼び出されます。
そして現時点で未実装なのか、ref が動作していません。

import { createRoot, type Remix } from "@remix-run/dom"; import { press } from "@remix-run/events/press"; import { connect, disconnect } from "@remix-run/dom"; function Test() { return ( <div ref={(n) => { console.log(n); }} on={[ connect(() => { console.log("mount"); }), disconnect(() => { console.log("unmount"); }), ]} > Test </div> ); } function App(this: Remix.Handle) { let count = 0; return () => ( <> <button on={press(() => { count++; this.render(); })} > Count: {count} </button> {count === 0 && <Test />} </> ); } createRoot(document.body).render(<App />);

その他のイベントの使い方

その他のイベントの使い方です。ここでの注意点ですが、Test コンポーネントは関数を戻しています。こうしないと、再レンダリングごとにmouseStateが初期化されてしまうので気をつけてください。

import { createRoot, type Remix } from "@remix-run/dom"; import { press } from "@remix-run/events/press"; import { dom } from "@remix-run/events"; function Test(this: Remix.Handle) { let mouseState = "mouseOut"; return ({ value }: { value: string }) => ( <div on={[ dom.mouseover(() => { mouseState = "mouseOver"; this.render(); }), dom.mouseout(() => { mouseState = "mouseOut"; this.render(); }), ]} > {value}:{mouseState} </div> ); } function App(this: Remix.Handle) { let count = 0; return () => ( <> <button on={[ press(() => { count++; this.render(); }), ]} > Count: {count} </button> <Test value="test" /> </> ); } createRoot(document.body).render(<App />);

まとめ

取り急ぎ、動くものを作って動作を確認してみました。興味のある人は是非いじってみてください。