Files
upstream-mirror e7da97194a
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
snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
2026-09-17 23:28:13 +05:00

330 lines
15 KiB
Plaintext
Raw Permalink 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: star
---
Этот документ описывает лучшие практики, которых следует придерживаться при работе с фронтендом.
## Управление состоянием
React и Jotai отвечают за управление состоянием в коде.
### Используйте атомы Jotai для хранения состояния
Полезно создавать столько атомов, сколько вам нужно для хранения состояния.
<Warning>
Лучше использовать дополнительные атомы, чем пытаться быть слишком кратким с передачей свойств.
</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` или атомы Jotai с `useAtomState`.
Смотрите [как управлять повторными рендерами](#managing-re-renders), если вы считаете, что вам нужен `useRef`, чтобы предотвратить их.
## Управление повторными рендерами
Управлять повторными рендерами в React может быть сложно.
Вот некоторые правила, которые стоит соблюдать, чтобы избегать ненужных повторных рендеров.
Помните, что вы всегда можете избежать повторных рендеров, если поймете причину их возникновения.
### Работа на корневом уровне
Избежать повторных рендеров в новых функциях стало проще, устранив их на корневом уровне.
Сайдкар-компонент `PageChangeEffect` содержит всего один `useEffect`, который реализует всю логику при изменении страницы.
Таким образом, вы знаете, что есть только одно место, которое может вызвать повторный рендер.
### Всегда думайте дважды, прежде чем добавлять `useEffect` в ваш код
Повторные рендеры часто вызываются ненужными `useEffect`.
Подумайте, нужно ли вам использовать `useEffect`, или же вы можете перенести логику в функцию обработчика событий.
Как правило, несложно перенести логику в функции `handleClick` или `handleChange`.
Вы также можете найти их в библиотеках, таких как Apollo: `onCompleted`, `onError` и т. д.
### Используйте дополнительный компонент для извлечения `useEffect` или логики получения данных
Если вы считаете, что нужно добавить `useEffect` в корневой компонент, стоит рассмотреть возможность его извлечения в сайдкар-компонент.
Вы можете применять то же самое для логики получения данных, с хуками Apollo.
```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('');
```
#### Некоторые слова, которых стоит избегать в названиях переменных
* dummy
### Обработчики событий
Названия обработчиков событий должны начинаться с `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>;
```
## Изменения с нарушением совместимости
Всегда проводите тщательное ручное тестирование перед тем, как продолжить, чтобы гарантировать, что изменения не вызвали перебоев в других местах, поскольку тесты еще не были широко интегрированы.