Hooks нужны не ради нового синтаксиса, а ради более простого способа работать с состоянием, побочными эффектами и повторным использованием логики. До них схожий код часто размазывался по классам, HOC и render props. С hooks тот же сценарий обычно читается короче и ближе к самой задаче.

Если воспринимать hooks как набор “магических функций”, ими легко злоупотреблять. Если же понимать, какую проблему решает каждый хук, то и useState, и useEffect, и useReducer становятся обычными инструментами, а не источником случайных оптимизаций.

Обновлено 17 марта 2026: статья переписана под современный React, а устаревшие и сломанные примеры заменены на рабочие.

Как использовать useState Hook

useState добавляет локальное состояние в функциональный компонент. Раньше для этого часто приходилось писать класс:

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }
}

С hooks тот же сценарий выглядит проще:

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount((current) => current + 1)}>
        Increment
      </button>
      <button onClick={() => setCount((current) => current - 1)}>
        Decrement
      </button>
    </div>
  );
}

useState возвращает пару: текущее значение и функцию обновления. Если новое состояние зависит от предыдущего, лучше сразу использовать функциональную форму setState, как в примере выше.

Как использовать useEffect Hook

useEffect нужен для побочных эффектов: сетевых запросов, подписок, таймеров, синхронизации с браузерным API и других действий, которые не должны происходить прямо во время рендера.

import { useEffect, useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("Alex");

  useEffect(() => {
    document.title = `${name}: ${count}`;
  }, [name, count]);

  return (
    <div>
      <p>
        Hi {name}, you clicked {count} times
      </p>
      <button onClick={() => setCount((current) => current + 1)}>
        Increment
      </button>
      <button
        onClick={() =>
          setName((current) => (current === "Alex" ? "Alexey" : "Alex"))
        }
      >
        Change name
      </button>
    </div>
  );
}

Массив зависимостей определяет, когда эффект нужно повторить. Если в массиве [name, count], эффект повторится только при изменении name или count.

Для очистки эффекта верни функцию:

useEffect(() => {
  const id = window.setInterval(() => {
    console.log("tick");
  }, 1000);

  return () => {
    window.clearInterval(id);
  };
}, []);

Важно: useEffect не нужен для обычных вычислений. Если значение можно получить прямо во время рендера без побочного эффекта, хук здесь не нужен.

Правила Hooks

Hooks - обычные функции JavaScript, но React ожидает от них строгий порядок вызова:

  • Используй Hooks только на верхнем уровне. Не вызывай hooks внутри циклов, условий или вложенных функций.
  • Используй Hooks только из функций React. Не вызывай hooks из обычных утилит.

ESLint Плагин

Лучше не полагаться на память. Правила hooks должен проверять линтер.

npm install --save-dev eslint-plugin-react-hooks
// eslint.config.js
const reactHooks = require("eslint-plugin-react-hooks");

module.exports = [
  {
    plugins: {
      "react-hooks": reactHooks,
    },
    rules: {
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",
    },
  },
];

Подробнее об ESLint

Дополнительные Hooks

Следующие hooks полезны уже в более конкретных сценариях.

useReducer

useReducer хорошо подходит, когда состояние связано несколькими переходами и useState начинает расползаться по компоненту.

import { useReducer } from "react";

const initialState = { count: 0, name: "Alex" };

function reducer(state, action) {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + 1 };
    case "DECREMENT":
      return { ...state, count: state.count - 1 };
    case "CHANGE_NAME":
      return {
        ...state,
        name: state.name === "Alex" ? "Alexey" : "Alex",
      };
    default:
      return state;
  }
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>
        Hi {state.name} you clicked {state.count} times
      </p>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>
        Increment
      </button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>
        Decrement
      </button>
      <button onClick={() => dispatch({ type: "CHANGE_NAME" })}>
        Change name
      </button>
    </div>
  );
}

Здесь вся логика переходов живет в одном месте, и это чаще удобнее, чем несколько разрозненных setState.

Пользовательский хук или Custom hook

Custom hook нужен для повторного использования логики, а не JSX. Название должно начинаться с use.

import { useEffect, useState } from "react";

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const savedValue = window.localStorage.getItem(key);
    return savedValue !== null ? JSON.parse(savedValue) : initialValue;
  });

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

Это хороший пример: состояние и синхронизация с внешним API браузера вынесены в переиспользуемую функцию.

Существующие хуки

Экосистема hooks давно выросла. Из полезных источников:

useMemo

useMemo возвращает запомненное значение и нужен для дорогих вычислений, а не “для красоты”.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Не используй его для побочных эффектов и не пытайся через него хранить бизнес-логику. Если значение легко посчитать, useMemo чаще всего не нужен.

useCallback и React.memo

useCallback полезен, когда ссылка на функцию сама по себе важна, например для React.memo.

import { memo, useCallback, useReducer } from "react";

const Buttons = memo(function Buttons({ onIncrement, onDecrement }) {
  return (
    <div>
      <button onClick={onIncrement}>Increment</button>
      <button onClick={onDecrement}>Decrement</button>
    </div>
  );
});

function reducer(state, action) {
  switch (action.type) {
    case "INCREMENT":
      return { count: state.count + 1 };
    case "DECREMENT":
      return { count: state.count - 1 };
    default:
      return state;
  }
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  const onIncrement = useCallback(() => {
    dispatch({ type: "INCREMENT" });
  }, []);

  const onDecrement = useCallback(() => {
    dispatch({ type: "DECREMENT" });
  }, []);

  return (
    <>
      <p>{state.count}</p>
      <Buttons onIncrement={onIncrement} onDecrement={onDecrement} />
    </>
  );
}

React.memo и useCallback не нужны по умолчанию, но хорошо работают вместе там, где действительно есть проблема лишних ререндеров.