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
330 lines
10 KiB
Plaintext
330 lines
10 KiB
Plaintext
---
|
|
title: Osvědčené postupy
|
|
icon: star
|
|
---
|
|
|
|
Tento dokument popisuje osvědčené postupy, které byste měli dodržovat při práci na frontend.
|
|
|
|
## Správa stavu
|
|
|
|
React a Jotai zajišťují správu stavu v kódu.
|
|
|
|
### Použijte atomy Jotai k ukládání stavu
|
|
|
|
Je dobrým zvykem vytvořit tolik atomů, kolik potřebujete ke správě stavu.
|
|
|
|
<Warning>
|
|
|
|
Je lepší použít více atomů než se snažit být příliš stručný s prop drilling.
|
|
|
|
</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>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Nepoužívejte `useRef` k ukládání stavu
|
|
|
|
Vyhněte se používání `useRef` k ukládání stavu.
|
|
|
|
Pokud chcete ukládat stav, měli byste použít `useState` nebo atomy Jotai s `useAtomState`.
|
|
|
|
Podívejte se, jak spravovat překreslení, pokud máte pocit, že potřebujete `useRef`, abyste zabránili některým překreslením.
|
|
|
|
## Správa překreslení
|
|
|
|
Překreslení může být obtížné spravovat v Reactu.
|
|
|
|
Zde jsou některá pravidla, která je třeba dodržovat, abyste se vyhnuli zbytečnému překreslování.
|
|
|
|
Pamatujte, že můžete **vždy** zabránit opakovanému renderování pochopením jejich příčiny.
|
|
|
|
### Pracujte na úrovni kořene
|
|
|
|
Vyhýbání se překreslení v nových funkcích je nyní snadné tím, že je eliminujete na úrovni kořene.
|
|
|
|
Komponenta "PageChangeEffect" obsahuje pouze jeden "useEffect", který drží veškerou logiku pro provedení změny stránky.
|
|
|
|
Tímto způsobem víte, že existuje pouze jedno místo, které může spustit překreslování.
|
|
|
|
### Vždy si dvakrát rozmyslete před přidáním `useEffect` do vaší kódové databáze
|
|
|
|
Opakované renderování jsou často způsobeny zbytečným `useEffect`.
|
|
|
|
Měli byste si myslet, zda potřebujete `useEffect`, nebo zda můžete přesunout logiku ve funkci obsluhy událostí.
|
|
|
|
Pro vás je obecně snadné přesunout logiku do funkce `handleClick` nebo `handleChange`.
|
|
|
|
Můžete je také najít v knihovnách jako Apollo: `onCompleted`, `onError` atd.
|
|
|
|
### Použij sourozeneckou komponentu k extrakci `useEffect` nebo logiky načítání dat
|
|
|
|
Pokud máte pocit, že potřebujete přidat `useEffect` do základní komponenty, měli byste zvážit její extrakci v postranním vozíku.
|
|
|
|
Stejný postup můžete aplikovat na logiku získávání dat pomocí 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 />
|
|
</>
|
|
);
|
|
```
|
|
|
|
### Použijte rodiny atomů a selektory
|
|
|
|
Rodiny atomů a selektory jsou skvělým způsobem, jak se vyhnout překreslování.
|
|
|
|
Jsou užitečné, když potřebujete uložit seznam položek.
|
|
|
|
### Neměli byste používat `React.memo(MyComponent)`
|
|
|
|
Vyhněte se používání `React.memo()`, protože to neřeší příčinu překreslování, ale spíše přeruší řetězec překreslování, což může vést k neočekávanému chování a ztěžuje refaktorování kódu.
|
|
|
|
### Omezit použití `useCallback` nebo `useMemo`
|
|
|
|
Často nejsou nutné a kód ztěžují na čtení a údržbu pro nepodstatné zlepšení výkonu.
|
|
|
|
## Console.logs
|
|
|
|
Příkazy `console.log` jsou hodnotné při vývoji, protože poskytují pohled na hodnoty proměnných a průběh kódu v reálném čase. Ale, zanechání v produkčním kódu může vést k několika problémům:
|
|
|
|
1. **Výkon**: Přílišné logování může ovlivnit výkon za běhu, zejména u aplikací na straně klienta.
|
|
|
|
2. **Bezpečnost**: Logování citlivých dat může vystavit kritické informace komukoliv, kdo nahlédne do konzole prohlížeče.
|
|
|
|
3. **Čistota**: Naplnění konzole logy může zatížit důležitá varování nebo chyby, které je třeba vidět.
|
|
|
|
4. **Profesionalita**: Koneční uživatelé nebo klienti kontrolující konzolu a vidící množství logovacích příkazů mohou zpochybnit kvalitu a úroveň kódu.
|
|
|
|
Ujistěte se, že odstraníte všechny `console.logs`, než uvedete kód do produkce.
|
|
|
|
## Pojmenovávání
|
|
|
|
### Pojmenování proměnných
|
|
|
|
Jména proměnných by měla přesně zobrazovat účel nebo funkci proměnné.
|
|
|
|
#### Problém s generickými jmény
|
|
|
|
Generická jména v programování nejsou ideální, protože postrádají specifikaci, což vede k nejednoznačnosti a snižuje čitelnost kódu. Taková jména neposkytnou informace o účelu proměnné nebo funkce, čímž ztěžují vývojářům pochopení záměru kódu bez hlubšího zkoumání. To může vést ke zvýšené době ladění, vyšší náchylnosti k chybám a obtížím při údržbě a spolupráci. Mezitím, použití popisných jmen činí kód samozřejmým a snadněji navigovatelným, čímž se zvyšuje jeho kvalita a produktivita vývojáře.
|
|
|
|
```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('');
|
|
```
|
|
|
|
#### Některá slova, kterým se vyhnout v názvech proměnných
|
|
|
|
* dummy
|
|
|
|
### Obsluhovače událostí
|
|
|
|
Jména obsluhovačů událostí by měla začínat `handle`, zatímco `on` je prefix používaný k pojmenování událostí v komponentech props.
|
|
|
|
```tsx
|
|
// ❌ Bad
|
|
const onEmailChange = (val: string) => {
|
|
// ...
|
|
};
|
|
```
|
|
|
|
```tsx
|
|
// ✅ Good
|
|
const handleEmailChange = (val: string) => {
|
|
// ...
|
|
};
|
|
```
|
|
|
|
## Volitelné props
|
|
|
|
Vyhněte se tomu, abyste pro volitelný props předávali výchozí hodnotu.
|
|
|
|
**PŘÍKLAD**
|
|
|
|
Vezměte komponent `EmailField` definovanou níže:
|
|
|
|
```tsx
|
|
type EmailFieldProps = {
|
|
value: string;
|
|
disabled?: boolean;
|
|
};
|
|
|
|
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
|
|
<TextInput value={value} disabled={disabled} fullWidth />
|
|
);
|
|
```
|
|
|
|
**Použití**
|
|
|
|
```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" />;
|
|
```
|
|
|
|
## Komponenta jako props
|
|
|
|
Pokuste se co nejvíce předávat neinstancované komponenty jako props, aby si děti mohly samy rozhodnout, které props potřebují předat.
|
|
|
|
Nejčastější příklad je komponenta 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 pochopil, že komponenta je komponenta, musíte použít PascalCase, aby později ji bylo možné instancovat pomocí `<MyIcon>`
|
|
|
|
## Prop Drilling: Udržujte to minimální
|
|
|
|
Prop drilling, v kontextu React, odkazuje na praktiku předávání stavových proměnných a jejich setterů skrze mnoho vrstev komponent, i když mezilehlé komponenty je nepoužívají. Zatímco občas je to nutné, přehnané prop drilling může vést k:
|
|
|
|
1. **Snížená čitelnost**: Sledování původu nebo využití vlastnosti může být komplikované ve složitě vnořených strukturách komponent.
|
|
|
|
2. **Problémy s údržbou**: Změny v struktuře vlastností jedné komponenty mohou vyžadovat úpravy v některých komponentech, i když tyto vlastnosti přímo nepoužívají.
|
|
|
|
3. **Snížená znovupoužitelnost komponentů**: Komponent přijímající mnoho props pouze pro jejich předání, se stává méně univerzálním a obtížněji použitelným v různých kontextech.
|
|
|
|
Pokud máte pocit, že používáte přílišné prop drilling, podívejte se na osvědčené postupy správy stavu.
|
|
|
|
## Importy
|
|
|
|
Při importu, upřednostněte určené aliasy před upřesňováním úplných či relativních cest.
|
|
|
|
**Aliasy**
|
|
|
|
```js
|
|
{
|
|
alias: {
|
|
"~": path.resolve(__dirname, "src"),
|
|
"@": path.resolve(__dirname, "src/modules"),
|
|
"@testing": path.resolve(__dirname, "src/testing"),
|
|
},
|
|
}
|
|
```
|
|
|
|
**Použití**
|
|
|
|
```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';
|
|
```
|
|
|
|
## Validace schématu
|
|
|
|
[Zod](https://github.com/colinhacks/zod) je validátor schémat pro netypová data:
|
|
|
|
```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>;
|
|
```
|
|
|
|
## Zlomové změny
|
|
|
|
Vždy proveďte důkladné manuální testování, abyste zajistili, že úpravy nezpůsobily problémy jinde, zejména pokud testy zatím nebyly široce integrovány.
|