snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
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

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,329 @@
---
title: 最佳实践
icon: star
---
本文档概述了在前端工作时应遵循的最佳实践。
## 状态管理
React 和 Jotai 在代码库中处理状态管理。
### 使用 Jotai 原子来存储状态
根据需要创建足够多的原子来存储你的状态,是一种良好实践。
<Warning>
与其为了通过 props 层层传递而过度精简,不如使用额外的原子。
</Warning>
```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',
});
export const MyComponent = () => {
const [myAtom, setMyAtom] = useAtomState(myAtomState);
return (
<div>
<input
value={myAtom}
onChange={(e) => setMyAtom(e.target.value)}
/>
</div>
);
}
```
### 不要使用 `useRef` 来存储状态
避免使用 `useRef` 来存储状态。
如果想存储状态,应该使用 `useState` 或配合 `useAtomState` 的 Jotai 原子。
如果您觉得需要通过使用 `useRef` 来防止一些再渲染,请了解[如何管理再渲染](#managing-re-renders)。
## 管理再渲染
在 React 中管理再渲染可能很困难。
这里有一些规则以帮助避免不必要的再渲染。
请记住,通过了解其原因,可以**始终**避免再渲染。
### 在根级别工作
通过从根级消除它们,现在在新功能中避免再渲染变得容易。
`PageChangeEffect` sidecar 组件只包含一个 `useEffect` ,它持有页面更改时要执行的所有逻辑。
这样您就知道只有一个地方可以触发再渲染。
### 在代码库中添加 `useEffect` 之前总是考虑一次
重新渲染往往是由不必要的“使用效果”造成的。
您应该想一想您是否需要`useEffect`,或者您是否可以在事件处理函数中移动逻辑。
你会发现移动一个 "handleClick" 或 "handleChange" 函数中的逻辑通常很容易。
您还可以在如 Apollo 的库中找到它们:`onCompleted`,`onError` 等。
### 使用一个兄弟组件提取`useEffect`或数据获取逻辑。
如果你觉得需要在你的根组件中添加 "useEffect" ,你应该考虑在侧边组件中提取它。
对于数据获取逻辑也可以采用相同的方法,使用 Apollo hooks。
```tsx
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <div>{data}</div>;
};
export const App = () => (
<PageComponent />
);
```
```tsx
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
return <div>{data}</div>;
};
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <></>;
};
export const App = () => (
<>
<PageData />
<PageComponent />
</>
);
```
### 使用原子族状态和选择器
原子族状态和选择器是避免重新渲染的好方法。
当您需要存储项目列表时,它们很有用。
### 您不应使用 `React.memo(MyComponent)`
避免使用 `React.memo()` 因为它不能解决再渲染的原因,而是中断了再渲染链,可能导致意外行为并使代码难以重构。
### 限制 `useCallback` 或 `useMemo` 的使用
它们通常没有必要,会使代码难以阅读和维护,而性能提升难以察觉。
## Console.logs
`console.log` 语句在开发过程中非常有价值,提供关于变量值和代码流的实时洞察。 但是,将它们留在生产代码中可能导致几个问题:
1. **性能**:过多的日志记录会影响运行时性能,尤其是在客户端应用程序中。
2. **安全性**:记录敏感数据可能会将重要信息暴露给任何检查浏览器控制台的人。
3. **清洁度**:在控制台中填满日志可能会遮蔽开发人员或工具需要查看的重要警告或错误。
4. **专业性**:检查控制台的最终用户或客户看到数量众多的日志语句,可能会质疑代码的质量和精致度。
确保在提交代码到生产环境前移除所有 `console.logs`。
## 命名
### 变量命名
变量名称应该准确描述变量的用途或功能。
#### 泛化的名称问题
在编程中使用泛化的名称不是理想的,因为缺乏具体性,导致模糊性和降低代码的可读性。 这种名称无法传达变量或函数的目的,使得开发人员在不进行深入调查的情况下难以理解代码的意图。 这样会导致调试时间增加、错误的易发性提高以及在维护和协作中遇到困难。 同时,描述性的命名使代码自解释,更易于导航,提高代码质量和开发人员的生产力。
```tsx
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
#### 变量名称中应避免使用的一些词
* 空壳
### 事件处理
事件处理器名称应以 `handle` 开头,而 `on` 是用于命名组件属性事件的前缀。
```tsx
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
```
## 可选属性
避免为可选属性传递默认值。
**示例**
参见下面定义的 `EmailField` 组件:
```tsx
type EmailFieldProps = {
value: string;
disabled?: boolean;
};
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
<TextInput value={value} disabled={disabled} fullWidth />
);
```
**用法**
```tsx
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
```
```tsx
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;
```
## 组件作为属性
尽可能将未实例化的组件作为属性传递,以便子组件可以自行决定需要传递哪些属性。
最常见的例子是图标组件:
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
return (
<div>
<MyIcon size={theme.icon.size.md}>
</div>
)
};
```
为了让 React 理解一个组件就是一个组件,需要使用 PascalCase,然后用 `<MyIcon>` 来实例化它。
## 属性传递:保持简约
在 React 中,属性传递是指通过多个组件层传递状态变量及其设置器,即使中介组件不使用它们。 虽然有时是必要的,过多的属性传递可能会导致:
1. **可读性降低**:在深嵌套组件结构中追踪属性的来源或使用位置可能会变得复杂。
2. **维护挑战**:一个组件的属性结构变化可能需要在多个组件中进行调整,即便它们不直接使用该属性。
3. **组件重用性降低**:一个仅接受众多属性用于向下传递的组件变得不那么通用,且在不同上下文中更难以重用。
如果您觉得使用了过多的属性传递,请参见[状态管理最佳实践](#state-management)。
## 导入
导入时,选择指定的别名而不是指定完整或相对路径。
**句柄别名**
```js
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
```
**用法**
```tsx
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
```
```tsx
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
## 模式验证
[Zod](https://github.com/colinhacks/zod) 是用于无类型对象的模式验证器:
```js
const validationSchema = z
.object({
exist: z.boolean(),
email: z
.string()
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
type Form = z.infer<typeof validationSchema>;
```
## 重大变更
在进行下一步之前,始终进行彻底的手动测试,以确保修改没有在其他地方造成干扰,因为测试尚未全面集成。

View File

@@ -0,0 +1,110 @@
---
title: 文件夹架构
icon: folder-tree
info: 深入了解我们的文件夹架构
---
在本指南中,您将探索项目目录结构的详细信息,以及它如何促进 Twenty 的组织和可维护性。
遵循此文件夹架构约定,可以更轻松地找到与特定功能相关的文件,并确保应用程序的可扩展性和可维护性。
```
front
└───modules
│ └───module1
│ │ └───submodule1
│ └───module2
│ └───ui
│ │ └───display
│ │ └───inputs
│ │ │ └───buttons
│ │ └───...
└───pages
└───...
```
## 页面
包含由应用程序路由定义的顶级组件。 它们从模块文件夹中导入更多低级组件(详细信息见下文)。
## 模块
每个模块代表一个功能或一组功能,包括其特定组件、状态和操作逻辑。
它们都应遵循以下结构。 您可以在模块中嵌套模块(称为子模块),同样的规则将适用。
```
module1
└───components
│ └───component1
│ └───component2
└───constants
└───contexts
└───graphql
│ └───fragments
│ └───queries
│ └───mutations
└───hooks
│ └───internal
└───states
│ └───selectors
└───types
└───utils
```
### 上下文
上下文是一种通过组件树传递数据的方法,不必在每个级别手动传 props。
详情请参阅 [React Context](https://react.dev/reference/react#context-hooks)。
### GraphQL
包含片段、查询和变更。
详情请参阅 [GraphQL](https://graphql.org/learn/)。
* 片段
片段是查询的可重用部分,可以在不同地方使用。 使用片段可以更容易地避免代码重复。
详情请参阅 [GraphQL Fragments](https://graphql.org/learn/queries/#fragments)。
* 查询
详情请参阅 [GraphQL Queries](https://graphql.org/learn/queries/)。
* 变更
详情请参阅 [GraphQL Mutations](https://graphql.org/learn/queries/#mutations)。
### 钩子
详情请参阅 [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks)。
### 状态
包含状态管理逻辑。 [Jotai](https://jotai.org) 处理这部分。
* 选择器:派生 Atom(使用 `createAtomSelector`)根据其他 Atom 计算出值,并会自动记忆化。
React 内置状态管理仍然在组件内处理状态。
### 工具
应仅包含可重用的纯函数。 否则,请在 `hooks` 文件夹中创建自定义钩子。
## UI
包含应用程序中使用的所有可重用的 UI 组件。
这个文件夹可以包含子文件夹,像 `data`、`display`、`feedback` 和 `input`,用于特定类型的组件。 每个组件都应是自包含和可重用的,以便您可以在应用程序的不同部分使用它。
通过将 UI 组件与 `modules` 文件夹中的其他组件分开,可以更轻松地保持一致的设计,并在更改 UI 时不影响其他部分(业务逻辑)的代码库。
## 接口和依赖关系
您可以从任何模块中导入其他模块代码,但 `ui` 文件夹除外。 这将使代码易于测试。
### 内部
模块的每个部分(钩子、状态等) 都可以有一个 `internal` 文件夹,其中包含仅在模块内使用的部分。

View File

@@ -0,0 +1,91 @@
---
title: 前端命令
icon: terminal
---
## 实用命令
### 启动应用
```bash
npx nx start twenty-front
```
### 根据API graphql schema重新生成graphql schema
```bash
npx nx run twenty-front:graphql:generate --configuration=metadata
```
或
```bash
npx nx run twenty-front:graphql:generate
```
### Lint
```bash
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## 翻译
```bash
npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
```
### 测试
```bash
npx nx run twenty-front:test # run jest tests
npx nx run twenty-front:storybook:serve:dev # run storybook
npx nx run twenty-front:storybook:test # run tests # (needs yarn storybook:serve:dev to be running)
npx nx run twenty-front:storybook:coverage # (needs yarn storybook:serve:dev to be running)
```
## 技术栈
项目有一个干净且简单的技术栈,使用最少的样板代码。
**应用**
* [React](https://react.dev/)
* [Apollo](https://www.apollographql.com/docs/)
* [GraphQL 代码生成](https://the-guild.dev/graphql/codegen)
* [Jotai](https://jotai.org/)
* [TypeScript](https://www.typescriptlang.org/)
**测试**
* [Jest](https://jestjs.io/)
* [Storybook](https://storybook.js.org/)
**工具**
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
## 架构
### 路由
[React Router](https://reactrouter.com/) 负责处理路由。
为了避免不必要的 [re-renders](/l/zh/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders),所有路由逻辑都在 `PageChangeEffect`中的 `useEffect` 中。
### 状态管理
[Jotai](https://jotai.org/) 处理状态管理。
查看[最佳实践](/l/zh/developers/contribute/capabilities/frontend-development/best-practices-front#state-management)以获取有关状态管理的更多信息。
## 测试
[Jest](https://jestjs.io/) 用于单元测试,而 [Storybook](https://storybook.js.org/) 用于组件测试。
Jest 主要用于测试工具函数,而不是组件本身。
Storybook 用于测试隔离组件的行为,以及展示设计系统。

View File

@@ -0,0 +1,178 @@
---
title: 快捷键
---
## 介绍
当你需要监听一个快捷键时,通常会使用 `onKeyDown` 事件监听器。
然而,在 `twenty-front` 中,你可能会遇到用于不同组件的相同快捷键之间的冲突,这些组件同时挂载。
例如,如果你有一个页面监听回车键,而一个包含选择组件的模态框也监听回车键,在所有这些组件同时挂载时可能会发生冲突。
## `useScopedHotkeys` hook
为了解决这个问题,我们有一个自定义的 hook,可以在不产生冲突的情况下监听快捷键。
您可以将它放在组件中,它只会在组件挂载并且指定的 **快捷键范围** 激活时监听快捷键。
## 在实践中如何监听快捷键?
设置快捷键监听涉及两个步骤:
1. 设置将会监听快捷键的[快捷键范围](#what-is-a-hotkey-scope-)
2. 使用 `useScopedHotkeys` hook 来监听快捷键
即使在简单的页面中,也需要设置快捷键范围,因为其他界面元素如左侧菜单或命令菜单也可能监听快捷键。
## 快捷键的使用案例
通常情况下,会有两种需要快捷键的使用案例:
1. 在页面或页面中挂载的组件中
2. 在由于用户操作而获得焦点的模态组件中
第二种使用情况可以递归发生:例如,在模态框中的下拉框。
### 在页面中监听快捷键
示例:
```tsx
const PageListeningEnter = () => {
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
// 1. Set the hotkey scope in a useEffect
useEffect(() => {
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleEnterPage,
);
// Revert to the previous hotkey scope when the component is unmounted
return () => {
goBackToPreviousHotkeyScope();
};
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
// 2. Use the useScopedHotkeys hook
useScopedHotkeys(
Key.Enter,
() => {
// Some logic executed on this page when the user presses Enter
// ...
},
ExampleHotkeyScopes.ExampleEnterPage,
);
return <div>My page that listens for Enter</div>;
};
```
### 在模态类型组件中监听快捷键
在这个示例中,我们将使用一个模态组件,该组件监听 Esc 键来告诉其父组件关闭自己。
这里用户交互正在改变范围。
```tsx
const ExamplePageWithModal = () => {
const [showModal, setShowModal] = useState(false);
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const handleOpenModalClick = () => {
// 1. Set the hotkey scope when user opens the modal
setShowModal(true);
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleModal,
);
};
const handleModalClose = () => {
// 1. Revert to the previous hotkey scope when the modal is closed
setShowModal(false);
goBackToPreviousHotkeyScope();
};
return <div>
<h1>My page with a modal</h1>
<button onClick={handleOpenModalClick}>Open modal</button>
{showModal && <MyModalComponent onClose={handleModalClose} />}
</div>;
};
```
然后在模态组件中:
```tsx
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
// 2. Use the useScopedHotkeys hook to listen for Escape.
// Note that escape is a common hotkey that could be used by many other components
// So it's important to use a hotkey scope to avoid conflicts
useScopedHotkeys(
Key.Escape,
() => {
onClose()
},
ExampleHotkeyScopes.ExampleModal,
);
return <div>My modal component</div>;
};
```
当你不能确定只需使用使用效果挂载/卸载就足以避免冲突时,使用此模式是很重要的。
这些冲突可能难以调和,而且使用效果可能更经常地发生。
## 什么是快捷键范围?
快捷键范围是表示快捷键激活上下文的字符串。 通常编码为枚举。
当你更改快捷键范围时,监听此范围的快捷键将启用,监听其他范围的快捷键将被禁用。
一次只能设置一个范围。
例如,每个页面的快捷键范围在 `PageHotkeyScope` 枚举中定义:
```tsx
export enum PageHotkeyScope {
Settings = 'settings',
CreateWorkspace = 'create-workspace',
SignInUp = 'sign-in-up',
CreateProfile = 'create-profile',
PlanRequired = 'plan-required',
ShowPage = 'show-page',
PersonShowPage = 'person-show-page',
CompanyShowPage = 'company-show-page',
CompaniesPage = 'companies-page',
PeoplePage = 'people-page',
OpportunitiesPage = 'opportunities-page',
ProfilePage = 'profile-page',
WorkspaceMemberPage = 'workspace-member-page',
TaskPage = 'task-page',
}
```
在内部,当前选择的范围存储在整个应用程序共享的 Jotai 原子中:
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
key: 'currentHotkeyScopeState',
defaultValue: INITIAL_HOTKEYS_SCOPE,
});
```
但这个原子不应该手动处理! 我们将在下一节中学习如何使用它。
## 内部是如何运作的?
我们在 [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) 之上制作了一个薄包装,使其性能更高并避免不必要的重新渲染。
我们还创建了一个 Jotai 原子来处理快捷键范围状态,并使其在整个应用程序中可用。

View File

@@ -0,0 +1,8 @@
---
title: Storybook
description: 浏览 Twenty 的 UI 组件库
---
在 Storybook 中查看我们完整的组件库和文档。
[打开 Storybook →](https://storybook.twenty.com)

View File

@@ -0,0 +1,291 @@
---
title: 样式指南
icon: paintbrush
---
本文档包含编写代码时需要遵循的规则。
这里的目标是拥有一个一致的代码库,易于阅读和维护。
为此,比起过于简洁,冗长一些更好。
请始终记住,人们阅读代码的次数远多于他们编写代码的次数,特别是在开源项目中,任何人都可以贡献。
有很多规则没有在此定义,但可以通过linters自动检查。
## React
### 使用函数组件
始终使用 TSX 函数组件。
不要使用带有`const`的默认`import`,因为它更难阅读,并且更难通过代码补全功能导入。
```tsx
// ❌ Bad, harder to read, harder to import with code completion
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good, easy to read, easy to import with code completion
export function MyComponent() {
return <div>Hello World</div>;
};
```
### 属性
创建props类型并称为`(ComponentName)Props`,如果不需要导出它。
使用props解构。
```tsx
// ❌ Bad, no type
export const MyComponent = (props) => <div>Hello {props.name}</div>;
// ✅ Good, type
type MyComponentProps = {
name: string;
};
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
#### 避免使用 `React.FC` 或 `React.FunctionComponent` 适用于属性类型的定义
```tsx
/* ❌ - Bad, defines the component type annotations with `FC`
* - With `React.FC`, the component implicitly accepts a `children` prop
* even if it's not defined in the prop type. This might not always be
* desirable, especially if the component doesn't intend to render
* children.
*/
const EmailField: React.FC<{
value: string;
}> = ({ value }) => <TextInput value={value} disabled fullWidth />;
```
```tsx
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
*/
type EmailFieldProps = {
value: string;
};
const EmailField = ({ value }: EmailFieldProps) => (
<TextInput value={value} disabled fullWidth />
);
```
#### JSX元素中禁止单变量属性传播
避免在JSX元素中使用单个变量属性传播,如`{...props}`。 这种做法通常会导致代码不易读且难以维护,因为不清楚组件接收了哪些属性。
```tsx
/* ❌ - Bad, spreads a single variable prop into the underlying component
*/
const MyComponent = (props: OwnProps) => {
return <OtherComponent {...props} />;
}
```
```tsx
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
};
```
理由:
* 一目了然,它更清楚哪个属性代码传递下来,这使得理解和维护更容易。
* 这有助于防止组件间通过属性的紧密耦合。
* Linting工具使显式列出属性时更易于识别拼写错误或未使用的属性。
## JavaScript
### 使用nullish 合并运算符 `??`
```tsx
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### 使用可选链 `?.`
```tsx
// ❌ Bad
onClick && onClick();
// ✅ Good
onClick?.();
```
## TypeScript
### 使用 `type` 替代 `interface`
总是使用 `type` 而不是 `interface`,因为它们几乎总是重叠,而 `type` 更灵活。
```tsx
// ❌ Bad
interface MyInterface {
name: string;
}
// ✅ Good
type MyType = {
name: string;
};
```
### 使用字符串字面量代替枚举
[字符串字面量](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types)是TypeScript中处理枚举值的首选方式。 它们可以更轻松地使用Pick和Omit扩展,并提供更好的开发者体验,特别是在代码补全时。
您可以查看[这里](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums)了解为什么TypeScript建议避免使用枚举。
```tsx
// ❌ Bad, utilizes an enum
enum Color {
Red = "red",
Green = "green",
Blue = "blue",
}
let color = Color.Red;
```
```tsx
// ✅ Good, utilizes a string literal
let color: "red" | "green" | "blue" = "red";
```
#### GraphQL和内部库
应使用GraphQL codegen生成的枚举。
在使用内部库时, 使用枚举更好,这样内部库不必暴露不属于内部API的字符串文字类型。
示例:
```TSX
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
```
## 样式
### 使用StyledComponents
使用[Linaria styled](https://github.com/callstack/linaria)对组件进行样式化。
```tsx
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
在“真实”组件和Styled 组件之间的区别,前置“Styled”命名。
```tsx
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
### 主题
大多数组件样式首选利用主题。
#### 测量单位
避免在样式化组件中直接使用`px`或`rem`值。 所需的值通常已在主题中定义,因此建议为此目的使用主题。
#### 颜色
避免引入新颜色;而是使用来自主题的现有调色板。 如果遇到调色板不匹配的情况,请留下评论以便团队纠正。
```tsx
// ❌ Bad, directly specifies style values without utilizing the theme
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
font-weight: 400;
margin-left: 4px;
border-radius: 50px;
`;
```
```tsx
// ✅ Good, utilizes the theme
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin-left: ${({ theme }) => theme.spacing(1)};
border-radius: ${({ theme }) => theme.border.rounded};
`;
```
## 强制不允许类型导入
避免类型导入。 为强制实施此标准,Oxlint 规则会检查并报告所有类型导入。 这有助于在TypeScript代码中保持一致性和可读性。
```tsx
// ❌ Bad
import { type Meta, type StoryObj } from '@storybook/react';
// ❌ Bad
import type { Meta, StoryObj } from '@storybook/react';
// ✅ Good
import { Meta, StoryObj } from '@storybook/react';
```
### 为什么不允许类型导入
* **一致性**:通过避免类型导入并为类型和值导入使用单一方法,代码库在其模块导入风格上保持一致性。
* **可读性**:无类型导入提高代码可读性,使得在导入值或类型时更显而易见。 这减少了歧义,并使理解导入符号的用途更容易。
* **可维护性**:它增强了代码库的可维护性,因为开发人员可以在审查或修改代码时识别和定位仅类型导入。
### Oxlint 规则
Oxlint 规则,`typescript/consistent-type-imports`,强制执行不允许类型导入标准。 该规则会生成错误或警告以提示任何类型导入的违规。
请注意,此规则专门针对在罕见的边缘情况中发生的非预期类型导入。 TypeScript 本身也不鼓励这种做法,如[TypeScript 3.8 发行说明](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html)所述。 在大多数情况下,您不需要使用仅类型导入。
为确保您的代码符合此规则,请确保在开发工作流程中运行 Oxlint。

View File

@@ -0,0 +1,61 @@
---
title: 使用Figma进行工作
info: 了解如何与 Twenty 的 Figma 协作
---
Figma是一款协作界面设计工具,帮助设计师和开发者之间沟通无障碍。
本指南介绍如何与Figma协作。
## 访问
1. **访问共享链接:** 您可以在[此处](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty)访问项目的 Figma 文件。
2. **登录:** 如果尚未登录,Figma会提示您进行登录。
只有登录用户才能使用关键功能,如开发者模式和选择专用框架的能力。
<Warning>
没有账户将无法有效协作。
</Warning>
## Figma结构
在左侧边栏,您可以访问 Twenty 的 Figma 中的不同页面。 页面组织方式如下:
* **组件页面:** 这是第一页。 设计师使用它创建和组织设计文件中可重用的设计元素。 例如,按钮、图标、符号,或其他可重用的组件。 它能保持设计一致性。
* **主页:** 第二页是主页,展示项目的完整用户界面。 可以按下**播放**,使用完整的应用程序原型。
* **功能页面:** 其他页面通常专用于进行中的功能。 它们包含应用程序或网站特定功能或模块的设计。 这些通常仍在进行中。
## 有用的提示
在仅查看权限下,您无法编辑设计,但可以访问用于将设计转换为代码的所有功能。
### 使用开发者模式
Figma的开发者模式通过提供便捷的设计导航、有效的资产管理、有效的沟通工具、工具箱集成、快速代码片段和关键图层信息,增强了开发人员的生产力,填补了设计与开发之间的缺口。 可以在[此处](https://www.figma.com/dev-mode/)了解有关开发者模式的更多信息。
切换到工具栏右侧的“开发者”模式以查看设计规格、复制CSS并访问资产。
### 使用原型
在画布上点击任意元素,并在界面右上角按下“播放”按钮以访问原型视图。 原型模式允许您像最终产品一样与设计进行交互。 它展示了屏幕之间的流程以及界面元素(如按钮、链接或菜单)在交互时的行为方式。
1. **了解过渡和动画:** 在原型模式中,您可以查看设计师在屏幕或UI元素之间添加的任何过渡或动画,为开发人员提供明确的视觉说明,理解预期的行为和风格。
2. **实施澄清:** 原型还可以帮助减少歧义。 开发人员可以与原型交互,以更好地理解特定元素的功能或外观。
有关Figma平台学习的更多详细信息和指南,可以访问官方[Figma文档](https://help.figma.com/hc/en-us)。
### 测量距离
选择一个元素,按住`Option`键(Mac)或`Alt`键(Windows),然后悬停在另一个元素上以查看它们之间的距离。
### VSCode的Figma扩展(推荐)
[Figma for VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
可让您浏览和检查设计文件,与设计师协作,跟踪更改,并加速实现落地——且无需离开文本编辑器。
这是我们推荐的扩展之一。
## 协作
1. **使用评论:** 您可以使用评论功能,点击工具栏左侧的气泡图标。
2. **光标聊天:** Figma的一个很棒的功能是光标聊天。 如果您看到其他人在使用Figma的同时按下Mac上的`;`或者Windows上的`/`来发送消息。