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
It’s good practice to create as many atoms as you need to store your state.
It’s better to use extra atoms than trying to be too concise with props 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.
See how to manage re-renders if you feel like you need useRef to prevent some re-renders from happening.
Managing re-renders
Re-render-urile pot fi greu de gestionat în React.
Aici sunt câteva reguli de urmat pentru a evita re-render-urile inutile.
Keep in mind that you can always avoid re-renders by understanding their cause.
Lucrați la nivelul rădăcinii
Avoiding re-renders in new features is now made easy by eliminating them at the root level.
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.
// ❌ 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>
);
// ✅ 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>
);
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.
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
// ✅ Good, uses a descriptive name
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.
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
// ✅ Good
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
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
// ✅ Good, assumes the default value
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} />;
// In 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
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
// ✅ Good, utilises the designated aliases
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('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>;
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.