Acest document prezintă cele mai bune practici pe care ar trebui să le urmați atunci când lucrați la frontend.
Managementul stării
React și Recoil se ocupă de managementul stării în cod.
Folosiți useRecoilState pentru a stoca starea
Este o bună practică să creezi atâția atomi câți ai nevoie pentru a-ți stoca starea.
Este mai bine să folosești atomi suplimentari decât să încerci să fii prea concis recurgând la prop drilling.
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>
);
}
Nu folosiți useRef pentru a stoca starea
Evitați utilizarea useRef pentru a stoca starea.
Dacă doriți să stocați starea, ar trebui să folosiți useState sau useRecoilState.
Vezi cum să gestionezi re-randările dacă ți se pare că ai nevoie de useRef pentru a preveni apariția unor re-randări.
Gestionarea re-randărilor
Re-render-urile pot fi greu de gestionat în React.
Aici sunt câteva reguli de urmat pentru a evita re-render-urile inutile.
Ține minte că poți întotdeauna evita re-randările înțelegând cauza lor.
Lucrați la nivelul rădăcinii
Evitarea re-randărilor în funcționalități noi este acum ușoară prin eliminarea lor de la rădăcină.
Componenta secundară PageChangeEffect conține doar un useEffect ce deține toată logica de execuție la o schimbare de pagină.
În acest fel știți că există doar un loc care poate declanșa un re-render.
Gândiți-vă de două ori înainte de a adăuga useEffect în baza de cod
Re-render-urile sunt adesea cauzate de useEffect inutile.
Ar trebui să vă gândiți dacă aveți nevoie de useEffect sau dacă puteți muta logica într-o funcție de handler de eveniment.
Vă va fi, în general, ușor să mutați logica într-o funcție handleClick sau handleChange.
De asemenea, le puteți găsi în biblioteci precum Apollo: onCompleted, onError, etc.
Dacă simțiți nevoia să adăugați un useEffect în componenta rădăcină, ar trebui să luați în considerare extragerea sa într-o componentă secundară.
Puteți aplica același principiu pentru logica de interogare de date, folosind hooks cu Apollo.
// ❌ Greșit, va provoca re-randări chiar dacă datele nu se schimbă,
// deoarece useEffect trebuie re-evaluat
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>
);
// ✅ Bun, nu va provoca re-randări dacă datele nu se schimbă,
// deoarece useEffect este re-evaluat într-o altă componentă la același nivel
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>
);
Folosiți stări de familie și selectoare de familie cu recoil
Stările și selectoarele de familie cu recoil sunt o metodă excelentă de a evita re-render-urile.
Sunt utile când trebuie să stocați o listă de elemente.
Nu ar trebui să folosiți React.memo(MyComponent)
Evitați utilizarea React.memo() deoarece nu rezolvă cauza re-render-ului, ci întrerupe lanțul re-render, ceea ce poate duce la comportamente neașteptate și face codul foarte greu de refactorizat.
Limitați utilizarea useCallback sau useMemo
Acestea sunt adesea inutile și vor face codul mai greu de citit și menținut pentru un câștig în performanță care nu este sesizabil.
Console.logs
Instrucțiunile console.log sunt valoroase în timpul dezvoltării, oferind informații în timp real despre valorile variabilelor și fluxul de cod. Totuși, lăsându-le în codul de producție pot duce la mai multe probleme:
-
Performanță: Logările excesive pot afecta performanța de execuție, în special în aplicațiile pe partea de client.
-
Siguranță: Logarea datelor sensibile poate expune informații critice oricui inspectează consola browser-ului.
-
Claritate: Umplerea consolei cu logări poate ascunde avertismente sau erori importante pe care dezvoltatorii sau uneltele le trebuie să le vadă.
-
Profesionalism: Utilizatorii finali sau clienții care verifică consola și văd o multitudine de instrucțiuni de log s-ar putea să pună la îndoială calitatea și finisajul codului.
Asigurați-vă că eliminați toate console.log-urile înainte de a trimite codul în producție.
Denumiri
Denumire de variabilă
Denumirile variabilelor trebuie să descrie cu precizie scopul sau funcția variabilei.
Problema cu denumirile generice
Denumirile generice în programare nu sunt ideale deoarece nu au specificitate, ducând la ambiguitate și la reducerea lizibilității codului. Astfel de denumiri nu reușesc să transmită scopul variabilei sau funcției, ceea ce îngreunează înțelegerea intenției codului de către dezvoltatori fără o investigație mai profundă. Acest lucru poate duce la creșterea timpului de depanare, la o mai mare susceptibilitate la erori și dificultăți în întreținere și colaborare. Între timp, denumirile descriptive fac codul auto-explicativ și mai ușor de navigat, îmbunătățind calitatea codului și productivitatea dezvoltatorilor.
// ❌ Greșit, folosește un nume generic care nu îi comunică
// clar scopul sau conținutul
const [value, setValue] = useState('');
// ✅ Bun, folosește un nume descriptiv
const [email, setEmail] = useState('');
Câteva cuvinte de evitat în denumirile de variabile
Handlere de evenimente
Numele handlerelor de evenimente ar trebui să înceapă cu handle, în timp ce on este un prefix folosit pentru a denumi evenimentele în prop-urile componentelor.
// ❌ Greșit
const onEmailChange = (val: string) => {
// ...
};
// ✅ Bun
const handleEmailChange = (val: string) => {
// ...
};
Props opționale
Evitați să transmiteți valoarea implicită pentru un props opțional.
EXEMPLU
Țineți cont de componenta EmailField definită mai jos:
type EmailFieldProps = {
value: string;
disabled?: boolean;
};
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
<TextInput value={value} disabled={disabled} fullWidth />
);
Utilizare
// ❌ Greșit, transmiterea aceleiași valori ca valoarea implicită nu aduce niciun beneficiu
const Form = () => <EmailField value="username@email.com" disabled={false} />;
// ✅ Bun, presupune valoarea implicită
const Form = () => <EmailField value="username@email.com" />;
Componentă ca props
Încercați, pe cât posibil, să transmiteți componente neinstanțiate ca props, astfel încât copiii să poată decide singuri de ce props au nevoie să transmită.
Cel mai frecvent exemplu pentru aceasta sunt componentele de pictograme:
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// În MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
return (
<div>
<MyIcon size={theme.icon.size.md}>
</div>
)
};
Pentru ca React să înțeleagă că componenta este o componentă, trebuie să folosești PascalCase, pentru a o instanția ulterior cu <MyIcon>
Prop Drilling: Păstrați-l Minimal
Prop drilling, în contextul React, se referă la practica de a transmite variabile de stare și setatorii acestora prin multe straturi de componente, chiar dacă componentele intermediare nu le folosesc. Deși uneori necesar, prop drilling excesiv poate duce la:
-
Citibilitate Scăzută: Urmărirea de unde provine un prop sau unde este utilizat poate deveni complexă într-o structură de componente foarte profundă.
-
Provocări de Mentenanță: Schimbările în structura props a unei componente pot necesita ajustări în mai multe componente, chiar dacă acestea nu utilizează direct prop-ul.
-
Reutilizabilitate Redusă a Componentelor: O componentă ce primește multe props doar pentru a le transmite mai departe devine mai puțin de uz general și mai greu de reutilizat în contexte diferite.
Dacă simțiți că folosiți prea mult prop drilling, consultați cele mai bune practici de gestionare a stării.
Importuri
Când importați, optați pentru pseudonimele desemnate în loc să specificați căi complete sau relative.
Pseudonimele
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
Utilizare
// ❌ Greșit, specifică întreaga cale relativă
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
// ✅ Bun, utilizează aliasurile stabilite
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
Validarea Schemelor
Zod este validatorul de scheme pentru obiectele netipizate:
const validationSchema = z
.object({
exist: z.boolean(),
email: z
.string()
.email('Adresa de email trebuie să fie validă'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Parola trebuie să conțină cel puțin 8 caractere'),
})
.required();
type Form = z.infer<typeof validationSchema>;
Schimbări Majore
Efectuați întotdeauna teste manuale temeinice înainte de a continua pentru a garanta că modificările nu au cauzat întreruperi în altă parte, având în vedere că testele nu au fost încă integrate extensiv.