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).
|
|
|
|
## Імпорт
|
|
|
|
Під час імпорту обирайте призначені псевдоніми, а не вказуйте повні або відносні шляхи.
|
|
|
|
**The Aliases**
|
|
|
|
```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>;
|
|
```
|
|
|
|
## Критичні зміни
|
|
|
|
Завжди проводьте ретельне ручне тестування перед тим, як продовжити, щоб гарантувати, що модифікації не спричинили порушень в інших місцях, враховуючи, що тести ще не були повністю інтегровані.
|
|
|