Kodėl Hooks pakeitė React žaidimo taisykles
Prisimenu, kaip 2018-ų pabaigoje React komanda pristatė Hooks, ir tai buvo tarsi šviesos spindulys daugeliui programuotojų, kurie jau buvo pavargę nuo klasinių komponentų ceremonijų. Nebereikėjo rašyti `this.setState`, nebereikėjo galvoti apie `bind`, nebereikėjo sukti galvos dėl lifecycle metodų. Hooks atėjo kaip gaivus vėjas ir iš esmės pakeitė tai, kaip mes rašome React aplikacijas.
Prieš Hooks erą, jei norėjai turėti būseną (state) ar naudoti lifecycle metodus, privalėjai rašyti klasinius komponentus. O tai reiškė daug boilerplate kodo, sudėtingesnę logiką ir dažnai – sunkiau suprantamą kodą. Funkciniai komponentai buvo tik „kvailukams” – jie galėjo tik priimti props ir kažką atvaizduoti. Bet Hooks viską pakeitė.
Dabar galime turėti pilnai funkcionalius komponentus su būsena, šalutiniais efektais, kontekstu ir viskuo kitu, ko mums reikia. Ir visa tai – su gerokai švaresniu, lengviau skaitomu kodu. Nenuostabu, kad šiandien dauguma naujų React projektų rašomi beveik išimtinai su funkciniais komponentais ir Hooks.
useState – būsenos valdymas be klasių
Pradėkime nuo paties paprasčiausio ir dažniausiai naudojamo Hook – `useState`. Tai jūsų pagrindinis įrankis, kai reikia pridėti būseną į funkcinį komponentą. Sintaksė tokia paprasta, kad net pradedantieji greitai ją įsisavina:
„`javascript
const [count, setCount] = useState(0);
„`
Šita eilutė daro daug dalykų vienu metu. Ji sukuria būsenos kintamąjį `count` su pradine reikšme 0, ir duoda jums funkciją `setCount`, kuria galite tą būseną atnaujinti. Jokių `this.state`, jokių `this.setState` – tiesiog paprasta destruktūrizacija ir gatavas rezultatas.
Bet štai kur daugelis pradedančiųjų suklysta – jie bando naudoti `useState` cikluose ar sąlyginiuose sakiniuose. Niekada taip nedarykite! Hooks turi būti kviečiami visada toje pačioje eilės tvarkoje, kiekviename komponento renderinime. React pasikliauja šia tvarka, kad žinotų, kuris state priklauso kuriam Hook.
Štai praktinis pavyzdys, kaip galima naudoti kelis `useState` viename komponente:
„`javascript
function UserProfile() {
const [name, setName] = useState(”);
const [email, setEmail] = useState(”);
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
// API kvietimas…
setIsLoading(false);
};
return (
// JSX kodas…
);
}
„`
Vienas patarimas iš praktikos – jei jūsų būsena tampa per sudėtinga su daugybe susijusių reikšmių, galbūt verta apsvarstyti `useReducer` arba bent jau grupuoti susijusias reikšmes į objektus.
useEffect – šalutinių efektų meistras
Jei `useState` yra duona, tai `useEffect` yra sviestas React Hooks pasaulyje. Šis Hook leidžia jums atlikti šalutinius efektus funkcinėse komponentuose – tai, ką anksčiau darydavote su `componentDidMount`, `componentDidUpdate` ir `componentWillUnmount`.
Pati didžiausia klaida, kurią matau beveik kiekviename code review – neteisingas `useEffect` dependency array naudojimas. Žmonės arba visai jo neprideda (ir gauna begalines kilpas), arba prideda per daug priklausomybių (ir efektas vykdomas per dažnai), arba – dar blogiau – tyčia ignoruoja ESLint įspėjimus.
„`javascript
useEffect(() => {
// Šis kodas vykdomas po kiekvieno renderinimo
console.log(‘Component rendered’);
});
useEffect(() => {
// Šis kodas vykdomas tik vieną kartą, kai komponentas sumontuojamas
fetchData();
}, []);
useEffect(() => {
// Šis kodas vykdomas, kai pasikeičia userId
fetchUserData(userId);
}, [userId]);
„`
Labai svarbu suprasti, kad `useEffect` vykdomas po renderinimo. Tai reiškia, kad jūsų komponentas pirmiausia atvaizduojamas, o tik tada vykdomas efektas. Jei jums reikia kažko prieš renderinimą, žiūrėkite į `useLayoutEffect`, bet 99% atvejų jums jo nereikės.
Dar viena dažna klaida – užmiršti cleanup funkciją. Jei jūsų efektas sukuria subscription, event listener ar intervalą, privalote jį išvalyti:
„`javascript
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(interval); // Cleanup!
}, []);
„`
Custom Hooks – kodo perpanaudojimo šventasis Gralis
Čia prasideda tikroji magija. Custom Hooks leidžia jums ištraukti komponento logiką į perpanaudojamas funkcijas. Tai vienas iš galingiausių Hooks aspektų, ir vis dėlto daugelis programuotojų juo nepakankamai naudojasi.
Taisyklė paprasta – jei jūsų Hook pavadinimas prasideda `use` ir jis kviečia kitus Hooks, tai custom Hook. Pavyzdžiui, štai kaip galėtumėte sukurti Hook duomenų gavimui iš API:
„`javascript
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
async function fetchData() {
try {
setLoading(true);
const response = await fetch(url);
const json = await response.json();
if (!cancelled) {
setData(json);
setError(null);
}
} catch (err) {
if (!cancelled) {
setError(err);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchData();
return () => {
cancelled = true;
};
}, [url]);
return { data, loading, error };
}
„`
Dabar šį Hook galite naudoti bet kuriame komponente:
„`javascript
function UserList() {
const { data, loading, error } = useFetch(‘/api/users’);
if (loading) return
if (error) return
return
- {data.map(user =>
- {user.name}
)}
;
}
„`
Matote, kaip švariai tai atrodo? Visa fetch logika paslėpta Hook viduje, o komponentas lieka paprastas ir suprantamas. Tai yra tikroji custom Hooks galia.
useContext ir useReducer – sudėtingesnės būsenos valdymas
Kai jūsų aplikacija auga, paprastas `useState` gali tapti nepakankamas. Čia į žaidimą įsijungia `useContext` ir `useReducer` – du Hooks, kurie kartu gali pakeisti Redux daugelyje projektų (nors ne visuose, būkime sąžiningi).
`useContext` leidžia jums pasiekti konteksto reikšmes be begalinių props drilling. Užuot perdavę props per penkis komponentų lygius, tiesiog sukuriate kontekstą ir pasiekiate jį ten, kur reikia:
„`javascript
const ThemeContext = React.createContext();
function App() {
const [theme, setTheme] = useState(‘light’);
return (
);
}
function DeepNestedComponent() {
const { theme, setTheme } = useContext(ThemeContext);
return (
);
}
„`
`useReducer` yra kaip `useState` su steroidais. Jis puikiai tinka, kai jūsų būsenos logika tampa sudėtinga, su daugybe skirtingų veiksmų:
„`javascript
function reducer(state, action) {
switch (action.type) {
case ‘increment’:
return { count: state.count + 1 };
case ‘decrement’:
return { count: state.count – 1 };
case ‘reset’:
return { count: 0 };
default:
throw new Error(‘Unknown action’);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
);
}
„`
Kombinuojant `useContext` ir `useReducer`, galite sukurti gana galingą būsenos valdymo sistemą be jokių papildomų bibliotekų. Tai ypač naudinga vidutinio dydžio projektams.
Performance Hooks – useMemo ir useCallback
Dabar palieskime temą, kuri sukelia daug ginčų React bendruomenėje – optimizacijos Hooks. `useMemo` ir `useCallback` yra skirti našumui gerinti, bet ironija ta, kad dažnai jie naudojami neteisingai ir faktiškai pablogina našumą.
Pirmiausia suprantame, kas yra kas:
– `useMemo` – memoizuoja reikšmę (skaičiavimo rezultatą)
– `useCallback` – memoizuoja funkciją
Štai klasikinė klaida:
„`javascript
// BLOGAI – nereikalinga optimizacija
function Component() {
const value = useMemo(() => 2 + 2, []);
// …
}
„`
Tokia paprasta operacija kaip 2+2 vykdoma greičiau nei `useMemo` overhead. Naudokite šiuos Hooks tik tada, kai:
1. Atliekate brangius skaičiavimus
2. Perduodate funkcijas į optimizuotus child komponentus
3. Priklausomybės masyvuose naudojate objektus ar funkcijas
Geras pavyzdys:
„`javascript
function ExpensiveComponent({ items }) {
const sortedItems = useMemo(() => {
console.log(‘Sorting items…’);
return items.sort((a, b) => a.value – b.value);
}, [items]);
const handleClick = useCallback((id) => {
// Kažkas su id…
}, []);
return (
))}
);
}
„`
Aukso taisyklė – pirmiausia parašykite veikiantį kodą, tada profiliu matuokite našumą, ir tik tada optimizuokite. Priešingu atveju tiesiog pridėsite nereikalingo kodo ir sudėtingumo.
useRef – daugiau nei tik DOM prieiga
Daugelis žmonių mano, kad `useRef` skirtas tik DOM elementams pasiekti, bet tai tik viena jo funkcija. `useRef` iš tikrųjų yra būdas išlaikyti bet kokią reikšmę, kuri neturi sukelti re-renderingo, kai pasikeičia.
Taip, DOM prieiga yra dažniausias use case:
„`javascript
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
);
}
„`
Bet štai kur `useRef` tampa tikrai įdomus – galite jį naudoti bet kokiai reikšmei, kurią norite išlaikyti tarp renderinimų, bet nenorite, kad jos pasikeitimas sukeltų re-renderingą:
„`javascript
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
const startTimer = () => {
if (intervalRef.current !== null) return;
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
};
const stopTimer = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
useEffect(() => {
return () => stopTimer(); // Cleanup
}, []);
return (
Count: {count}
);
}
„`
Čia `intervalRef` laiko interval ID, bet jo pasikeitimas nesukelia komponento re-renderingo. Tai puikus būdas laikyti „instance variables” funkcinėse komponentuose.
Kas toliau ir kaip visa tai sujungti
React Hooks jau tapo standartu, ir nauji projektai beveik išimtinai rašomi su funkciniais komponentais. Bet tai nereiškia, kad reikia panikoj perrašyti visus senus klasinius komponentus – jie vis dar puikiai veikia ir bus palaikomi.
Jei tik pradedate naudoti Hooks, štai keletas praktinių patarimų iš realios patirties:
Pradėkite paprastai. Neskubėkite iškart kurti sudėtingų custom Hooks ar optimizuoti viską su `useMemo`. Pirmiausia išmokite gerai naudoti `useState` ir `useEffect` – tai 80% to, ko jums reikės kasdien.
Skaitykite klaidas. React duoda puikius error messages, kai pažeidžiate Hooks taisykles. Neignoruokite jų – jie padės išvengti subtilių bugų.
Naudokite ESLint plugin. `eslint-plugin-react-hooks` sugaus daugumą klaidų compile time. Įdiekite jį ir klausykite, ką jis sako apie dependency arrays.
Kurkite custom Hooks anksčiau nei manote reikiant. Jei pastebite, kad tas pats Hooks pattern kartojasi keliuose komponentuose – laikas iškelti jį į custom Hook. Tai ne tik sumažins kodo dubliavimąsi, bet ir padarys logiką lengviau testuojamą.
Hooks pakeitė React ekosistemą fundamentaliai. Jie padarė kodą švaresniu, lengviau suprantamu ir perpanaudojamu. Taip, yra mokymosi kreivė, ypač su `useEffect` dependency arrays, bet kai įsisavinsite pagrindines koncepcijas, grįžti atgal prie klasinių komponentų tikrai nenorėsite.
Šiandien, kai kuriu naują React komponentą, net negalvoju apie klases. Funkcinis komponentas su keliais Hooks – ir turiu viską, ko reikia. Tai greičiau, švariau ir tiesiog maloniau. O argi ne dėl to mes visi programuojame?
