Хуки позволяют определять и использовать состояние и другие возможности React без создания классов. По сути хуки представляют функции, которые позволяют подключиться к состоянию и другим возможностям, которые есть в React.
Мы можем создавать свои хуки, однако React по умолчанию уже предоставляет ряд встроенных хуков:
useState: предназначен для управления состоянием компонентов
useEffect: предназначен для перехвата различного рода изменений в компонентах, которые нельзя обработать внутри компонентов
useContext: позволяет подписываться на контекст React
useReducer: позволяет управлять локальным состоянием сложных компонентов
useCallback: позволяет управлять функциями обратного вызова
useMemo: предназначен для управления мемоизированными (грубо говоря кэшированными) значениями
useRef: возвращать некоторое изменяемое значение, например, ссылку на html-элементы DOM, которыми затем можно управлять в коде JavaScript
useImperativeHandle: настраивает объект, который передается родительскому компоненту при использовании ref
useLayoutEffect: аналогичен хуку useEffect()
, но вызывается синхронно после всех изменений в структуре DOM
useDebugValue: предназначен для отображения некоторого значения в целях отладки
useId: предназначен для генерации уникального идентификатора
useTransition: применяется для создания переходов при рендеринге
useDeferredValue: позволяет отложить рендеринг некритичных частей структуры DOM
useSyncExternalStore: предназначен для синхронизации данных с внешними хранилищами
useInsertionEffect: предназначен для библиотек, которые используют CSS в JS, для оптимизации при внедрении стилей при рендеринге
Рассмотрим простейший пример, как мы можем перейти от классов к хукам. Допустим, у нас есть следующий класс-компонент:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> class ClickButton extends React.Component { constructor(props) { super(props); this.state = {counter: 0}; this.press = this.press.bind(this); } incrementCounter(prevState, props) { return { counter: prevState.counter + props.increment }; } press(){ this.setState(this.incrementCounter); } render() { return <div> <button onClick={this.press}>Count</button> <div>Counter: {this.state.counter}<br /> Increment: {this.props.increment}</div> </div> } } ReactDOM.createRoot( document.getElementById("app") ) .render( <ClickButton increment={2} /> ) </script> </body> </html>
Здесь определен компонент ClickButton, который принимает через props
некоторое значение increment
.
В конструкторе определяется состояние в виде переменной counter
, которая равна 0. Кроме того, в классе определяется
метод press()
, в котором изменяется состояние компонента.
Для изменения состояния вызывается другой метод - incrementCounter
, который берет из props
значение increment
и использует его для увеличения значения переменной counter
.
В коде класса-компонента определяется кнопка, по нажатию на которую как раз и вызывается метод press()
:
<button onClick={this.press}>Count</button>
В итоге по нажатию на кнопку мы получим увеличение переменной counter
:
Теперь определим аналогичный компонент с использованием хуков:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> function ClickButtonHook(props){ const [count, setCount] = React.useState(0); const press = function(){ setCount(count + props.increment); }; return (<div> <button onClick={press}>Count</button> <div>Counter: {count}<br /> Increment: {props.increment}</div> </div>); } ReactDOM.createRoot( document.getElementById("app") ) .render( <ClickButtonHook increment={2} /> ) </script> </body> </html>
В данном случае определен функциональный компонент ClickButtonHook
, так как мы не можем использовать хуки в
классах-компонентах, а только в функциях.
Вначале определяем переменные состояния:
const [count, setCount] = React.useState(0);
В данном случае определяются две переменных: count
и setCount
. Переменная count
хранит состояние компонента, а
переменная setCount
позволяет изменять значение переменной count.
В функцию useState()
передается число 0 - это то значение, которое по умолчанию получает переменная
count
.
Для изменения состояния в компоненте определена функция press()
, которая выполняет выражение setCount(count + props.increment);
-
к переменной count
прибавляется значение increment
из props
. Это выражение (count + props.increment
)
и определяет новое значение переменной count
. Таким образом, состояние компонента ClickButtonHook будет изменено.
В коде также определена кнопка, по нажатию на которую вызывается метод press()
. В итоге мы получим ту же программу, но с использованием хуков.
И как видно, этот код несколько короче, чем код класса-компонента.
В примере выше библиотека React подключалась непосредственно на веб-страницу, где и определен весь код приложения.
Однако если компоненты расположены в отдельных файлах, то мы можем импортировать хук useState
(впрочем как и другие встроенные хуки) следующим образом:
import React, { useState } from "react"; function ClickButtonHook(props){ const [count, setCount] = React.useState(0); const press= () => setCount(count + props.increment); return <div> <button onClick={press}>Count</button> <div>Counter: {count}<br /> Increment: {props.increment}</div> </div>; }
Хуки имеют ряд ограничений при определении и использовании:
Хуки вызываются только на верхнем уровне (top-level) компонента. Они НЕ вызываются внутри циклов, условных конструкций, внутри стандартных функций javascript.
Хуки можно вызывать только из функциональных компонентов React, либо из других хуков. Но их нельзя вызывать из классов-компонентов.
Функциональные компоненты можно определять как обычные функции:
function ClickButtonHook(props){ const [count, setCount] = React.useState(0); const press= () => setCount(count + props.increment); return <div> <button onClick={press}>Count</button> <div>Counter: {count}<br /> Increment: {props.increment}</div> </div>; }
Либо в виде стрелочных функций:
const ClickButtonHook = (props)=>{ const [count, setCount] = React.useState(0); const press= () => setCount(count + props.increment); return <div> <button onClick={press}>Count</button> <div>Counter: {count}<br /> Increment: {props.increment}</div> </div>; }