8cadd00d34
Created by Github action --------- Co-authored-by: Crowdin Bot <support+bot@crowdin.com> Co-authored-by: github-actions <github-actions@twenty.com>
334 lines
15 KiB
Plaintext
334 lines
15 KiB
Plaintext
---
|
|
title: Лучшие практики
|
|
image: /images/user-guide/tips/light-bulb.png
|
|
---
|
|
|
|
<Frame>
|
|
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
|
</Frame>
|
|
|
|
Этот документ описывает лучшие практики, которых следует придерживаться при работе с фронтендом.
|
|
|
|
## Управление состоянием
|
|
|
|
React и Recoil отвечают за управление состоянием в коде.
|
|
|
|
### Используйте `useRecoilState` для хранения состояния
|
|
|
|
Полезно создавать столько атомов, сколько вам нужно для хранения состояния.
|
|
|
|
<Warning>
|
|
|
|
Лучше использовать дополнительные атомы, чем пытаться быть слишком кратким с передачей свойств.
|
|
|
|
</Warning>
|
|
|
|
```tsx
|
|
export const myAtomState = atom({
|
|
key: 'myAtomState',
|
|
default: 'default value',
|
|
});
|
|
|
|
export const MyComponent = () => {
|
|
const [myAtom, setMyAtom] = useRecoilState(myAtomState);
|
|
|
|
return (
|
|
<div>
|
|
<input
|
|
value={myAtom}
|
|
onChange={(e) => setMyAtom(e.target.value)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Не используйте `useRef` для хранения состояния
|
|
|
|
Избегайте использования `useRef` для хранения состояния.
|
|
|
|
Если вы хотите сохранить состояние, вам следует использовать `useState` или `useRecoilState`.
|
|
|
|
Смотрите [как управлять повторными рендерами](#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] = useRecoilState(dataState);
|
|
const [someDependency] = useRecoilState(someDependencyState);
|
|
|
|
useEffect(() => {
|
|
if(someDependency !== data) {
|
|
setData(someDependency);
|
|
}
|
|
}, [someDependency]);
|
|
|
|
return <div>{data}</div>;
|
|
};
|
|
|
|
export const App = () => (
|
|
<RecoilRoot>
|
|
<PageComponent />
|
|
</RecoilRoot>
|
|
);
|
|
```
|
|
|
|
```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] = useRecoilState(dataState);
|
|
|
|
return <div>{data}</div>;
|
|
};
|
|
|
|
export const PageData = () => {
|
|
const [data, setData] = useRecoilState(dataState);
|
|
const [someDependency] = useRecoilState(someDependencyState);
|
|
|
|
useEffect(() => {
|
|
if(someDependency !== data) {
|
|
setData(someDependency);
|
|
}
|
|
}, [someDependency]);
|
|
|
|
return <></>;
|
|
};
|
|
|
|
export const App = () => (
|
|
<RecoilRoot>
|
|
<PageData />
|
|
<PageComponent />
|
|
</RecoilRoot>
|
|
);
|
|
```
|
|
|
|
### Используйте состояния семейства Recoil и селекторы семейства Recoil
|
|
|
|
Состояния семейства Recoil и селекторы — отличный способ избежать повторных рендеров.
|
|
|
|
Они полезны, когда нужно хранить список элементов.
|
|
|
|
### Не следует использовать `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>;
|
|
```
|
|
|
|
## Изменения с нарушением совместимости
|
|
|
|
Всегда проводите тщательное ручное тестирование перед тем, как продолжить, чтобы гарантировать, что изменения не вызвали перебоев в других местах, поскольку тесты еще не были широко интегрированы.
|
|
|