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
10 KiB
Plaintext
334 lines
10 KiB
Plaintext
---
|
|
title: Najlepsze praktyki
|
|
image: /images/user-guide/tips/light-bulb.png
|
|
---
|
|
|
|
<Frame>
|
|
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
|
</Frame>
|
|
|
|
Ten dokument opisuje najlepsze praktyki, które powinieneś stosować podczas pracy nad frontendem.
|
|
|
|
## Zarządzanie stanem
|
|
|
|
React i Recoil obsługują zarządzanie stanem w kodzie.
|
|
|
|
### Użyj `useRecoilState` do przechowywania stanu
|
|
|
|
Dobrą praktyką jest tworzenie tylu atomów, ile potrzeba do przechowywania stanu.
|
|
|
|
<Warning>
|
|
|
|
Lepiej użyć dodatkowych atomów niż próbować być zbyt oszczędnym przy przekazywaniu atrybutów.
|
|
|
|
</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>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Nie używaj `useRef` do przechowywania stanu
|
|
|
|
Unikaj używania `useRef` do przechowywania stanu.
|
|
|
|
Jeśli chcesz przechowywać stan, powinieneś użyć `useState` lub `useRecoilState`.
|
|
|
|
Zobacz [jak zarządzać ponownymi renderowaniami](#managing-re-renders) jeśli czujesz, że potrzebujesz `useRef` do zapobiegania niektórym ponownym renderowaniom.
|
|
|
|
## Zarządzanie ponownymi renderowaniami
|
|
|
|
Ponowne renderowania mogą być trudne do zarządzania w React.
|
|
|
|
Oto kilka zasad do stosowania, aby unikać niepotrzebnych ponownych renderowań.
|
|
|
|
Pamiętaj, że możesz **zawsze** unikać ponownych renderowań poprzez zrozumienie ich przyczyny.
|
|
|
|
### Pracuj na poziomie korzenia
|
|
|
|
Unikanie ponownych renderowań w nowych funkcjach jest teraz łatwe dzięki eliminacji ich na poziomie korzenia.
|
|
|
|
Komponent boczny `PageChangeEffect` zawiera tylko jedno `useEffect`, które zawiera całą logikę do wykonania przy zmianie strony.
|
|
|
|
W ten sposób wiesz, że jest tylko jedno miejsce, które może wywołać ponowne renderowanie.
|
|
|
|
### Zawsze zastanów się dwa razy, zanim dodasz `useEffect` do swojego kodu
|
|
|
|
Ponowne renderowania są często powodowane przez niepotrzebne `useEffect`.
|
|
|
|
Powinieneś zastanowić się, czy potrzebujesz `useEffect`, czy możesz przenieść logikę do funkcji obsługującej zdarzenie.
|
|
|
|
Zazwyczaj łatwo jest przenieść logikę do funkcji `handleClick` lub `handleChange`.
|
|
|
|
Możesz je również znaleźć w bibliotekach takich jak Apollo: `onCompleted`, `onError`, itp.
|
|
|
|
### Użyj sąsiedniego komponentu do wyciągania logiki `useEffect` lub pobierania danych
|
|
|
|
Jeśli czujesz, że musisz dodać `useEffect` w swoim głównym komponencie, powinieneś rozważyć wyciągnięcie go do komponentu bocznego.
|
|
|
|
To samo możesz zastosować do logiki pobierania danych, z użyciem haków 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>
|
|
);
|
|
```
|
|
|
|
### Używaj stanów rodzinnych Recoil i selektorów rodzinnych Recoil
|
|
|
|
Stany rodzinne i selektory rodziny Recoil to świetny sposób na unikanie ponownych renderowań.
|
|
|
|
Są przydatne, gdy trzeba przechowywać listę przedmiotów.
|
|
|
|
### Nie powinieneś używać `React.memo(MyComponent)`
|
|
|
|
Unikaj używania `React.memo()`, ponieważ nie rozwiązuje to przyczyny ponownego renderowania, ale zamiast tego łamie łańcuch ponownego renderowania, co może prowadzić do nieoczekiwanego zachowania i uczynić kod trudnym do refaktoryzacji.
|
|
|
|
### Ogranicz użycie `useCallback` lub `useMemo`
|
|
|
|
Często nie są one konieczne i utrudnią czytelność i konserwację kodu dla zysków w wydajności które są nieznaczące.
|
|
|
|
## Console.logs
|
|
|
|
Instrukcje `console.log` są wartościowe podczas dewelopmentu, oferując wgląd w czasie rzeczywistym w wartości zmiennych i przepływ kodu. Ale pozostawienie ich w kodzie produkcyjnym może prowadzić do różnych problemów:
|
|
|
|
1. **Wydajność**: Nadmierne logowanie może wpływać na wydajność podczas działania, szczególnie w aplikacjach po stronie klienta.
|
|
|
|
2. **Bezpieczeństwo**: Logowanie danych wrażliwych może ujawnić ważne informacje każdemu, kto przegląda konsolę przeglądarki.
|
|
|
|
3. **Przejrzystość**: Wypełnienie konsoli logami może zamazać ważne ostrzeżenia lub błędy, które deweloperzy lub narzędzia muszą zobaczyć.
|
|
|
|
4. **Profesjonalizm**: Użytkownicy końcowi lub klienci sprawdzający konsolę i widzący mnóstwo logów mogą poddawać w wątpliwość jakość i wykończenie kodu.
|
|
|
|
Upewnij się, że usuniesz wszystkie `console.logs` przed przesłaniem kodu do produkcji.
|
|
|
|
## Nazewnictwo
|
|
|
|
### Nazewnictwo zmiennych
|
|
|
|
Nazwy zmiennych powinny dokładnie opisywać cel lub funkcję zmiennej.
|
|
|
|
#### Problem z ogólnymi nazwami
|
|
|
|
Ogólne nazwy w programowaniu nie są idealne, ponieważ brakuje im specyficzności, co prowadzi do niejasności i zmniejszonej czytelności kodu. Takie nazwy nie przekazują celu zmiennej lub funkcji, co utrudnia deweloperom zrozumienie intencji kodu bez głębszego dochodzenia. Może to prowadzić do zwiększonego czasu debugowania, większej podatności na błędy oraz trudności w utrzymaniu i współpracy. Tymczasem, opisowe nazewnictwo sprawia, że kod jest samowyjaśniający się i łatwiejszy do nawigacji, poprawiając jakość kodu i wydajność dewelopera.
|
|
|
|
```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('');
|
|
```
|
|
|
|
#### Niektóre słowa do unikania w nazwach zmiennych
|
|
|
|
- attrapa
|
|
|
|
### Obsługa zdarzeń
|
|
|
|
Nazwy obsług procedur zdarzeń powinny zaczynać się od `handle`, podczas gdy `on` jest prefiksem używanym do nazwania zdarzeń w atrybutach komponentu.
|
|
|
|
```tsx
|
|
// ❌ Bad
|
|
const onEmailChange = (val: string) => {
|
|
// ...
|
|
};
|
|
```
|
|
|
|
```tsx
|
|
// ✅ Good
|
|
const handleEmailChange = (val: string) => {
|
|
// ...
|
|
};
|
|
```
|
|
|
|
## Opcjonalne Atrybuty
|
|
|
|
Unikaj przekazywania wartości domyślnej dla opcjonalnego atrybutu.
|
|
|
|
**PRZYKŁAD**
|
|
|
|
Zobacz komponent `EmailField` zdefiniowany poniżej:
|
|
|
|
```tsx
|
|
type EmailFieldProps = {
|
|
value: string;
|
|
disabled?: boolean;
|
|
};
|
|
|
|
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
|
|
<TextInput value={value} disabled={disabled} fullWidth />
|
|
);
|
|
```
|
|
|
|
**Zastosowanie**
|
|
|
|
```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" />;
|
|
```
|
|
|
|
## Komponent jako atrybut
|
|
|
|
Spróbuj, o ile to możliwe, przekazywać niezainicjowane komponenty jako props, aby dzieci mogły same decydować, jakie propsy potrzebują przesłać.
|
|
|
|
Najczęstszym przykładem są komponenty ikon:
|
|
|
|
```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>
|
|
)
|
|
};
|
|
```
|
|
|
|
Aby React zrozumiał, że komponent to komponent, musisz użyć PascalCase, aby później zainicjować go jako `<MyIcon>`.
|
|
|
|
## Prop Drilling: Zachowaj Minimalizm
|
|
|
|
Prop drilling w kontekście React, odnosi się do praktyki przekazywania zmiennych stanu i ich ustawień przez wiele warstw komponentów, nawet jeśli komponenty pośrednie ich nie używają. Chociaż czasami jest to konieczne, nadmierne prop drilling może prowadzić do:
|
|
|
|
1. **Zmniejszona czytelność**: Śledzenie, skąd pochodzi prop lub gdzie jest używany, może stać się skomplikowane w głęboko zagnieżdżonej strukturze komponentów.
|
|
|
|
2. **Wyzwania związane z utrzymaniem**: Zmiany w strukturze prop jednego komponentu mogą wymagać dostosowań w kilku komponentach, nawet jeśli bezpośrednio nie używają tego prop.
|
|
|
|
3. **Zmniejszona możliwość ponownego użycia komponentu**: Komponent otrzymujący wiele propsów tylko po to, aby przesłać je dalej, staje się mniej uniwersalny i trudniejszy do ponownego użycia w różnych kontekstach.
|
|
|
|
Jeśli czujesz, że używasz nadmiernego prop drilling, zobacz [najlepsze praktyki zarządzania stanem](#state-management).
|
|
|
|
## Importuj
|
|
|
|
Podczas importowania, wybieraj przypisane aliasy zamiast szczegółowych lub względnych ścieżek.
|
|
|
|
**Aliasy uchwytu**
|
|
|
|
```js
|
|
{
|
|
alias: {
|
|
"~": path.resolve(__dirname, "src"),
|
|
"@": path.resolve(__dirname, "src/modules"),
|
|
"@testing": path.resolve(__dirname, "src/testing"),
|
|
},
|
|
}
|
|
```
|
|
|
|
**Zastosowanie**
|
|
|
|
```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';
|
|
```
|
|
|
|
## Walidacja schematu
|
|
|
|
[Zod](https://github.com/colinhacks/zod) jest walidatorem schematów dla obiektów nieopisanych:
|
|
|
|
```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>;
|
|
```
|
|
|
|
## Zmiany przełomowe
|
|
|
|
Zawsze wykonuj dokładne testy ręczne przed kontynuacją, aby zagwarantować, że modyfikacje nie spowodowały zakłóceń w innych miejscach, biorąc pod uwagę, że testy nie zostały jeszcze kompleksowo zintegrowane.
|
|
|