Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s
177 lines
5.4 KiB
Plaintext
177 lines
5.4 KiB
Plaintext
---
|
||
title: スタイルガイド
|
||
icon: paintbrush
|
||
description: Twentyに貢献するためのコード規約とベストプラクティス。
|
||
---
|
||
|
||
## React
|
||
|
||
### 関数コンポーネントのみ
|
||
|
||
常に名前付きエクスポートの TSX 関数コンポーネントを使用してください。
|
||
|
||
```tsx
|
||
// ❌ Bad
|
||
const MyComponent = () => {
|
||
return <div>Hello World</div>;
|
||
};
|
||
export default MyComponent;
|
||
|
||
// ✅ Good
|
||
export function MyComponent() {
|
||
return <div>Hello World</div>;
|
||
};
|
||
```
|
||
|
||
### プロパティ
|
||
|
||
`{ComponentName}Props` という名前の型を作成する。 分割代入を使用する。 `React.FC` を使用しない。
|
||
|
||
```tsx
|
||
type MyComponentProps = {
|
||
name: string;
|
||
};
|
||
|
||
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
|
||
```
|
||
|
||
### 単一変数によるプロップスのスプレッドは禁止
|
||
|
||
```tsx
|
||
// ❌ Bad
|
||
const MyComponent = (props: MyComponentProps) => <Other {...props} />;
|
||
|
||
// ✅ Good
|
||
const MyComponent = ({ prop1, prop2 }: MyComponentProps) => <Other {...{ prop1, prop2 }} />;
|
||
```
|
||
|
||
## 状態管理
|
||
|
||
### グローバル状態には 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<string>({
|
||
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 <div>{data}</div>;
|
||
};
|
||
|
||
// ✅ 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 <div>{data}</div>;
|
||
};
|
||
```
|
||
|
||
## 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 行未満
|