Эта статья — перевод оригинальной статьи «React Custom Hooks vs. Helper Functions — When To Use Both».
Также я веду телеграм канал «Frontend по‑флотски», где рассказываю про интересные вещи из мира разработки интерфейсов.
Вступление
При работе довольно часто приходится сталкиваться с различными технологиями и сценариями использования на ежедневной основе. Две популярные концепции - это React Custom Hooks и Helper functions. Концепция Helper functions существует уже очень давно, в то время как React Custom Hooks все еще достаточно современна. Обе концепции позволяют разработчикам абстрагироваться и повторно использовать код, который они пишут, разными способами, хотя они оба имеют немного разные сценарии использования.
Сегодня мы рассмотрим сходства между этими двумя концепциями и сделаем вывод о том, когда правильнее всего использовать каждую из них.
Для начала давайте рассмотрим, что такое React Custom Hooks.
Что такое React Custom Hooks?
React Custom Hooks — это JavaScript‑функции, которые дают вам возможность повторно использовать логику с состоянием во всей кодовой базе React. При использовании Custom Hooks мы используем API React Hooks, который позволяет нам управлять состоянием и его побочными эффектами в соответствии с процессом функциональных компонентов React.
Одной из уникальных характеристик Custom Hooks является возможность использовать управление состоянием, что означает, что мы можем получить доступ к встроенным в React хукам, таким как useState и useEffect. Другим уникальным идентификатором является тот факт, что мы должны следовать именованным соглашениям для React Custom Hooks, поскольку мы должны префиксировать начало хука словом use, за которым следует е��о имя, например, useFetch.
Когда мы используем пользовательские хуки, они могут получать доступ и изменять состояние компонента, а также методы жизненного цикла, поскольку они глубоко связаны с логикой работы компонентов React.
Пример того, как выглядит React Custom Hook, мы можем увидеть в этом примере кода:
import { useState, useEffect } from 'react'; export function useFetch(url) { const [data, setData] = useState([]); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(null); useEffect(() => { const fetchData = async () => { try { const json = await fetch(url).then((r) => r.json()); setIsLoading(false); setData(json); } catch (error) { setError(error); setIsLoading(false); } }; fetchData(); return { data, error, isLoading }; }
Этот пользовательский хук называется useFetch и содержит логику многократного использования для получения данных из API. Он может управлять состояниями загрузки и ошибок и может быть импортирован в несколько компонентов.
Теперь, когда у нас есть фундаментальное понимание React Custom Hooks, давайте посмотрим, чем они отличаются от Helper Functions.
Что такое Helper Functions?
Helper Functions — это, по сути, самостоятельные функции, которые используются для выполнения различных вычислений или задач. Такие функции можно использовать в любом месте приложения, поскольку они не являются частью компонента React или системы управления состоянием. Еще одно ключевое отличие заключается в том, что они могут использоваться во многих языках программирования и не привязаны к какой‑либо экосистеме. Они представляют собой концепцию, которую можно использовать где угодно.
В отличие от пользовательских функций React, функции‑помощники выполняют вычисления и задачи, относящиеся к заданным входным данным. Они не могут взаимодействовать с побочными эффектами или состояниями компонентов. Они также не нуждаются в предопределенных соглашениях об именовании, таких как использование, и должны быть названы в соответствии с задачей, для которой вы их назначили.
Посмотрите на эту вспомогательную функцию в примере здесь:
import dayjs from 'dayjs'; function formatDate(date) { return dayjs(date).format('MM/DD/YYYY'); } export default formatDate;
В этом фрагменте кода мы используем Javascript-библиотеку дат Day.js для разбора и форматирования даты, что дает нам более мощный метод форматирования наших дат.
Теперь, с нашим обновленным пониманием React Custom Hooks и Helper Functions, самое время посмотреть, как мы можем использовать их в простом приложении. В следующем разделе мы создадим приложение, которое будет использовать оба варианта.
Создание приложения, использующего пользовательский крючок и вспомогательную функцию
Приложение, которое мы будем создавать, — это простое приложение Pokémon Pokédex, которое вы можете видеть на этом рисунке.

Мы получаем данные и информацию о покемонах из Pokémon API, а затем используем эти данные для создания нашего приложения, которое затем оформляется с помощью Tailwind CSS. После того как мы закончили объяснение, пора приступать к созданию нашего приложения.
Вы можете найти код здесь, на GitHub.
Первое, что нам нужно сделать, — это создать структуру проекта, поэтому найдите на компьютере место для проекта, например рабочий стол, а затем выполните эти команды, чтобы создать проект Next.js.
На экране настройки Next.js убедитесь, что вы выбрали "да" для Tailwind CSS и App Router, чтобы наши проекты имели одинаковые настройки. В этом проекте мы будем использовать JavaScript, остальные настройки по умолчанию должны быть в порядке.
npx create-next-app pokemon-pokedex-app cd pokemon-pokedex-app
Теперь у нас должен быть проект Next.js, и мы должны находиться внутри папки pokemon-pokedex-app, поэтому следующим шагом будет установка пакетов JavaScript, которые нам нужны для этого приложения. Нам нужно установить dayjs для вычисления времени и дат и uuid для создания уникальных идентификаторов для наших покемонов.
Установите оба пакета с помощью этой команды:
npm i dayjs uuid
Теперь, когда наши пакеты установлены, нам нужно создать все файлы и папки для нашего проекта.
Выполните приведенную ниже команду, чтобы создать архитектуру нашего проекта:
cd src/app mkdir components hooks utils mkdir components/Header components/Pokemon components/PokemonDetails touch components/Header/Header.js components/Pokemon/Pokemon.js components/PokemonDetails/PokemonDetails.js touch hooks/usePokemon.js touch utils/dateUtils.js utils/fetchUtils.js cd ../..
С помощью этой команды мы:
Создаём папку
componentsдля компонентовHeader,PokemonиPokemonDetails.Создаём папку
hooksдля нашего хукаusePokemon, который получает данные из Pokemon APIСоздаём папку
utilsдля наших функцийfetchиdate.
Итак, в следующих шагах мы добавим код в наши файлы, после чего наш проект будет завершен, поэтому откройте проект в редакторе кода.
Первым будет наш файл next.config.mjs в нашей корневой папке.
Замените весь код в этом файле на новый код здесь:
/** @type {import('next').NextConfig} */ const nextConfig = { images: { remotePatterns: [ { protocol: 'https', hostname: 'raw.githubusercontent.com', }, ], }, }; export default nextConfig;
Все, что мы делаем в этом файле, — это добавляем шаблон изображения для GitHub, чтобы мы могли обращаться к изображениям покемонов в нашем приложении, не получая ошибок. Мы должны определить этот маршрут, чтобы он был одобрен.
Теперь давайте займемся нашим файлом layout.js, заменив весь код на приведенный ниже:
import { Yanone_Kaffeesatz } from 'next/font/google'; import './globals.css'; const yanone = Yanone_Kaffeesatz({ subsets: ['latin'] }); export const metadata = { title: 'Create Next App', description: 'Generated by create next app', }; export default function RootLayout({ children }) { return ( <html lang="en"> <body className={yanone.className}>{children}</body> </html> ); }
Основное изменение в этом файле — использование шрифта Yanone_Kaffeesatz Google для нашего приложения, который заменяет стандартный шрифт Inter.
Файл globals.css следующий в нашем списке, нам просто нужно сделать некоторую очистку кода.
Как и раньше, замените код на этот фрагмент:
@tailwind base; @tailwind components; @tailwind utilities; body { font-size: 20px; }
Мы очистили часть кода и сделали размер шрифта по умолчанию 20px для нашего приложения.
На этом с файлами начальной конфигурации покончено, осталось добавить код для наших компонентов, хуков, утилиты и главной страницы, и наше приложение будет готово.
Начиная с самого верха, давайте сделаем наш компонент Header.js внутри Header/Header.js.
Добавьте этот код в наш файл:
import { useState, useEffect } from 'react'; import { getLiveDateTime } from '../../utils/dateUtils'; export default function Header() { const [dateTime, setDateTime] = useState(getLiveDateTime()); useEffect(() => { const interval = setInterval(() => { setDateTime(getLiveDateTime()); }, 1000); return () => clearInterval(interval); }, []); return ( <> <header className="flex row justify-between items-center bg-slate-900 text-white p-4 rounded-lg"> <div> <h1 className="text-5xl uppercase">Pokémon</h1> </div> <div> <p>Date: {dateTime.date}</p> <p>Time: {dateTime.time}</p> </div> </header> </> ); }
Этот компонент отображает заголовок нашего приложения — Pokémon, а также показывает дату и время. Это достигается импортом вспомогательной функции utils/dateUtils.js, которая использует JavaScript-библиотеку dayjs для вычисления времени и даты.
Следующим файлом для работы будет файл Pokemon.js в папке Pokemon.
Вот код для нашего файла:
import { useState, useEffect } from 'react'; import usePokemon from '../../hooks/usePokemon'; import { fetchPokemon } from '../../utils/fetchUtils'; import PokemonDetails from '../PokemonDetails/PokemonDetails'; export default function Pokemon() { const { data, isLoading, error } = usePokemon( 'https://pokeapi.co/api/v2/pokemon' ); const [pokemonDetails, setPokemonDetails] = useState([]); useEffect(() => { const fetchPokemonDetails = async () => { if (data && data.results) { const details = await Promise.all( data.results.map(async (pokemon) => { const pokemonData = await fetchPokemon(pokemon.url); return pokemonData; }) ); setPokemonDetails(details); } }; fetchPokemonDetails(); }, [data]); if (isLoading) { return <div>Loading...</div>; } if (error) { return <div>Error: {error.message}</div>; } return ( <> <div className="flex row flex-wrap gap-4 justify-evenly"> <PokemonDetails pokemonDetails={pokemonDetails} /> </div> </> ); }
Это наш основной файл компонента Pokémon, который использует наш хук usePokemon.js для получения данных из Pokémon API. Он работает вместе с нашим утилитарным файлом fetchUtils.js для получения данных. У нас есть настройка обработки ошибок при получении данных, а наши данные о состоянии передаются в компонент PokemonDetails.js, который отображает наш пользовательский интерфейс.
Итак, мы должны добавить код для нашего файла PokemonDetails.js в папку PokemonDetails.
Поместите этот код в файл:
import Image from 'next/image'; import { v4 as uuidv4 } from 'uuid'; export default function PokemonDetails({ pokemonDetails }) { return ( <> {pokemonDetails.map((pokemon) => ( <div key={pokemon.id} className={ pokemon.types[0].type.name === 'fire' ? 'bg-orange-400' : pokemon.types[0].type.name === 'water' ? 'bg-blue-400' : pokemon.types[0].type.name === 'grass' ? 'bg-green-400' : pokemon.types[0].type.name === 'bug' ? 'bg-green-700' : pokemon.types[0].type.name === 'normal' ? 'bg-slate-400' : '' } > <div className="text-white p-4"> <div className="capitalize"> <h1 className="text-4xl">{pokemon.name}</h1> </div> <div className="flex row gap-2 mt-4 mb-4"> <div className="bg-indigo-500 shadow-lg shadow-indigo-500/50 p-2 rounded-lg text-sm"> Height: {pokemon.height} </div> <div className="bg-indigo-500 shadow-lg shadow-indigo-500/50 p-2 rounded-lg text-sm"> Weight: {pokemon.weight} </div> </div> <div className="bg-white text-black rounded-lg p-4"> {pokemon.stats.map((stat) => ( <div key={uuidv4()}> <div className="capitalize flex row items-center gap-2"> <table> <tr> <td width={110}>{stat.stat.name}</td> <td width={40}>{stat.base_stat}</td> <td width={40}> <div style={{ width: `${stat.base_stat}px`, height: '0.5rem', backgroundColor: `${ stat.base_stat <= 29 ? 'red' : stat.base_stat <= 60 ? 'yellow' : 'green' }`, }} ></div> </td> </tr> </table> </div> </div> ))} </div> <div> <Image priority alt={pokemon.name} height={300} width={300} src={pokemon.sprites.other.home.front_default} /> </div> </div> </div> ))} </> ); }
Практически весь код в этом файле используется для создания интерфейса нашего приложения Pokémon. Стилизация выполнена с помощью Tailwind CSS.
Осталось сделать еще несколько файлов до завершения проекта. Следующим файлом для работы будет файл usePokemon.js в нашей папке hooks.
Нашему файлу понадобится этот код, поэтому добавьте его сейчас:
import { useState, useEffect } from 'react'; import { fetchPokemon } from '../utils/fetchUtils'; const usePokemon = (initialUrl) => { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { const pokemonData = await fetchPokemon(initialUrl); setData(pokemonData); } catch (error) { setError(error); } finally { setIsLoading(false); } }; fetchData(); }, [initialUrl]); return { data, isLoading, error }; }; export default usePokemon;
Этот пользовательский хук используется для получения данных из API, и в нашем случае это будет API Pokémon.
Теперь мы завершим наш файл dateUtils.js в папке utils этим кодом:
import dayjs from 'dayjs'; export const getLiveDateTime = () => { const now = dayjs(); return { date: now.format('MMMM D, YYYY'), time: now.format('h:mm:ss A'), }; };
С помощью этого файла мы используем JavaScript-библиотеку dayjs для вычисления дат и времени в любом файле, в который она импортируется.
Итак, теперь для нашего второго файла утилит, fetchUtils.js, добавьте в него этот код:
export const fetchPokemon = async (url) => { try { const response = await fetch(url); const data = await response.json(); console.log(data); return data; } catch (error) { console.error('Error fetching Pokemon:', error); throw error; } };
Этот файл утилиты работает с нашим хуком usePokemon.js для получения данных из API.
Наконец, давайте завершим наш проект, заменив и добавив код в наш файл main page.js в корневой папке.
Вот код, который нам нужен для этого файла:
'use client'; import Header from './components/Header/Header'; import Pokemon from './components/Pokemon/Pokemon'; export default function PokemonList() { return ( <div className="p-5"> <Header /> <h1 className="text-4xl mt-4 mb-4">Pokédex</h1> <Pokemon /> </div> ); }
Наш файл page.js — это главная точка входа для всех наших компонентов, и с этим кодом наш проект завершен.
Запустите проект с помощью обычного сценария запуска Next.js, как показано здесь, и вы увидите в браузере приложение Pokémon Pokédex:
npm run dev
Заключение
Сегодня мы узнали, как важно знать различия между вспомогательными функциями и React Custom, если вы хотите разрабатывать организованный, чистый и управляемый код. При повторном использовании логики с состоянием в React рекомендуется использовать пользовательские хуки, поскольку вспомогательные функции лучше всего подходят для работы без состояния, общего назначения. Можно повысить модульность и удобство повторного использования вашей кодовой базы, если правильно решить, когда использовать оба варианта.
