React Hook-ове — какво са, основни hook-ове и правила за използване

Автор: IT Sectr Публикувано: 2026-07-04 Време за четене: 10 мин

React Hook-ове — това са функции, които позволяват използването на състояние, ефекти, контекст и други възможности на React във функционални компоненти, без писане на класове. Представени в React 16.8, hook-овете радикално промениха подхода към разработката, заменяйки сложни модели като HOC и render props. Според React, 2024, съществуват 15 вградени hook-а, покриващи всички сценарии за управление на компоненти.

Основни

  • Hook-ове — функции за свързване на възможностите на React с функционални компоненти без extends Component.
  • useState — основен hook за управление на състояние, връща стойност и setter.
  • useEffect — за странични ефекти: API заявки, абонаменти, таймери, DOM манипулации.
  • useContext — за достъп до React контекст без Consumer обвивки и static contextType.
  • Правила на hook-овете — извиквайте само на най-горно ниво и само във функционални компоненти или персонализирани hook-ове.

Какво са React Hook-овете?

React Hook-ове — набор от вградени функции в React, които позволяват ‘свързване’ с вътрешните механизми на рамката без създаване на класове. Преди React 16.8 функционалните компоненти можеха само да приемат props и да връщат JSX — всяка сложна логика изискваше класов компонент.

Проблемът на класовите компоненти — разделяне на свързаната логика. Кодът, отнасящ се до една същност (например WebSocket абонамент), беше принудително разделен между componentDidMount, componentDidUpdate и componentWillUnmount. Hook-овете решават това: цялата логика се групира в един useEffect.

Вторият проблем — повторно използване на логика. За повторно използване на логика на състояние между класови компоненти трябваше да се прилагат HOC (компоненти от по-висок ред) или render props — и двата модела създаваха допълнително влагане в дървото на компонентите. Персонализираните hook-ове решават този проблем без влагане.

useState — управление на състояние

useState — най-основният hook. Той приема начална стойност и връща tuple: текущата стойност и функция за нейното актуализиране. За разлика от this.setState в класовете, useState не слива обекти — новото състояние напълно замества предишното за конкретно извикване на useState.

js
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>
  );
}

Функционална форма на setState

Setter-ът на useState може да приеме функция, която получава предишната стойност и връща нова. Функционалната форма е задължителна, когато новото състояние се изчислява от предишното и са възможни групирани извиквания на setState — всяко извикване ще получи актуалната предишна стойност.

useEffect — странични ефекти и жизнен цикъл

useEffect — hook за изпълнение на странични ефекти във функционални компоненти. Той приема два аргумента: функция-ефект и масив от зависимости. React изпълнява ефекта след всеки рендер, но при посочване на зависимости — само когато някоя от тях се промени.

Празен масив от зависимости [] означава, че ефектът се изпълнява веднъж след първия рендер (аналогично на componentDidMount). Връщането на функция от ефекта е cleanup, който се изпълнява при демонтиране (аналогично на componentWillUnmount) и преди повторното изпълнение на ефекта.

js
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 - затваряне на socket
    return () => socket.close();
  }, [roomId]);

  return <ul>
    {messages.map((msg, i) =>
      <li key={i}>{msg.text}</li>
    )}
  </ul>;
}

Зависимостите на useEffect — са единствената връзка между ефекта и външния свят. Ако в ефекта се използва променлива от props или състояние, тя трябва да бъде в масива от зависимости. Пренебрегването на това правило е източник на грешки с остарели данни (stale closure).

useContext — достъп до контекст

useContext — hook за четене на стойността на контекста. Той приема обекта на контекста, създаден чрез React.createContext, и връща текущата стойност. Ако контекстът се промени, компонентът автоматично се пререндерира с новата стойност.

Преди появата на useContext, достъпът до контекст във функционални компоненти изискваше обвивка в Context.Consumer, което създаваше влагане. useContext напълно елиминира Consumer — достатъчно е да извикате hook-а на всяко място във функционалния компонент.

js
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 — сложна логика на състояние

useReducer — алтернатива на useState за състояния със сложна логика на актуализация. Той приема reducer функция и начално състояние, връща текущото състояние и dispatch функция. Reducer получава текущото състояние и действие, връща ново състояние — чиста функция без странични ефекти.

useReducer е особено полезен, когато няколко действия (set, reset, increment, addItem) управляват едно състояние. Вместо няколко useState с различни setter-и — един reducer с ясни типове действия. Това прави логиката предвидима и лесна за тестване.

