Remix 3 を実際に動かしてみる
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
1import { defineConfig } from "rolldown";23export default defineConfig({4 input: "src/index.tsx",5 output: {6 file: "public/bundle.js",7 },8 resolve: {9 alias: {10 "react/jsx-runtime": "@remix-run/dom/jsx-runtime",11 "react/jsx-dev-runtime": "@remix-run/dom/jsx-dev-runtime",12 },13 },14});
typescript の設定
Remix3 の jsx の形式を有効にするため、jsxImportSourceを@remix-run/domにする必要があります。
tsconfig.json
1{2 "compilerOptions": {3 "strict": true,4 "lib": ["ES2024", "DOM", "DOM.Iterable"],5 "module": "ES2022",6 "moduleResolution": "Bundler",7 "target": "ESNext",8 "allowImportingTsExtensions": true,9 "rewriteRelativeImportExtensions": true,10 "verbatimModuleSyntax": true,11 "skipLibCheck": true,12 "jsx": "react-jsx",13 "jsxImportSource": "@remix-run/dom"14 }15}
サンプルプログラム
カウントを回すだけ
まずはボタンを押して、カウントを表示するプログラムです。
これを見るとわかりますが、React との違いはステートが存在しないということです。React を初期から使っている人なら気がつくと思いますが、クラスコンポーネントのような動作を関数で行っています。再レンダリングの指示はthis.render()で能動的に呼び出します。
そして Remix3 ではイベントはすべてonに集約されており、そこでイベントを受け取ることになります。pressという、あらかじめ定義されているものを使っていますが、自分で定義することも可能です。
1import { createRoot, type Remix } from "@remix-run/dom";2import { press } from "@remix-run/events/press";34function App(this: Remix.Handle) {5 let count = 0;6 return () => (7 <button8 on={press(() => {9 count++;10 this.render();11 })}12 >13 Count: {count}14 </button>15 );16}1718createRoot(document.body).render(<App />);
マウント、アンマウントの使い方
動作確認用に Test というコンポーネントを作って、ボタンを押したら消滅するようにします。
これを動かすと、マウント時にconnectが、アンマウント時にdisconnectが呼び出されます。
そして現時点で未実装なのか、ref が動作していません。
1import { createRoot, type Remix } from "@remix-run/dom";2import { press } from "@remix-run/events/press";3import { connect, disconnect } from "@remix-run/dom";45function Test() {6 return (7 <div8 ref={(n) => {9 console.log(n);10 }}11 on={[12 connect(() => {13 console.log("mount");14 }),15 disconnect(() => {16 console.log("unmount");17 }),18 ]}19 >20 Test21 </div>22 );23}2425function App(this: Remix.Handle) {26 let count = 0;27 return () => (28 <>29 <button30 on={press(() => {31 count++;32 this.render();33 })}34 >35 Count: {count}36 </button>37 {count === 0 && <Test />}38 </>39 );40}4142createRoot(document.body).render(<App />);
その他のイベントの使い方
その他のイベントの使い方です。ここでの注意点ですが、Test コンポーネントは関数を戻しています。こうしないと、再レンダリングごとにmouseStateが初期化されてしまうので気をつけてください。
1import { createRoot, type Remix } from "@remix-run/dom";2import { press } from "@remix-run/events/press";3import { dom } from "@remix-run/events";45function Test(this: Remix.Handle) {6 let mouseState = "mouseOut";7 return ({ value }: { value: string }) => (8 <div9 on={[10 dom.mouseover(() => {11 mouseState = "mouseOver";12 this.render();13 }),14 dom.mouseout(() => {15 mouseState = "mouseOut";16 this.render();17 }),18 ]}19 >20 {value}:{mouseState}21 </div>22 );23}2425function App(this: Remix.Handle) {26 let count = 0;2728 return () => (29 <>30 <button31 on={[32 press(() => {33 count++;34 this.render();35 }),36 ]}37 >38 Count: {count}39 </button>40 <Test value="test" />41 </>42 );43}4445createRoot(document.body).render(<App />);
まとめ
取り急ぎ、動くものを作って動作を確認してみました。興味のある人は是非いじってみてください。