Naučite se sestavljati React hooke po meri za abstrakcijo kompleksne logike, boljšo ponovno uporabo kode in lažje vzdrževanje. Vključuje praktične primere in najboljše prakse.
Kompozicija React hookov po meri: Obvladovanje abstrakcije kompleksne logike
React hooki po meri so močno orodje za enkapsulacijo in ponovno uporabo logike s stanjem (stateful logic) znotraj vaših React aplikacij. Vendar pa, ko vaše aplikacije postajajo kompleksnejše, postaja kompleksnejša tudi logika znotraj vaših hookov po meri. To lahko vodi do monolitnih hookov, ki jih je težko razumeti, testirati in vzdrževati. Kompozicija hookov po meri ponuja rešitev za to težavo, saj vam omogoča razgradnjo kompleksne logike na manjše, bolj obvladljive in ponovno uporabne hooke.
Kaj je kompozicija hookov po meri?
Kompozicija hookov po meri je praksa združevanja več manjših hookov po meri za ustvarjanje bolj kompleksne funkcionalnosti. Namesto da bi ustvarili en sam, velik hook, ki obravnava vse, ustvarite več manjših hookov, od katerih je vsak odgovoren za določen vidik logike. Te manjše hooke lahko nato sestavite skupaj, da dosežete želeno funkcionalnost.
Predstavljajte si to kot gradnjo z LEGO kockami. Vsaka kocka (majhen hook) ima specifično funkcijo in jih na različne načine kombinirate za gradnjo kompleksnih struktur (večjih funkcionalnosti).
Prednosti kompozicije hookov po meri
- Izboljšana ponovna uporaba kode: Manjši, bolj osredotočeni hooki so po naravi bolj ponovno uporabni v različnih komponentah in celo v različnih projektih.
- Lažje vzdrževanje: Razgradnja kompleksne logike na manjše, samostojne enote olajša razumevanje, odpravljanje napak in spreminjanje kode. Spremembe v enem hooku manj verjetno vplivajo na druge dele vaše aplikacije.
- Povečana testabilnost: Manjše hooke je lažje testirati posamično, kar vodi do bolj robustne in zanesljive kode.
- Boljša organizacija kode: Kompozicija spodbuja bolj modularno in organizirano kodno bazo, kar olajša navigacijo in razumevanje odnosov med različnimi deli vaše aplikacije.
- Zmanjšano podvajanje kode: Z izvlečenjem skupne logike v ponovno uporabne hooke zmanjšate podvajanje kode, kar vodi do bolj jedrnate in vzdržljive kodne baze.
Kdaj uporabiti kompozicijo hookov po meri?
Razmislite o uporabi kompozicije hookov po meri, kadar:
- En sam hook po meri postaja prevelik in kompleksen.
- Ugotovite, da podvajate podobno logiko v več hookih po meri ali komponentah.
- Želite izboljšati testabilnost svojih hookov po meri.
- Želite ustvariti bolj modularno in ponovno uporabno kodno bazo.
Osnovna načela kompozicije hookov po meri
Tukaj je nekaj ključnih načel, ki vas bodo vodila pri pristopu h kompoziciji hookov po meri:
- Načelo enotne odgovornosti: Vsak hook po meri bi moral imeti eno samo, dobro opredeljeno odgovornost. Tako jih je lažje razumeti, testirati in ponovno uporabiti.
- Ločevanje skrbi: Ločite različne vidike vaše logike v različne hooke. Na primer, lahko imate en hook za pridobivanje podatkov, drugega za upravljanje stanja in tretjega za obravnavo stranskih učinkov.
- Sestavljivost (kompozabilnost): Oblikujte svoje hooke tako, da jih je mogoče enostavno sestaviti z drugimi hooki. To pogosto vključuje vračanje podatkov ali funkcij, ki jih lahko uporabijo drugi hooki.
- Konvencije poimenovanja: Uporabite jasna in opisna imena za svoje hooke, da označite njihov namen in funkcionalnost. Pogosta konvencija je, da se imena hookov začnejo s predpono `use`.
Pogosti vzorci kompozicije
Za sestavljanje hookov po meri se lahko uporabi več vzorcev. Tukaj so nekateri najpogostejši:
1. Enostavna kompozicija hookov
To je najosnovnejša oblika kompozicije, kjer en hook preprosto pokliče drugega in uporabi njegovo vrnjeno vrednost.
Primer: Predstavljajte si, da imate hook za pridobivanje uporabniških podatkov in drugega za formatiranje datumov. Te hooke lahko sestavite, da ustvarite nov hook, ki pridobi uporabniške podatke in formatira uporabnikov datum registracije.
import { useState, useEffect } from 'react';
function useUserData(userId) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const jsonData = await response.json();
setData(jsonData);
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
fetchData();
}, [userId]);
return { data, loading, error };
}
function useFormattedDate(dateString) {
try {
const date = new Date(dateString);
const formattedDate = date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' });
return formattedDate;
} catch (error) {
console.error("Error formatting date:", error);
return "Invalid Date";
}
}
function useUserWithFormattedDate(userId) {
const { data, loading, error } = useUserData(userId);
const formattedRegistrationDate = data ? useFormattedDate(data.registrationDate) : null;
return { ...data, formattedRegistrationDate, loading, error };
}
export default useUserWithFormattedDate;
Pojasnilo:
useUserDatapridobi uporabniške podatke iz API-ja.useFormattedDateformatira datumski niz v uporabniku prijazno obliko. Elegantno obravnava morebitne napake pri razčlenjevanju datuma. Argument `undefined` v `toLocaleDateString` uporabi uporabnikovo lokalizacijo za formatiranje.useUserWithFormattedDatesestavi oba hooka. Najprej uporabi `useUserData` za pridobitev uporabniških podatkov. Nato, če so podatki na voljo, uporabi `useFormattedDate` za formatiranje `registrationDate`. Na koncu vrne originalne uporabniške podatke skupaj s formatiranim datumom, stanjem nalaganja in morebitnimi napakami.
2. Kompozicija hookov z deljenim stanjem
V tem vzorcu več hookov deli in spreminja isto stanje. To je mogoče doseči z uporabo useContext ali s posredovanjem stanja in funkcij za nastavitev (setter) med hooki.
Primer: Predstavljajte si gradnjo večstopenjskega obrazca. Vsak korak bi lahko imel svoj hook za upravljanje specifičnih vnosnih polj in validacijske logike tega koraka, vendar vsi delijo skupno stanje obrazca, ki ga upravlja nadrejeni hook z uporabo useReducer in useContext.
import React, { createContext, useContext, useReducer } from 'react';
// Define the initial state
const initialState = {
step: 1,
name: '',
email: '',
address: ''
};
// Define the actions
const ACTIONS = {
NEXT_STEP: 'NEXT_STEP',
PREVIOUS_STEP: 'PREVIOUS_STEP',
UPDATE_FIELD: 'UPDATE_FIELD'
};
// Create the reducer
function formReducer(state, action) {
switch (action.type) {
case ACTIONS.NEXT_STEP:
return { ...state, step: state.step + 1 };
case ACTIONS.PREVIOUS_STEP:
return { ...state, step: state.step - 1 };
case ACTIONS.UPDATE_FIELD:
return { ...state, [action.payload.field]: action.payload.value };
default:
return state;
}
}
// Create the context
const FormContext = createContext();
// Create a provider component
function FormProvider({ children }) {
const [state, dispatch] = useReducer(formReducer, initialState);
const value = {
state,
dispatch,
nextStep: () => dispatch({ type: ACTIONS.NEXT_STEP }),
previousStep: () => dispatch({ type: ACTIONS.PREVIOUS_STEP }),
updateField: (field, value) => dispatch({ type: ACTIONS.UPDATE_FIELD, payload: { field, value } })
};
return (
{children}
);
}
// Custom hook for accessing the form context
function useFormContext() {
const context = useContext(FormContext);
if (!context) {
throw new Error('useFormContext must be used within a FormProvider');
}
return context;
}
// Custom hook for Step 1
function useStep1() {
const { state, updateField } = useFormContext();
const updateName = (value) => updateField('name', value);
return {
name: state.name,
updateName
};
}
// Custom hook for Step 2
function useStep2() {
const { state, updateField } = useFormContext();
const updateEmail = (value) => updateField('email', value);
return {
email: state.email,
updateEmail
};
}
// Custom hook for Step 3
function useStep3() {
const { state, updateField } = useFormContext();
const updateAddress = (value) => updateField('address', value);
return {
address: state.address,
updateAddress
};
}
export { FormProvider, useFormContext, useStep1, useStep2, useStep3 };
Pojasnilo:
FormContextje ustvarjen z `createContext` za shranjevanje stanja obrazca in funkcije dispatch.formReducerupravlja posodobitve stanja obrazca z `useReducer`. Dejanja, kot soNEXT_STEP,PREVIOUS_STEPinUPDATE_FIELD, so definirana za spreminjanje stanja.- Komponenta
FormProviderposreduje kontekst obrazca svojim otrokom, s čimer omogoči dostop do stanja in funkcije dispatch vsem korakom obrazca. Prav tako izpostavi pomožne funkcije za `nextStep`, `previousStep` in `updateField`, da poenostavi proženje dejanj. - Hook
useFormContextomogoča komponentam dostop do vrednosti konteksta obrazca. - Vsak korak (
useStep1,useStep2,useStep3) ustvari svoj hook za upravljanje vnosa, povezanega s svojim korakom, in uporabljauseFormContextza pridobitev stanja in funkcije dispatch za njegovo posodobitev. Vsak korak izpostavi samo podatke in funkcije, ki so pomembne za ta korak, s čimer se drži načela enotne odgovornosti.
3. Kompozicija hookov z upravljanjem življenjskega cikla
Ta vzorec vključuje hooke, ki upravljajo različne faze življenjskega cikla komponente, kot so pripenjanje (mounting), posodabljanje in odpenjanje (unmounting). To se pogosto doseže z uporabo useEffect znotraj sestavljenih hookov.
Primer: Predstavljajte si komponento, ki mora slediti statusu povezanosti (online/offline) in mora ob odpenjanju izvesti tudi nekaj čiščenja. Za vsako od teh nalog lahko ustvarite ločene hooke in jih nato sestavite.
import { useState, useEffect } from 'react';
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
return () => {
document.title = 'Original Title'; // Revert to a default title on unmount
};
}, [title]);
}
function useAppLifecycle(title) {
const isOnline = useOnlineStatus();
useDocumentTitle(title);
return isOnline; // Return the online status
}
export { useAppLifecycle, useOnlineStatus, useDocumentTitle };
Pojasnilo:
useOnlineStatussledi statusu povezanosti uporabnika z uporabo dogodkovonlineinoffline. HookuseEffectnastavi poslušalce dogodkov, ko se komponenta pripne, in jih očisti, ko se odepne.useDocumentTitleposodobi naslov dokumenta. Prav tako povrne naslov na privzeto vrednost, ko se komponenta odepne, s čimer zagotovi, da ni ostankov težav z naslovom.useAppLifecyclesestavi oba hooka. UporabljauseOnlineStatusza določanje, ali je uporabnik povezan, inuseDocumentTitleza nastavitev naslova dokumenta. Združeni hook vrne status povezanosti.
Praktični primeri in primeri uporabe
1. Internacionalizacija (i18n)
Upravljanje prevodov in preklapljanje med lokalizacijami lahko postane zapleteno. Za ločevanje skrbi lahko uporabite kompozicijo hookov:
useLocale(): Upravlja trenutno lokalizacijo.useTranslations(): Pridobi in zagotovi prevode za trenutno lokalizacijo.useTranslate(key): Hook, ki vzame ključ za prevod in vrne preveden niz, pri čemer uporablja hookuseTranslationsza dostop do prevodov.
To vam omogoča enostavno preklapljanje med lokalizacijami in dostop do prevodov po celotni aplikaciji. Razmislite o uporabi knjižnic, kot je i18next, skupaj s hooki po meri za upravljanje logike prevajanja. Na primer, useTranslations bi lahko naložil prevode na podlagi izbrane lokalizacije iz datotek JSON v različnih jezikih.
2. Validacija obrazcev
Kompleksni obrazci pogosto zahtevajo obsežno validacijo. Za ustvarjanje ponovno uporabne validacijske logike lahko uporabite kompozicijo hookov:
useInput(initialValue): Upravlja stanje posameznega vnosnega polja.useValidator(value, rules): Validira posamezno vnosno polje na podlagi niza pravil (npr. obvezno, e-pošta, minLength).useForm(fields): Upravlja stanje in validacijo celotnega obrazca, pri čemer za vsako polje sestaviuseInputinuseValidator.
Ta pristop spodbuja ponovno uporabo kode in olajša dodajanje ali spreminjanje validacijskih pravil. Knjižnice, kot sta Formik ali React Hook Form, ponujajo vnaprej pripravljene rešitve, ki pa jih je mogoče dopolniti s hooki po meri za specifične validacijske potrebe.
3. Pridobivanje in predpomnjenje podatkov
Upravljanje pridobivanja podatkov, predpomnjenja in obravnavanja napak je mogoče poenostaviti s kompozicijo hookov:
useFetch(url): Pridobi podatke z danega URL-ja.useCache(key, fetchFunction): Predpomni rezultat funkcije za pridobivanje podatkov z uporabo ključa.useData(url, options): ZdružujeuseFetchinuseCacheza pridobivanje podatkov in predpomnjenje rezultatov.
To vam omogoča enostavno predpomnjenje pogosto dostopanih podatkov in izboljšanje zmogljivosti. Knjižnice, kot sta SWR (Stale-While-Revalidate) in React Query, ponujajo močne rešitve za pridobivanje in predpomnjenje podatkov, ki jih je mogoče razširiti s hooki po meri.
4. Avtentikacija
Obravnavanje avtentikacijske logike je lahko zapleteno, zlasti pri delu z različnimi metodami avtentikacije (npr. JWT, OAuth). Kompozicija hookov lahko pomaga ločiti različne vidike avtentikacijskega procesa:
useAuthToken(): Upravlja avtentikacijski žeton (npr. shranjevanje in pridobivanje iz lokalnega pomnilnika).useUser(): Pridobi in zagotovi informacije o trenutnem uporabniku na podlagi avtentikacijskega žetona.useAuth(): Zagotavlja funkcije, povezane z avtentikacijo, kot so prijava, odjava in registracija, pri čemer sestavlja druge hooke.
Ta pristop vam omogoča enostavno preklapljanje med različnimi metodami avtentikacije ali dodajanje novih funkcionalnosti v avtentikacijski proces. Knjižnice, kot sta Auth0 in Firebase Authentication, se lahko uporabljajo kot zaledje za upravljanje uporabniških računov in avtentikacije, za interakcijo s temi storitvami pa se lahko ustvarijo hooki po meri.
Najboljše prakse za kompozicijo hookov po meri
- Ohranite osredotočenost hookov: Vsak hook bi moral imeti jasen in specifičen namen.
- Izogibajte se globokemu gnezdenju: Omejite število ravni kompozicije, da se izognete težko razumljivi kodi. Če hook postane preveč zapleten, razmislite o njegovi nadaljnji razgradnji.
- Dokumentirajte svoje hooke: Zagotovite jasno in jedrnato dokumentacijo za vsak hook, ki pojasnjuje njegov namen, vhode in izhode. To je še posebej pomembno za hooke, ki jih uporabljajo drugi razvijalci.
- Testirajte svoje hooke: Napišite enotske teste za vsak hook, da zagotovite, da deluje pravilno. To je še posebej pomembno za hooke, ki upravljajo stanje ali izvajajo stranske učinke.
- Razmislite o uporabi knjižnice za upravljanje stanja: Za kompleksne scenarije upravljanja stanja razmislite o uporabi knjižnice, kot so Redux, Zustand ali Jotai. Te knjižnice ponujajo naprednejše funkcije za upravljanje stanja in lahko poenostavijo kompozicijo hookov.
- Pomislite na obravnavo napak: V svoje hooke implementirajte robustno obravnavo napak, da preprečite nepričakovano vedenje. Razmislite o uporabi blokov try-catch za lovljenje napak in zagotavljanje informativnih sporočil o napakah.
- Upoštevajte zmogljivost: Bodite pozorni na posledice zmogljivosti vaših hookov. Izogibajte se nepotrebnim ponovnim upodabljanjem (re-renders) in optimizirajte svojo kodo za zmogljivost. Uporabite React.memo, useMemo in useCallback za optimizacijo zmogljivosti, kjer je to primerno.
Zaključek
Kompozicija React hookov po meri je močna tehnika za abstrakcijo kompleksne logike ter izboljšanje ponovne uporabe kode, vzdržljivosti in testabilnosti. Z razgradnjo kompleksnih nalog na manjše, bolj obvladljive hooke lahko ustvarite bolj modularno in organizirano kodno bazo. Z upoštevanjem najboljših praks, opisanih v tem članku, lahko učinkovito izkoristite kompozicijo hookov po meri za gradnjo robustnih in razširljivih React aplikacij. Ne pozabite vedno dati prednost jasnosti in preprostosti v svoji kodi ter se ne bojte eksperimentirati z različnimi vzorci kompozicije, da najdete, kaj najbolje ustreza vašim specifičnim potrebam.