js
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, useMemo, useCallback — оптимизация

useRef създава променлив обект, който се запазва между рендерите. Промяната на current не предизвиква пререндер. Основно приложение — достъп до DOM елементи и съхраняване на мутабилни стойности (таймери, предишни стойности, инстанции на библиотеки).

useMemo мемоизира резултата от функция. Преизчислява стойността само когато се промени една от зависимостите. Използвайте за скъпи изчисления — филтриране на големи масиви, сортиране, форматиране на данни. useCallback — специален случай на useMemo за мемоизация на функции.

js
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²) и повече).

Правила на hook-овете и чести грешки

React въведе две строги правила за hook-ове. Първо: извиквайте hook-ове само на най-горно ниво — не вътре в условия, цикли или вложени функции. Второ: извиквайте hook-ове само от React функционални компоненти или персонализирани hook-ове. ESLint плъгинът eslint-plugin-react-hooks автоматично проверява и двете правила.

Най-честата грешка — нарушаване на реда на извикване на hook-овете. React разчита на реда на извикване на hook-овете между рендерите. Ако hook се намира вътре в условие и не е бил извикан в един от рендерите, всички последващи hook-ове се изместват, причинявайки грешки.

ГрешкаКакво се случваРешение
Hook в условиеРедът на hook-овете се нарушаваПреместете логиката на условието вътре в hook-а
Stale closureЕфектът използва стара стойностДобавете всички зависимости в масива
Безкраен цикълuseEffect без зависимости променя състояниеПосочете зависимости или използвайте useReducer
Промяна на ref в рендерСтраничен ефект в тялото на функцияПреместете мутациите в useEffect

Втората честа грешка — stale closure в useEffect. Ако в ефекта се използва променлива, която не е посочена в зависимостите, ефектът ‘улавя’ нейната стойност в момента на създаване на затварянето. Решение — винаги включвайте всички използвани променливи в масива от зависимости. Ако зависимостите са твърде много, разделете ефекта на няколко части.

Често задавани въпроси

Защо hook-овете не могат да се извикват в условия?

React разчита на реда на извикване на hook-овете между рендерите. Ако hook е извикан в условие и не е работил в един от рендерите, всички последващи hook-ове ‘се плъзгат’ — тяхното състояние се смесва. Правилото на ESLint hooks/exhaustive-deps предупреждава за това.

Каква е разликата между useEffect и useLayoutEffect?

useLayoutEffect е синхронен и се изпълнява преди браузърът да нарисува промените. useEffect е асинхронен — след рисуването. Използвайте useLayoutEffect само когато трябва да измерите или промените DOM, преди потребителят да види промените (позиция на popover, възстановяване на скрол).

Как да отмените fetch заявка при демонтиране?

Използвайте AbortController вътре в useEffect. Създайте controller, предайте signal на fetch и в cleanup функцията извикайте controller.abort(). За стари браузъри е достатъчен флаг cancelled: return () => { cancelled = true; } с проверка преди setState.

Какво е персонализиран hook и кога да го създадем?

Персонализиран hook — JavaScript функция с префикс use, която използва вградени hook-ове. Създавайте го, когато една и съща логика с hook-ове се повтаря в два или повече компонента. Примери: useDebounce, useLocalStorage, useMediaQuery. Персонализираните hook-ове заменят HOC и render props.

Защо useEffect се изпълнява два пъти в Strict Mode?

В Strict Mode React умишлено монтира и демонтира компонента два пъти в режим на разработка, за да открие грешки с неизчистени ефекти. Ако cleanup е написан правилно, двойното извикване не причинява проблеми. В производствен режим това не се случва.

Резюме

  • React Hook-ове — функции за използване на състояние, ефекти и контекст във функционални компоненти, които замениха класовете.
  • useState — основен hook за локално състояние с функционален setter за безопасни актуализации.
  • useEffect — универсален hook за странични ефекти с масив от зависимости и cleanup функция.
  • useContext — директен достъп до React контекст без Consumer обвивки и допълнително влагане.
  • useReducer — за сложна логика на състояние с типове действия, алтернатива на Redux в рамките на един компонент.
  • useRef, useMemo, useCallback — инструменти за оптимизация: DOM референции, мемоизация на стойности и callback-и.
  • Правило за най-горно ниво — строго ограничение за извикване на hook-ове извън условия, цикли и вложени функции.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също