writing-kea-logics について
PostHog のフロントエンド開発者向けに、kea の状態ロジックの規約をまとめたスキルです。状態の選択、React との接続、型生成を扱い、一般的なパターンも参照できます。
主な機能
- 01kea ロジックの規約
- 02状態コンテナの選択ガイド
- 03React コンポーネント接続パターン
- 04インライン型生成の手順
- 05一般的なパターンの索引
活用シーン
- 01kea ロジックファイルの新規作成や変更
- 02React hooks を使うコードのレビュー
- 03単一ロジックの型再生成
- 04状態変更に使うビルダーの選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ posthog/ posthog --skill writing-kea-logicsインストール後、Claude Code で/writing-kea-logicsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/anti-patterns.mdconnecting-logics.mdforms.mdkeyed-logics.mdloading-data.mdpersisting-state.mdpolling.mdreacting-to-changes.mdrouting.mdstate-decision.mdtesting.md詳細
kea ロジックの書き方
PostHog はフロントエンドの状態コンテナとして kea を使用しています。ほとんど
すべての非自明なビジネスロジックは React ではなく *Logic.ts / *Logic.tsx ファイルに
置かれます。keajs.org のドキュメントが扱っているバージョンより先行した kea のプレリリースを
使用している可能性があります。迷ったときは、固定されているバージョンを pnpm-workspace.yaml で
確認してください。
このスキルは、上流の kea ドキュメント に加えて、PostHog 固有の規約をまとめたものです。 ビルダーのシグネチャについて迷ったときは、上流のドキュメントを参照してください。 使うべきかどうか迷ったときは、ここを読んでください。
このスキルを使う場面
- 新しい
*Logic.ts/*Logic.tsxファイルを作成するとき - 既存のロジックにビルダー(actions、reducers、selectors、listeners、loaders、forms)を追加するとき
- ある状態に対して
reducerとselectorとcacheとloaderのどれを選ぶか判断するとき - React コンポーネントをロジックに接続するとき
- kea ロジックを導入または変更する PR をレビューするとき
- ロジックのほうがより慣用的である箇所で React フックを使用しているコードをレビューするとき
併用するスキル(重複させないこと)
- using-kea-disposables —
setInterval、addEventListener、 その他クリーンアップが必要なあらゆるリソース。
作業がこれと重なる場合は、先に併用スキルを読んでください。
基本原則
-
ビジネスロジックはコンポーネントではなくロジックに置きます。 CLAUDE.md に明記されています。「kea ロジックファイルがある場合は、すべての ビジネスロジックをそこに書き、React フックは可能な限り避けてください。」フックはビューの関心事のためにあります。
-
1 つの概念につき、1 つの信頼できる情報源。 アクション駆動の reducer、 派生 selector、非同期 loader のうち、必ず 1 つだけを選んでください。同じ値を複数の場所に ミラーしないでください。
-
kea-subscriptionsよりも listeners を優先してください。 サブスクリプションは、ディスパッチのたびに 再実行される redux サブスクリプションをインストールするため、計測できるほど遅くなります。代わりに、 値を変更したアクションをリッスンしてください。詳しくは references/reacting-to-changes.md を参照してください。 -
生成された型が契約です。 すべてのロジックには、
kea()呼び出しの上にインラインで生成されたMakeLogicTypeブロックがあります。ロジックの型はロジックのソースファイルからインポートし、 別の*LogicType.tsファイルからは決してインポートしないでください。 -
クリーンアップが必要なリソースは
cache.disposablesを経由します。 using-kea-disposables スキルを参照してください。
一目でわかる構造
import { MakeLogicType, actions, afterMount, connect, kea, key, listeners, path, props, reducers, selectors } from 'kea'
import { loaders } from 'kea-loaders'
import * as api from 'products/foo/frontend/generated/api'
export interface FooLogicProps {
fooId: string
}
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface fooLogicValues {
name: string
nameUpper: string
}
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface fooLogicActions {
setName: (name: string) => { name: string }
}
export type fooLogicType = MakeLogicType<fooLogicValues, fooLogicActions, FooLogicProps>
export const fooLogic = kea<fooLogicType>([
props({} as FooLogicProps),
key((props) => props.fooId),
path((key) => ['scenes', 'foo', 'fooLogic', key]),
connect(() => ({ values: [teamLogic, ['currentTeamId']] })),
actions({ setName: (name: string) => ({ name }) }),
loaders(({ props }) => ({
foo: [null as Foo | null, { loadFoo: async () => api.foosRetrieve(props.fooId) }],
})),
reducers({ name: ['', { setName: (_, { name }) => name }] }),
selectors({ nameUpper: [(s) => [s.name], (name): string => name.toUpperCase()] }),
listeners(({ actions }) => ({
loadFooSuccess: () => {
/* ... */
},
})),
afterMount(({ actions }) => {
actions.loadFoo()
}),
])
慣例的なブロックの順序: props → key → path → connect → actions → forms → loaders →
reducers → selectors → sharedListeners → listeners → subscriptions(まれ)→
windowValues → urlToAction / actionToUrl → afterMount / propsChanged / beforeUnmount。
これらすべてが必要になることはほとんどありません。典型的には 6 つほどのブロックです。ロジックが 実際に使用するものを選び、残りは省いてください。
判断フロー — 始める前に適切なコンテナを選ぶ
kea のバグのほとんどは、ある状態に対して誤ったコンテナを選ぶことから生じます。ビルダーに 手を伸ばす前に、これを順に検討してください:
- HTTP 呼び出しから取得するものですか? loader を使用してください。
- 他の状態から計算できますか?
selectorを使用してください。 - アクションがそれを変更し、変更時に UI が再レンダリングされる必要がありますか?
reducerを使用してください。 - タイマー、リスナー、その他破棄可能なリソースですか?
cache.disposablesを使用してください — using-kea-disposables を参照してください。
cache.foo は UI が決して読まない一時的なフラグのための逃げ道です。最初にではなく、
最後に手を伸ばしてください。詳細な内訳については
references/state-decision.md を参照してください。
パターンインデックス
各リファレンスは、1 つのジョブ・トゥ・ビー・ダンについて、パターンの形、なぜそれが適切な形なのか、そしてトレードオフを説明しています。リファレンス内のファイル引用は「現在の実例」であり、時間の経過とともに古くなるため、パターン自体が信頼できる情報源です。
| やりたいこと | 読むべきドキュメント |
|---|---|
| reducer / selector / cache / loader のどれを選ぶか判断する | references/state-decision.md |
| API からデータを読み込む | references/loading-data.md |
| エンドポイントをポーリングする、または一定間隔で更新する | references/polling.md |
| フォームを構築する | references/forms.md |
| 状態を URL と同期する | references/routing.md |
| リロードをまたいで状態を永続化する | references/persisting-state.md |
| 値の変化に反応する | references/reacting-to-changes.md |
| 1 つのロジックを複数インスタンスで持つ | references/keyed-logics.md |
| ロジック間またはコンポーネントのサブツリー間で状態を共有する | references/connecting-logics.md |
| ロジックをテストする | references/testing.md |
| 見ただけで変換すべきとわかるパターンを認識する | references/anti-patterns.md |
型と typegen
import { MakeLogicType, kea } from 'kea'
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface fooLogicValues {
name: string
}
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface fooLogicActions {
setName: (name: string) => { name: string }
}
export type fooLogicType = MakeLogicType<fooLogicValues, fooLogicActions>
export const fooLogic = kea<fooLogicType>([...])
インラインの型ブロックは kea-typegen 3.8.3 によって生成されます。コマンド:
pnpm --filter=@posthog/frontend typegen:watch— ロジックを書いている間のウォッチモードpnpm --filter=@posthog/frontend typegen:write— 一度だけ書き込むpnpm --filter=@posthog/frontend typegen:check— CI での整合性チェック
1 つのロジックを反復開発する
コードベース全体に対する完全な typegen は遅いです。1 つのロジックを反復開発しているときは、typegen をそのファイルに限定してください:
# Regenerate the type for one logic
pnpm --filter=@posthog/frontend typegen:file frontend/src/scenes/foo/fooLogic.ts
ロジックを書いている間はこのループを使用してください。現在の tsgo はプロジェクト設定とファイル引数の併用をサポートしていないため、最後に typegen:check と typescript:check を完全に実行して、他に何も壊れていないことを確認してください。
別の *LogicType.ts ファイルを作成したりインポートしたりしないでください。ロジックを変更して typegen を再実行することで、インラインで生成されたブロックが信頼できる情報源であり続けます。
キー付きロジックでは、エクスポートに明示的に型注釈を付けてください:
export const fooLogic: LogicWrapper<fooLogicType> = kea<fooLogicType>([...])。
迷ったときは
- 新しいパターンを発明する前に、上記の該当するリファレンスを読んでください。
- ビルダーのシグネチャについては、上流の keajs.org ドキュメントを読んでください。
- リポジトリ内で
*Logic.tsの中からビルダー名を検索してください — 何百もの動作する例があり、規約は安定しています。 - 状態管理の判断では、他の場所のコードを削除できる選択肢を優先してください。