react について
プロジェクト開発者向けのReactコンポーネント作成ガイドです。StyleXの移行、コンポーネント選択、状態管理、描画性能に関する規約をまとめています。
主な機能
- 01StyleXのスタイル作成と移行
- 02UIコンポーネント選択の優先順位
- 03状態の所有者と分割境界
- 04Popoverのクローズ動作
- 05描画性能とメモ化の規則
- 06レイアウトコンポーネントの規約
活用シーン
- 01既存コンポーネントのStyleX移行
- 02適切な基本UIコンポーネントの選択
- 03状態と再利用境界の見直し
- 04ナビゲーション後のPopover終了確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ lobehub/ lobehub --skill reactインストール後、Claude Code で/reactを入力して読み込まれたか確認します。
同梱ファイルreferences/layout-kit.md
詳細
React コンポーネントの記述ガイド
スタイリング
| シナリオ | アプローチ |
|---|---|
| すでに StyleX を使用しているファイル、および新しい SPA のスタイル | stylex.create + @lobehub/ui/stylex/cssVar.stylex / @lobehub/ui/stylex/media.stylex。SPA コンパイラーは plugins/vite/stylex.ts(@stylexjs/unplugin、プレフィックスは lh)です。これらのトークンモジュールをアプリにコピーしないでください。 |
| 既存のスタイルがまだ移行されていない場合 | createStaticStyles + cssVar.*(ゼロランタイム、モジュールレベル) |
| シンプルな単発のスタイル | インラインの style 属性 |
真に動的なスタイル(readableColor/chroma のような JS の色関数) | createStyles + token — 最後の手段 |
StyleX
- 新しいコードでは、
@lobehub/uiからcreateStaticStyles/createStyles/css/keyframes/createGlobalStyle/injectGlobalをインポートしないでください(stylex-migration/no-emotion-styles)。引き続きこれらを使用しているファイルはscripts/stylexMigration/legacyFiles.jsonに記載されています。ファイルを移行した後は、bun run stylex:migration --prune-legacyを実行してください。エントリーを追加してはいけません。 - ファイル全体を一度に移行してください。StyleX のロングハンドルールは詳細度が高く(
.x:not(#\#))、同じ要素の emotion クラスより優先されますが、StyleX のショートハンドルールはそうではありません。そのため、一部だけ移行した要素ではプロパティごとに解決されます。 - 呼び出し元の
className/styleも転送する要素では、@/styles/sxの{...sx([styles.root, active && styles.active], className, style)}を使用してください。stylex.propsをclassNameの隣に展開すると、@stylexjs/no-conflicting-propsエラーになります。 - 呼び出し元がスタイルを上書きできるコンポーネントでは、
xstyle?: StyleXStylesを受け取り、sx/stylex.props配列の最後に渡してください。外部から指定した通常のclassNameでは StyleX ルールを上書きできません。@lobehub/uiコンポーネントにクラスを渡す場合(className、classNames)は機能します。lobe-ui の CSS は@layer lobe-ui内にあるためです。 - 値は
@stylexjs/valid-stylesに従います。複数値のショートハンド(margin: '0 8px'→marginBlock/marginInline)は使わず、flexには文字列を指定します(flex: '1')。 background、animation、borderおよびborder{Top,Right,Bottom,Left}のショートハンドは、どのような値でも(border: 'none'を含め)エラーなしでクラスが一切生成されません。ロングハンドを記述してください。backgroundのショートハンドを置き換える場合は、backgroundColor(およびbackgroundImage: 'none')、animationName/animationDuration/ …、borderWidth/borderStyle/borderColorを使用します。- 開発環境で移行を確認するには、DevDock のスナップショットヘルパーを使用します。移行したページで
__lobeStyleSnapshot.capture('<name>', '<root selector>')を実行し、ファイルを元に戻して(git stash push -- <path>)、再読み込みし、__lobeStyleSnapshot.compare('<name>')を実行してからgit stash popを実行します。各ペアは[captured, current](ここでは[migrated, original])を読み込みます。追跡対象の計算済みスタイル、ボックス(ルートからの相対位置)、またはテキストに差異がある要素と::before/::afterを一覧表示します。リストが空であれば、それらが一致していることを意味しますが、ホバー、フォーカス、閉じたダイアログを確認したという意味ではありません。移行した各状態を開いて、それも比較してください。ウィンドウサイズを維持し、ルート内のスクロール位置をすべて先頭に戻し、比較対象の領域からポインターを移動してください。 bun run stylex:migration --list <A|B|C|D>は、移行の難易度別にファイルを一覧表示します。A はフラット、B は自身の疑似クラス、C は子孫 / 祖先との結合(stylex.defaultMarker()とstylex.when.ancestor()またはxstyleを使用)、D はタグ / サードパーティ DOM セレクター(スコープ付きの通常の CSS)です。
コンポーネントの優先順位
src/components— プロジェクト固有の再利用可能なコンポーネント@lobehub/ui/base-ui— ヘッドレスプリミティブ。コンポーネントがここにある場合は、こちらを使用してください。同じ名前のルートエクスポートをインポートしてはいけません。@lobehub/ui— 高レベルのコンポーネント / antd をラップするコンポーネント(base-ui に同等のものがない場合のみ)- antd — base-ui と
@lobehub/uiのルートエクスポートのどちらにもない場合のみ - カスタム実装 — 真に最後の手段
利用可能なコンポーネントが不明な場合は、既存のコードを検索するか、node_modules/@lobehub/ui/es/index.mjs と node_modules/@lobehub/ui/es/base-ui/ を確認してください。
@lobehub/ui/base-ui — これらは常にこちらを優先してください
| コンポーネント | インポート |
|---|---|
Alert(および AlertProps) | import { Alert, type AlertProps } from '@lobehub/ui/base-ui'; |
Select(および SelectProps、SelectOption) | import { Select } from '@lobehub/ui/base-ui'; |
Modal(命令型 API) | import { createModal, confirmModal, useModalContext, type ModalInstance } from '@lobehub/ui/base-ui'; |
DropdownMenu | import { DropdownMenu } from '@lobehub/ui/base-ui'; |
ContextMenu | import { ContextMenu } from '@lobehub/ui/base-ui'; |
Popover | import { Popover } from '@lobehub/ui/base-ui'; |
ScrollArea | import { ScrollArea } from '@lobehub/ui/base-ui'; |
Switch | import { Switch } from '@lobehub/ui/base-ui'; |
Toast | import { Toast } from '@lobehub/ui/base-ui'; |
FloatingSheet | import { FloatingSheet } from '@lobehub/ui/base-ui'; |
Drawer | import { Drawer } from '@lobehub/ui/base-ui'; |
Modal については、専用の modal スキルを参照してください。従来の宣言型パターン <Modal open … /> ではなく、命令型の createModal({ content: … }) パターンを使用してください。base-ui には、すでに SPAGlobalProvider にマウントされた独自の ModalHost があります。
よくある間違い:
import { Select } from '@lobehub/ui'は正しそうに見えますが、これは antd ベースの Select です。base-ui の Select を使用してください。Modal、DropdownMenuなども同様です。
@lobehub/ui ルート — base-ui に同等のものがない場合に使用
| カテゴリ | コンポーネント |
|---|---|
| 一般 | ActionIcon, ActionIconGroup, Block, Button, Icon |
| データ表示 | Avatar, Collapse, Empty, Highlighter, Markdown, Tag, Tooltip |
| データ入力 | CodeEditor, CopyButton, EditableText, Form, Input, InputPassword, SearchBar, TextArea |
| レイアウト | Center, DraggablePanel, Flexbox, Grid, Header, MaskShadow |
| ナビゲーション | Burger, Menu, SideNav, Tabs |
状態
一時的な状態は、必要な範囲が最小となる所有者に保持してください。状態遷移やハンドラーによってレンダリングが見通しにくくなる場合、または再利用可能な単位となる場合はカスタムフックを抽出してください。コンポーネントが特定の数のフックを持つという理由だけで抽出してはいけません。
実際の状態、再利用、レンダリング更新、またはマウント可能な機能の境界を確立する場合にのみ、コンポーネントを分割してください。ファイルを小さくすることだけを目的に分割してはいけません。ファイルが source-layout の 1200 行制限に達した場合は、行数で区切るのではなく、これらの境界のいずれかを見つけてください。重いドメイン機能をホスト側で組み立てるアトムに分解する作業は、compose-atoms が担当します。
ポップオーバーを離れる場合
ポップオーバーのコンテンツ内にあるアクションによってポップオーバーを離れる場合は、ポップオーバーを閉じる必要があります。閉じないと、遷移先の上にポップオーバーが残ります(たとえば、ユーザーパネルのプランバッジから /settings/plans に移動する場合)。
- ナビゲーション:コンテンツ内にリンクやナビゲーションを含むホストは、
open状態を保持し、@/hooks/useCloseOnNavigateのuseCloseOnNavigate(open, () => setOpen(false))を呼び出して、返されたキーをPopoverに渡します。これにより、後から追加されたリンクや、ほかのファイルから追加されたリンクも対象になります。このキーが重要なのは、ナビゲーションによってホストが<Activity>(ホームサイドバーなど)内で非表示になる場合です。非表示のツリーでは新しいエフェクトが設定されないため、base-ui のポップアップが終了アニメーションを完了できず、再マウントによって削除されるまで画面に残ります。 - モーダルへの引き継ぎ、または最終選択:モーダルを開く前、または選択後に、
NavPanel/switcher/SwitcherMenu.tsxのようにusePopoverContext().close()(またはホストのセッター)を呼び出します。ポップオーバーの外部にレンダリングされたコンテンツでは、このコンテキストを使用できません。開発環境では、既定のcloseが例外をスローするためです。 - 共有リンクのプライバシー確認のように、結果が同じポップオーバー内に表示される確認ステップでは、ポップオーバーを開いたままにします。
レンダリングパフォーマンスとメモ化
memo、useMemo、useCallback は既定のコンポーネントラッパーではなく、必要に応じて使う最適化として扱います。導入する前に、実際にどこで再レンダリングが起きるかを特定し、まずは構造的な改善を検討してください。
- 更新境界で分割します。
- 一時的な状態を、最小の所有者に移します。
- Zustand のセレクターを絞り込み、広範な購読を避けます。
プロパティを持たないコンポーネントや、単純にレンダリングされるコンポーネント、通常は新しいオブジェクト、配列、関数、JSX の子要素を受け取るコンポーネントはメモ化しないでください。ツリーの上位に保持された状態を補うためにメモ化を使わないでください。
サブツリーが明らかに処理コストの高いもの、または頻繁に繰り返しレンダリングされるものであり、通常の親コンポーネントのレンダリング中に関連する入力が安定していて、プロファイリングまたは具体的なレンダリング経路の分析によって回避できる処理が特定された場合に限り、メモ化を使用してください。その理由を実装の概要または PR に記載してください。
レイアウト
@lobehub/ui の Flexbox と Center を使用してください。すべてのプロパティと例については、references/layout-kit.md を参照してください。
- Flex の子要素間の間隔には、
marginではなくgapを使用します。 - 利用可能なスペースを埋めるには、
flex={1}を使用します。 - 複雑なレイアウトでは Flexbox をネストし、スクロール可能な領域には
overflow: 'auto'を設定します。
関連スキル
ux:読み込み時の表示(シナリオ →references/feedback.md§4.1 のローダーテーブル)と、ユーザー向けのインタラクション設計。modal:命令型の base-ui モーダルパターン。spa-routes:SPA ナビゲーション、ルートの所有権、ルーター設定、.desktopバリアント。compose-atoms:大規模なドメイン機能を、マウント可能な機能アトムに分割します。各ホストは、マウントするものだけをインポートします。zustand:ストアの構造とセレクターの規約。