Files
twenty/packages/twenty-docs/l/zh/developers/contribute/style-guide.mdx
T
github-actions[bot] f018f17133 i18n - docs translations (#19970)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-04-22 14:44:57 +02:00

177 lines
4.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 样式指南
icon: 画笔
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>;
```
### 禁止仅为单个 prop 使用展开
```tsx
// ❌ Bad
const MyComponent = (props: MyComponentProps) => <Other {...props} />;
// ✅ Good
const MyComponent = ({ prop1, prop2 }: MyComponentProps) => <Other {...{ prop1, prop2 }} />;
```
## 状态管理
### 使用 Jotai 原子管理全局状态
```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',
});
```
* 优先使用原子,而非 props 逐层传递
* 不要将 `useRef` 用于状态 — 使用 `useState` 或原子。
* 对列表使用原子族和选择器。
### 避免不必要的重新渲染
* 将 `useEffect` 和数据获取提取到同级的 sidecar 组件中。
* 优先使用事件处理器(`handleClick`、`handleChange`)而不是 `useEffect`。
* 不要使用 `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
* **优先用 `type` 而非 `interface`** — 更灵活、更易组合。
* **优先用字符串字面量而非枚举** — 但 GraphQL 代码生成的枚举和内部库 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,具描述性(`email` 而非 `value``fieldMetadata` 而非 `fm`)。
* **常量**SCREAMING_SNAKE_CASE
* **类型/类**PascalCase
* **文件/目录**kebab-case`.component.tsx`、`.service.ts`、`.entity.ts`
* **事件处理器**`handleClick`(处理函数不要使用 `onClick` 作为名称)
* **组件 props**:以组件名作为前缀(`ButtonProps`)。
* **样式化组件**:以 `Styled` 作为前缀(`StyledTitle`)。
## 样式
使用 [Linaria](https://github.com/callstack/linaria) 的样式化组件。 使用主题值 — 避免硬编码 `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 行。