reflex-dev について
reflex-devは、JavaScriptを書かずにReflexとPythonでフルスタックWebアプリを構築するための開発スキルです。状態管理、コンポーネント、イベント処理、ルーティング、スタイル設定、開発手順を扱います。
主な機能
- 01Pythonでアプリを構築
- 02サーバー側の状態管理
- 03コンポーネントによるUI構成
- 04ユーザーイベントの処理
- 05ルートとレスポンシブレイアウトの設定
- 06アプリの実行とエクスポート
活用シーン
- 01インタラクティブなWebページの作成
- 02フォームやデータテーブルの構築
- 03アップロードとリアルタイム状態更新の処理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ silvainfm/ claude-skills --skill reflex-devインストール後、Claude Code で/reflex-devを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
examples/counter_app.pydata_table_crud.pyfile_upload.pyform_validation.pyREADME.mdreferences/patterns.md詳細
Reflex 開発
概要
Reflex は、JavaScript を記述せずに Web アプリケーションを構築できるフルスタック Python フレームワークです。アプリは React フロントエンドと FastAPI バックエンドにコンパイルされ、状態管理とイベントハンドラーはすべて Python で実行されます。
アーキテクチャ:
- フロントエンド:UI の描画用に React(JavaScript)へコンパイルされます
- バックエンド:Python のイベントハンドラーを実行する FastAPI サーバー
- 通信:リアルタイムの状態更新に WebSocket を使用します
- 状態:サーバー側の Python 状態がフロントエンドと同期されます
コアコンセプト
状態管理
状態は、変更可能なすべてのデータとイベントハンドラーを保持する Python クラスです。すべての状態変数は JSON でシリアライズ可能である必要があります。
import reflex as rx
class AppState(rx.State):
# State variables (any JSON-serializable type)
count: int = 0
items: list[str] = []
user_name: str = ""
# Event handlers - the ONLY way to modify state
def increment(self):
self.count += 1
def add_item(self, item: str):
self.items.append(item)
# Computed vars (derived state)
@rx.var
def item_count(self) -> int:
return len(self.items)
主なルール:
- 状態変数は JSON でシリアライズ可能(int、str、list、dict、bool、float)でなければなりません
- 状態を変更できるのはイベントハンドラーだけです
- 計算値/派生値には
@rx.varデコレーターを使用します - 状態はユーザーセッションごとに保持されます(ユーザー間で分離されます)
コンポーネント
コンポーネントは UI を構築するための基本要素です。Reflex には 60 種類以上の組み込みコンポーネントが用意されています。
import reflex as rx
def header() -> rx.Component:
return rx.heading("My App", size="lg")
def counter_component(state: AppState) -> rx.Component:
return rx.vstack(
rx.text(f"Count: {state.count}"),
rx.button("Increment", on_click=state.increment),
spacing="4"
)
一般的なコンポーネント:
- レイアウト:
rx.vstack、rx.hstack、rx.box、rx.container - テキスト:
rx.heading、rx.text、rx.code - 入力:
rx.input、rx.text_area、rx.select、rx.checkbox - インタラクティブ要素:
rx.button、rx.link、rx.icon_button - データ:
rx.table、rx.data_table、rx.list - グラフ:
rx.recharts.line_chart、rx.recharts.bar_chartなど
イベントハンドラー
イベントハンドラーはユーザーの操作に応答し、状態を変更する唯一の方法です。
class FormState(rx.State):
form_data: dict[str, str] = {}
# Simple event handler
def handle_submit(self):
print(f"Submitted: {self.form_data}")
# Event handler with argument
def update_field(self, field: str, value: str):
self.form_data[field] = value
# Async event handler (for API calls, DB queries)
async def fetch_data(self):
# Can use any Python library
import httpx
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data")
self.data = response.json()
イベントトリガー(コンポーネントとハンドラーを接続します):
on_click:ボタンのクリックon_change:入力フィールドの変更on_submit:フォームの送信on_mount:コンポーネントの初回描画on_blur、on_focus:入力フィールドのフォーカスイベント
プロジェクト構成
Reflex アプリの標準的な構成:
my_app/
├── my_app/
│ ├── __init__.py # Empty
│ └── my_app.py # Main app file (State + pages)
├── assets/ # Static files (images, fonts, etc.)
├── .web/ # Auto-generated frontend (don't edit)
├── rxconfig.py # Reflex configuration
└── requirements.txt # Python dependencies
メインアプリファイルのパターン
import reflex as rx
# 1. Define State
class State(rx.State):
count: int = 0
def increment(self):
self.count += 1
# 2. Define Pages
def index() -> rx.Component:
return rx.container(
rx.heading("Welcome"),
rx.button("Click", on_click=State.increment),
rx.text(f"Count: {State.count}")
)
def about() -> rx.Component:
return rx.container(
rx.heading("About"),
rx.link("Home", href="/")
)
# 3. Create App and Add Routes
app = rx.App()
app.add_page(index, route="/")
app.add_page(about, route="/about")
一般的なパターン
フォーム処理
class FormState(rx.State):
name: str = ""
email: str = ""
def handle_submit(self, form_data: dict):
self.name = form_data.get("name", "")
self.email = form_data.get("email", "")
def form_page():
return rx.form(
rx.vstack(
rx.input(name="name", placeholder="Name"),
rx.input(name="email", placeholder="Email"),
rx.button("Submit", type="submit"),
),
on_submit=FormState.handle_submit,
)
データテーブル
class DataState(rx.State):
data: list[dict] = [
{"id": 1, "name": "Alice", "age": 25},
{"id": 2, "name": "Bob", "age": 30},
]
def data_table_page():
return rx.data_table(
data=DataState.data,
columns=["id", "name", "age"],
sort=True,
search=True,
pagination=True,
)
ファイルアップロード
class UploadState(rx.State):
async def handle_upload(self, files: list[rx.UploadFile]):
for file in files:
upload_data = await file.read()
# Process file data
outfile = f"./uploads/{file.filename}"
with open(outfile, "wb") as f:
f.write(upload_data)
def upload_page():
return rx.vstack(
rx.upload(
rx.button("Select Files"),
id="upload1",
),
rx.button(
"Upload",
on_click=UploadState.handle_upload(rx.upload_files(upload_id="upload1"))
),
)
データベース統合(DuckDB を使用)
import duckdb
import polars as pl
class DBState(rx.State):
records: list[dict] = []
async def load_data(self):
# Use existing database connection
conn = duckdb.connect("data/mydb.duckdb")
df = conn.execute("SELECT * FROM mytable").pl()
self.records = df.to_dicts()
conn.close()
async def insert_record(self, data: dict):
conn = duckdb.connect("data/mydb.duckdb")
conn.execute(
"INSERT INTO mytable (name, value) VALUES (?, ?)",
[data["name"], data["value"]]
)
conn.close()
await self.load_data() # Refresh
スタイリングとレイアウト
インラインスタイリング
rx.box(
rx.text("Styled text"),
bg="#1a5f9e",
color="white",
padding="4",
border_radius="md",
)
レスポンシブレイアウト
rx.container(
rx.responsive_grid(
rx.box("Item 1", bg="blue"),
rx.box("Item 2", bg="green"),
rx.box("Item 3", bg="red"),
columns=[1, 2, 3], # 1 col mobile, 2 tablet, 3 desktop
spacing="4",
),
max_width="1200px",
)
一般的なスタイルプロパティ
- レイアウト:
width、height、padding、margin、display - 色:
bg(背景)、color(テキスト) - タイポグラフィ:
font_size、font_weight、text_align - 境界線:
border、border_radius、border_color - 間隔:
spacing(スタック用)、gap
ルーティング
複数ページ
app = rx.App()
# Route with parameters
@rx.page(route="/user/[id]")
def user_page() -> rx.Component:
return rx.text(f"User ID: {State.router.page.params.get('id')}")
# Simple routes
app.add_page(index, route="/")
app.add_page(about, route="/about")
ナビゲーション
# Links
rx.link("Go to About", href="/about")
# Programmatic navigation
def go_home(self):
return rx.redirect("/")
開発ワークフロー
新しいアプリを初期化する
pip install reflex
reflex init
開発サーバーを起動する
reflex run
アプリは http://localhost:3000 で起動し、自動で再読み込みされます。
一般的なコマンド
reflex run # Start dev server
reflex export # Build production bundle
reflex db init # Initialize database (if using Reflex DB)
reflex db migrate # Run migrations
ベストプラクティス
-
状態の整理:大きな状態をサブ状態に分割します
class AuthState(rx.State): user: str = "" class DataState(rx.State): items: list = [] -
コンポーネントの再利用性:再利用可能なコンポーネント関数を作成します
def card(title: str, content: str) -> rx.Component: return rx.box( rx.heading(title, size="md"), rx.text(content), padding="4", border="1px solid #ddd", ) -
イベントハンドラーのパフォーマンス:I/O 操作には async を使用します
async def fetch_data(self): # Async I/O won't block other users self.data = await some_api_call() -
型ヒント:状態変数とイベントハンドラーには必ず型ヒントを付けます
count: int = 0 items: list[str] = [] def update_count(self, value: int) -> None: self.count = value
参考資料
ドキュメント
- 公式ドキュメント:https://reflex.dev/docs/getting-started/introduction/
- コンポーネントライブラリ:https://reflex.dev/docs/library
- チュートリアル:https://reflex.dev/docs/getting-started/tutorial/
サンプルアプリ
動作する完全なサンプルについては、examples/ ディレクトリを参照してください:
- シンプルなカウンターアプリ
- CRUD 操作に対応したデータテーブル
- バリデーション付きフォーム
- ファイルのアップロードと処理
一般的なパターンのリファレンス
詳細な例については、references/patterns.md を参照してください:
- 認証フロー
- リアルタイム更新
- 複雑なフォームのバリデーション
- 複数ステップのワークフロー
- グラフを使ったデータの可視化