React Хукови — то су функције које омогућавају коришћење стања, ефеката, контекста и других React могућности у функционалним компонентама, без писања класа. Представљени у React 16.8, хукови су радикално променили приступ развоју, заменивши сложене обрасце попут HOC и render props. Према React, 2024, постоји 15 уграђених хукова који покривају све сценарије управљања компонентом.
Главно
React Хукови — скуп функција уграђених у React које омогућавају „повезивање” са унутрашњим механизмима оквира без креирања класа. Пре React 16.8, функционалне компоненте су могле само да примају props и враћају JSX — свака сложена логика је захтевала класну компоненту.
Проблем класних компоненти — раздвајање повезане логике. Код који се односи на једну целину (на пример, WebSocket претплата) био је принудно подељен између componentDidMount, componentDidUpdate и componentWillUnmount. Хукови то решавају: цела логика се групише у једном useEffect.
Други проблем — поновно коришћење логике. За поновно коришћење логике стања између класних компоненти морали су се примењивати HOC (компоненте вишег реда) или render props — оба обрасца су стварала додатно угњежђење у стаблу компоненти. Прилагођени хукови решавају овај проблем без угњежђења.
useState — најосновнији хук. Прима почетну вредност и враћа торку: тренутну вредност и функцију за њено ажурирање. За разлику од this.setState у класама, useState не спаја објекте — ново стање у потпуности замењује претходно за одређени позив useState.
import React, { useState } from 'react';
function Form() {
const [text, setText] = useState('');
const [submitting, setSubmitting] = useState(false);
async function handleSubmit() {
setSubmitting(true);
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify({ text })
});
setSubmitting(false);
}
return (
<div>
<input
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={handleSubmit}
disabled={submitting}>
{submitting ? 'Saving...' : 'Submit'}
</button>
</div>
);
}
Сеттер useState може примити функцију која добија претходну вредност и враћа нову. Функционална форма је обавезна када се ново стање израчунава из претходног и могући су бачевани позиви setState — сваки позив ће добити ажурну претходну вредност.
useEffect — хук за извођење споредних ефеката у функционалним компонентама. Прима два аргумента: функцију-ефекат и низ зависности. React извршава ефекат после сваког рендеровања, али при навођењу зависности — само када се било која од њих промени.
Празан низ зависности [] значи да се ефекат извршава једном после првог рендеровања (аналогно componentDidMount). Враћање функције из ефекта је cleanup, који се извршава при демонтирању (аналогно componentWillUnmount) и пре поновног извршавања ефекта.
import React, { useState, useEffect } from 'react';
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new WebSocket(`wss://chat/${roomId}`);
socket.onmessage = (event) => {
setMessages(prev => [...prev, JSON.parse(event.data)]);
};
// Cleanup - затвори сокет
return () => socket.close();
}, [roomId]);
return <ul>
{messages.map((msg, i) =>
<li key={i}>{msg.text}</li>
)}
</ul>;
}
Зависности useEffect — једина су веза између ефекта и спољног света. Ако се у ефекту користи променљива из props или стања, она мора бити у низу зависности. Игнорисање овог правила је извор грешака са застарелим подацима (stale closure).
useContext — хук за читање вредности контекста. Прима објекат контекста креиран путем React.createContext и враћа тренутну вредност. Ако се контекст промени, компонента се аутоматски поново рендерује са новом вредношћу.
Пре појаве useContext, приступ контексту у функционалним компонентама је захтевао омотач у Context.Consumer, што је стварало угњежђење. useContext потпуно елиминише Consumer — довољно је позвати хук на било ком месту функционалне компоненте.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('светло');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button
className={`btn btn-${theme}`}
>
{theme === 'dark' ? 'moon' : '☀️'}
</button>;
}
useContext није замена за Redux или глобално стање. React препоручује коришћење контекста за ретко промењиве вредности — тема, језик, ауторизација. За често ажуриране податке, свако поновно рендеровање ће поново израчунати све потрошаче контекста, што може смањити перформансе.
useReducer — алтернатива useState за стања са сложеном логиком ажурирања. Прима reducer функцију и почетно стање, враћа тренутно стање и dispatch функцију. Reducer прима тренутно стање и акцију, враћа ново стање — чиста функција без споредних ефеката.
useReducer је посебно користан када више акција (set, reset, increment, addItem) управља једним стањем. Уместо више useState са различитим сеттерима — један reducer са јасним типовима акција. То чини логику предвидљивом и лако тестираном.
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'setStep':
return { ...state, step: action.step };
case 'reset':
return { count: 0, step: 1 };
default:
throw new Error('Unknown action: ' + action.type);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, {
count: 0,
step: 1
});
return (
<>
<p>Count: {state.count}</p>
<button
onClick={() => dispatch({ type: 'increment' })}>+</button>
<button
onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</>
);
}
useRef ствара променљиви објекат који се задржава између рендеровања. Промена current не изазива поновно рендеровање. Главна примена — приступ DOM елементима и чување мутабилних вредности (тајмери, претходне вредности, инстанце библиотека).
useMemo мемоизира резултат функције. Прерачунава вредност само када се промени једна од зависности. Користите за скупа израчунавања — филтрирање великих низова, сортирање, форматирање података. useCallback — посебан случај useMemo за мемоизацију функција.
import React, { useRef, useMemo, useCallback } from 'react';
function SearchPage({ items, query, onItemSelect }) {
const inputRef = useRef(null);
// Аутофокус при монтирању
useEffect(() => {
inputRef.current?.focus();
}, []);
// Мемоизирај филтрирани резултат
const filtered = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase()
.includes(query.toLowerCase())
);
}, [items, query]);
// Мемоизирај callback
const handleSelect = useCallback((id) => {
onItemSelect(id);
}, [onItemSelect]);
return (
<div>
<input ref={inputRef}
value={query}
onChange={e => setQuery(e.target.value)}
/>
<ul>
{filtered.map(item =>
<li key={item.id}>
<button
onClick={() => handleSelect(item.id)}>
{item.name}
</button>
</li>
)}
</ul>
</div>
);
}
Важно правило: useRef, useMemo и useCallback — алати за оптимизацију, а не обавезне конструкције. Не обмотавајте све у useMemo — прво измерите перформансе. Преурана оптимизација компликује код. React рендерује компоненте брзо; useMemo је потребан само када су израчунавања заиста скупа (O(n²) и више).
React је увео два строга правила за хукове. Прво: позивајте хукове само на највишем нивоу — не унутар услова, петљи или угњеждених функција. Друго: позивајте хукове само из React функционалних компоненти или прилагођених хукова. ESLint додатак eslint-plugin-react-hooks аутоматски проверава оба правила.
Најчешћа грешка — кршење редоследа позивања хукова. React се ослања на редослед позивања хукова између рендеровања. Ако се хук налази унутар услова и није позван у једном од рендеровања, сви наредни хукови се померају, изазивајући грешке.
| Грешка | Шта се дешава | Решење |
|---|---|---|
| Хук у услову | Редослед хукова се ломи | Преместите логику услова унутар хука |
| Stale closure | Ефекат користи стару вредност | Додајте све зависности у низ |
| Бесконачна петља | useEffect без зависности мења стање | Наведите зависности или користите useReducer |
| Измена ref у рендеру | Споредни ефекат у телу функције | Преместите мутације у useEffect |
Друга честа грешка — stale closure у useEffect. Ако се у ефекту користи променљива која није наведена у зависностима, ефекат „хвата” њену вредност у тренутку стварања затворења. Решење — увек укључите све коришћене променљиве у низ зависности. Ако има превише зависности, поделите ефекат на неколико.
Често постављана питања
React се ослања на редослед позивања хукова између рендеровања. Ако је хук позван у услову и није радио у једном од рендеровања, сви наредни хукови „клизе” — њихово стање се меша. ESLint правило hooks/exhaustive-deps упозорава на ово.
useLayoutEffect је синхрони и извршава се пре него што прегледач нацрта измене. useEffect је асинхрони — после цртања. Користите useLayoutEffect само када треба измерити или изменити DOM пре него што корисник види измене (позиција popover-а, враћање скрола).
Користите AbortController унутар useEffect. Направите controller, проследите signal у fetch, и у cleanup функцији позовите controller.abort(). За старе прегледаче довољна је заставица cancelled: return () => { cancelled = true; } са провером пре setState.
Прилагођени хук — JavaScript функција са префиксом use која користи уграђене хукове. Креирајте га када се иста логика са хуковима понавља у две или више компоненти. Примери: useDebounce, useLocalStorage, useMediaQuery. Прилагођени хукови замењују HOC и render props.
У Strict Mode-у React намерно монтира и демонтира компоненту двапут у развојном режиму да би пронашао грешке са неочишћеним ефектима. Ако је cleanup исправно написан, двоструки позив не изазива проблеме. У продукционом режиму ово се не дешава.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође