Naučite kako efikasno komponirati React custom hookove za apstrakciju složene logike, poboljšanje ponovne iskoristivosti koda i olakšavanje održavanja u vašim projektima. Uključuje praktične primjere i najbolje prakse.
Kompozicija React Custom Hookova: Ovladavanje Apstrakcijom Kompleksne Logike
React custom hookovi moćan su alat za enkapsulaciju i ponovnu upotrebu logike sa stanjem (stateful logic) unutar vaših React aplikacija. Međutim, kako vaše aplikacije postaju složenije, tako raste i logika unutar vaših custom hookova. To može dovesti do monolitnih hookova koje je teško razumjeti, testirati i održavati. Kompozicija custom hookova nudi rješenje za ovaj problem omogućujući vam da razbijete složenu logiku na manje, lakše upravljive i ponovno iskoristive hookove.
Što je kompozicija custom hookova?
Kompozicija custom hookova je praksa kombiniranja više manjih custom hookova kako bi se stvorila složenija funkcionalnost. Umjesto stvaranja jednog velikog hooka koji se bavi svime, stvarate nekoliko manjih hookova, od kojih je svaki odgovoran za specifičan aspekt logike. Ti manji hookovi se zatim mogu komponirati zajedno kako bi se postigla željena funkcionalnost.
Zamislite to kao gradnju LEGO kockama. Svaka kocka (mali hook) ima specifičnu funkciju, a vi ih kombinirate na različite načine kako biste izgradili složene strukture (veće funkcionalnosti).
Prednosti kompozicije custom hookova
- Poboljšana ponovna iskoristivost koda: Manji, fokusiraniji hookovi inherentno su ponovno iskoristiviji u različitim komponentama, pa čak i različitim projektima.
- Olakšano održavanje: Razbijanje složene logike na manje, samostalne jedinice olakšava razumijevanje, otklanjanje pogrešaka i izmjenu koda. Promjene u jednom hooku manje će vjerojatno utjecati na druge dijelove vaše aplikacije.
- Povećana mogućnost testiranja: Manje hookove je lakše testirati izolirano, što dovodi do robusnijeg i pouzdanijeg koda.
- Bolja organizacija koda: Kompozicija potiče modularniju i organiziraniju bazu koda, olakšavajući navigaciju i razumijevanje odnosa između različitih dijelova vaše aplikacije.
- Smanjeno dupliciranje koda: Izdvajanjem zajedničke logike u ponovno iskoristive hookove, minimizirate dupliciranje koda, što dovodi do sažetije i lakše održive baze koda.
Kada koristiti kompoziciju custom hookova
Trebali biste razmotriti korištenje kompozicije custom hookova kada:
- Jedan custom hook postaje prevelik i složen.
- Primijetite da duplicirate sličnu logiku u više custom hookova ili komponenti.
- Želite poboljšati mogućnost testiranja vaših custom hookova.
- Želite stvoriti modularniju i ponovno iskoristivu bazu koda.
Osnovni principi kompozicije custom hookova
Ovdje su neki ključni principi koji će vas voditi u pristupu kompoziciji custom hookova:
- Princip jedinstvene odgovornosti (Single Responsibility Principle): Svaki custom hook trebao bi imati jednu, dobro definiranu odgovornost. To ih čini lakšima za razumijevanje, testiranje i ponovnu upotrebu.
- Odvajanje odgovornosti (Separation of Concerns): Odvojite različite aspekte vaše logike u različite hookove. Na primjer, mogli biste imati jedan hook za dohvaćanje podataka, drugi za upravljanje stanjem, a treći za rukovanje nuspojavama.
- Mogućnost komponiranja (Composability): Dizajnirajte svoje hookove tako da se mogu lako komponirati s drugim hookovima. To često uključuje vraćanje podataka ili funkcija koje drugi hookovi mogu koristiti.
- Konvencije imenovanja: Koristite jasna i opisna imena za svoje hookove kako biste naznačili njihovu svrhu i funkcionalnost. Uobičajena konvencija je prefiksiranje imena hookova s `use`.
Uobičajeni obrasci kompozicije
Postoji nekoliko obrazaca koji se mogu koristiti za komponiranje custom hookova. Ovdje su neki od najčešćih:
1. Jednostavna kompozicija hookova
Ovo je najosnovniji oblik kompozicije, gdje jedan hook jednostavno poziva drugi hook i koristi njegovu povratnu vrijednost.
Primjer: Zamislite da imate hook za dohvaćanje korisničkih podataka i drugi za formatiranje datuma. Možete komponirati te hookove kako biste stvorili novi hook koji dohvaća korisničke podatke i formatira datum registracije korisnika.
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;
Objašnjenje:
useUserDatadohvaća korisničke podatke s API-ja.useFormattedDateformatira datumski string u korisnički prihvatljiv format. Elegantno obrađuje potencijalne pogreške pri parsiranju datuma. ArgumentundefinedutoLocaleDateStringkoristi korisnikovu lokalizaciju za formatiranje.useUserWithFormattedDatekomponira oba hooka. Prvo koristiuseUserDataza dohvaćanje korisničkih podataka. Zatim, ako su podaci dostupni, koristiuseFormattedDateza formatiranjeregistrationDate. Na kraju, vraća originalne korisničke podatke zajedno s formatiranim datumom, stanjem učitavanja i eventualnim pogreškama.
2. Kompozicija hookova s dijeljenim stanjem
U ovom obrascu, više hookova dijeli i mijenja isto stanje. To se može postići korištenjem useContext ili prosljeđivanjem stanja i setter funkcija između hookova.
Primjer: Zamislite da gradite obrazac u više koraka. Svaki korak može imati vlastiti hook za upravljanje specifičnim poljima unosa i logikom validacije tog koraka, ali svi dijele zajedničko stanje obrasca kojim upravlja roditeljski hook koristeći useReducer i 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 };
Objašnjenje:
FormContextje stvoren pomoćucreateContextkako bi sadržavao stanje obrasca i dispatch funkciju.formReducerupravlja ažuriranjima stanja obrasca pomoćuuseReducer. Akcije poputNEXT_STEP,PREVIOUS_STEPiUPDATE_FIELDsu definirane za izmjenu stanja.- Komponenta
FormProviderpruža kontekst obrasca svojim podređenim elementima (children), čineći stanje i dispatch dostupnima svim koracima obrasca. Također izlaže pomoćne funkcije za `nextStep`, `previousStep` i `updateField` kako bi se pojednostavilo slanje akcija. - Hook
useFormContextomogućuje komponentama pristup vrijednostima iz konteksta obrasca. - Svaki korak (
useStep1,useStep2,useStep3) stvara vlastiti hook za upravljanje unosom vezanim za taj korak i koristiuseFormContextkako bi dobio stanje i dispatch funkciju za njegovo ažuriranje. Svaki korak izlaže samo podatke i funkcije relevantne za taj korak, pridržavajući se principa jedinstvene odgovornosti.
3. Kompozicija hookova s upravljanjem životnim ciklusom
Ovaj obrazac uključuje hookove koji upravljaju različitim fazama životnog ciklusa komponente, kao što su montiranje (mounting), ažuriranje (updating) i demontiranje (unmounting). To se često postiže korištenjem useEffect unutar komponiranih hookova.
Primjer: Zamislite komponentu koja treba pratiti online/offline status i također treba izvršiti neku radnju čišćenja kada se demontira. Možete stvoriti odvojene hookove za svaki od ovih zadataka i zatim ih komponirati.
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 };
Objašnjenje:
useOnlineStatusprati online status korisnika koristećionlineiofflinedogađaje.useEffecthook postavlja event listenere kada se komponenta montira i uklanja ih kada se demontira.useDocumentTitleažurira naslov dokumenta. Također vraća naslov na zadanu vrijednost kada se komponenta demontira, osiguravajući da ne ostanu problemi s naslovom.useAppLifecyclekomponira oba hooka. KoristiuseOnlineStatuskako bi utvrdio je li korisnik online iuseDocumentTitleza postavljanje naslova dokumenta. Kombinirani hook vraća online status.
Praktični primjeri i slučajevi upotrebe
1. Internacionalizacija (i18n)
Upravljanje prijevodima i promjenom lokalizacije može postati složeno. Možete koristiti kompoziciju hookova za odvajanje odgovornosti:
useLocale(): Upravlja trenutnom lokalizacijom.useTranslations(): Dohvaća i pruža prijevode za trenutnu lokalizaciju.useTranslate(key): Hook koji prima ključ prijevoda i vraća prevedeni tekst, koristećiuseTranslationshook za pristup prijevodima.
To vam omogućuje jednostavno prebacivanje lokalizacija i pristup prijevodima kroz cijelu aplikaciju. Razmislite o korištenju biblioteka poput i18next zajedno s custom hookovima za upravljanje logikom prevođenja. Na primjer, useTranslations bi mogao učitavati prijevode na temelju odabrane lokalizacije iz JSON datoteka na različitim jezicima.
2. Validacija obrazaca
Složeni obrasci često zahtijevaju opsežnu validaciju. Možete koristiti kompoziciju hookova za stvaranje ponovno iskoristive logike validacije:
useInput(initialValue): Upravlja stanjem jednog polja za unos.useValidator(value, rules): Validira jedno polje za unos na temelju skupa pravila (npr. obavezno, email, minLength).useForm(fields): Upravlja stanjem i validacijom cijelog obrasca, komponirajućiuseInputiuseValidatorza svako polje.
Ovaj pristup promiče ponovnu iskoristivost koda i olakšava dodavanje ili izmjenu pravila validacije. Biblioteke poput Formika ili React Hook Form pružaju gotova rješenja, ali se mogu nadopuniti custom hookovima za specifične potrebe validacije.
3. Dohvaćanje i keširanje podataka
Upravljanje dohvaćanjem podataka, keširanjem i obradom pogrešaka može se pojednostaviti kompozicijom hookova:
useFetch(url): Dohvaća podatke s danog URL-a.useCache(key, fetchFunction): Kešira rezultat funkcije za dohvaćanje koristeći ključ.useData(url, options): KombinirauseFetchiuseCacheza dohvaćanje podataka i keširanje rezultata.
To vam omogućuje jednostavno keširanje često korištenih podataka i poboljšanje performansi. Biblioteke poput SWR (Stale-While-Revalidate) i React Query pružaju moćna rješenja za dohvaćanje i keširanje podataka koja se mogu proširiti custom hookovima.
4. Autentifikacija
Upravljanje logikom autentifikacije može biti složeno, posebno kada se radi s različitim metodama autentifikacije (npr. JWT, OAuth). Kompozicija hookova može pomoći u odvajanju različitih aspekata procesa autentifikacije:
useAuthToken(): Upravlja autentifikacijskim tokenom (npr. pohranjivanje i dohvaćanje iz lokalne pohrane).useUser(): Dohvaća i pruža informacije o trenutnom korisniku na temelju autentifikacijskog tokena.useAuth(): Pruža funkcije vezane uz autentifikaciju poput prijave, odjave i registracije, komponirajući ostale hookove.
Ovaj pristup omogućuje vam jednostavno prebacivanje između različitih metoda autentifikacije ili dodavanje novih značajki u proces autentifikacije. Biblioteke poput Auth0 i Firebase Authentication mogu se koristiti kao pozadina za upravljanje korisničkim računima i autentifikacijom, a custom hookovi se mogu stvoriti za interakciju s tim uslugama.
Najbolje prakse za kompoziciju custom hookova
- Održavajte hookove fokusiranima: Svaki hook trebao bi imati jasnu i specifičnu svrhu.
- Izbjegavajte duboko ugnježđivanje: Ograničite broj razina kompozicije kako biste izbjegli da kod postane teško razumljiv. Ako hook postane previše složen, razmislite o njegovom daljnjem razbijanju.
- Dokumentirajte svoje hookove: Pružite jasnu i sažetu dokumentaciju za svaki hook, objašnjavajući njegovu svrhu, ulazne i izlazne podatke. To je posebno važno za hookove koje koriste drugi programeri.
- Testirajte svoje hookove: Napišite jedinične testove za svaki hook kako biste osigurali da ispravno radi. To je posebno važno za hookove koji upravljaju stanjem ili izvode nuspojave.
- Razmislite o korištenju biblioteke za upravljanje stanjem: Za složene scenarije upravljanja stanjem, razmislite o korištenju biblioteke poput Reduxa, Zustanda ili Jotaija. Te biblioteke pružaju naprednije značajke za upravljanje stanjem i mogu pojednostaviti kompoziciju hookova.
- Razmislite o obradi pogrešaka: Implementirajte robusnu obradu pogrešaka u svojim hookovima kako biste spriječili neočekivano ponašanje. Razmislite o korištenju try-catch blokova za hvatanje pogrešaka i pružanje informativnih poruka o pogreškama.
- Uzmite u obzir performanse: Budite svjesni implikacija performansi vaših hookova. Izbjegavajte nepotrebna ponovna iscrtavanja (re-renders) i optimizirajte svoj kod za performanse. Koristite React.memo, useMemo i useCallback za optimizaciju performansi gdje je to prikladno.
Zaključak
Kompozicija React custom hookova moćna je tehnika za apstrakciju složene logike i poboljšanje ponovne iskoristivosti, održivosti i mogućnosti testiranja koda. Razbijanjem složenih zadataka na manje, lakše upravljive hookove, možete stvoriti modularniju i organiziraniju bazu koda. Slijedeći najbolje prakse navedene u ovom članku, možete učinkovito iskoristiti kompoziciju custom hookova za izgradnju robusnih i skalabilnih React aplikacija. Zapamtite da uvijek dajete prednost jasnoći i jednostavnosti u svom kodu i ne bojte se eksperimentirati s različitim obrascima kompozicije kako biste pronašli ono što najbolje odgovara vašim specifičnim potrebama.