--- title: スタイルガイド icon: paintbrush description: Twentyに貢献するためのコード規約とベストプラクティス。 --- ## React ### 関数コンポーネントのみ 常に名前付きエクスポートの TSX 関数コンポーネントを使用してください。 ```tsx // ❌ Bad const MyComponent = () => { return
Hello World
; }; export default MyComponent; // ✅ Good export function MyComponent() { return
Hello World
; }; ``` ### プロパティ `{ComponentName}Props` という名前の型を作成する。 分割代入を使用する。 `React.FC` を使用しない。 ```tsx type MyComponentProps = { name: string; }; export const MyComponent = ({ name }: MyComponentProps) =>
Hello {name}
; ``` ### 単一変数によるプロップスのスプレッドは禁止 ```tsx // ❌ Bad const MyComponent = (props: MyComponentProps) => ; // ✅ Good const MyComponent = ({ prop1, prop2 }: MyComponentProps) => ; ``` ## 状態管理 ### グローバル状態には Jotai の atom を使用 ```tsx import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; export const myAtomState = createAtomState({ key: 'myAtomState', defaultValue: 'default value', }); ``` * prop drilling よりも atom を優先する * 状態管理に `useRef` を使用しない — `useState` または atom を使用する * リストには atom ファミリーとセレクターを使用する ### 不要な再レンダーを避ける * `useEffect` とデータ取得は同一階層のサイドカーコンポーネントに切り出す * `useEffect` よりもイベントハンドラー(`handleClick`、`handleChange`)を優先する * `React.memo()` は使用しない — 代わりに根本原因を修正する * `useCallback` および `useMemo` の使用を制限する ```tsx // ❌ Bad — useEffect in the same component causes re-renders export const Page = () => { const [data, setData] = useAtomState(dataState); const [dep] = useAtomState(depState); useEffect(() => { setData(dep); }, [dep]); return
{data}
; }; // ✅ Good — extract into sibling export const PageData = () => { const [data, setData] = useAtomState(dataState); const [dep] = useAtomState(depState); useEffect(() => { setData(dep); }, [dep]); return <>; }; export const Page = () => { const [data] = useAtomState(dataState); return
{data}
; }; ``` ## TypeScript * **`interface` より `type`** — より柔軟で、合成しやすい * **enum より文字列リテラル** — ただし GraphQL コード生成の enum と内部ライブラリ API は除く * **`any` は禁止** — 厳格な TypeScript を適用 * **型インポートは禁止** — 通常のインポートを使用(Oxlint の `typescript/consistent-type-imports` で強制) * **[Zod](https://github.com/colinhacks/zod) を使用**して、型未定義オブジェクトを実行時に検証する ## JavaScript ```tsx // Use nullish-coalescing (??) instead of || const value = process.env.MY_VALUE ?? 'default'; // Use optional chaining onClick?.(); ``` ## 命名について * **変数**: camelCase、意味がわかる名前(`value` ではなく `email`、`fm` ではなく `fieldMetadata`) * **定数**: SCREAMING_SNAKE_CASE * **型/クラス**: PascalCase * **ファイル/ディレクトリ**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`) * **イベントハンドラー**: `handleClick` (ハンドラー関数として `onClick` は使用しない) * **コンポーネントのプロップス**: コンポーネント名を接頭辞に付ける(`ButtonProps`) * **スタイル付きコンポーネント**: `Styled` を接頭辞に付ける(`StyledTitle`) ## スタイリング [Linaria](https://github.com/callstack/linaria) の styled components を使用する。 テーマの値を使用し、`px`、`rem`、色のハードコードは避ける。 ```tsx // ❌ Bad const StyledButton = styled.button` color: #333333; font-size: 1rem; margin-left: 4px; `; // ✅ Good const StyledButton = styled.button` color: ${({ theme }) => theme.font.color.primary}; font-size: ${({ theme }) => theme.font.size.md}; margin-left: ${({ theme }) => theme.spacing(1)}; `; ``` ## インポート 相対パスの代わりにエイリアスを使用する: ```tsx // ❌ Bad import { Foo } from '../../../../../testing/decorators/Foo'; // ✅ Good import { Foo } from '~/testing/decorators/Foo'; import { Bar } from '@/modules/bar/components/Bar'; ``` ## フォルダ構成 ``` front └── modules/ # Feature modules │ └── module1/ │ ├── components/ │ ├── constants/ │ ├── contexts/ │ ├── graphql/ (fragments, queries, mutations) │ ├── hooks/ │ ├── states/ (atoms, selectors) │ ├── types/ │ └── utils/ └── pages/ # Route-level components └── ui/ # Reusable UI components (display, input, feedback, ...) ``` * モジュールは他のモジュールからインポートしてよいが、`ui/` は依存関係を持たない状態を保つこと。 * モジュール専用コードには `internal/` サブフォルダを使用する。 * コンポーネントは 300 行未満、サービスは 500 行未満