React Hooks — огляд можливостей нового API

Мене звати Володимир Симоненко. Я фронтенд-розробник у компанії PyTeam. Займаюся веб-розробкою 3 роки. Загальний досвід у розробці програмного забезпечення на різних позиціях — 8 років. Нещодавно я робив доповідь на тему React Hooks і вирішив поділитися інформацією на DOU. Це стаття-огляд нових можливостей відомої та популярної бібліотеки для веб-інтерфейсів React.js і буде більш цікава тим, хто вже знайомий с реактом.

Hooks — нове API, що дозволяє писати функціональні компоненти зі станом та використовувати інші можливості реакту без написання класів. Доступно в прев’ю версії (поточна версія 16.8.0-alpha.1). Встановлюємо: npm i –S react@next.

Навіщо це потрібно

По-перше, це потрібно, щоб повторно використовувати логіку, що знаходиться в state, між компонентами. Для вирішення цієї задачі зазвичай використовують такі підходи: Higher-Order Components (HOC) і Render Props.

По-друге, хуки дозволяють розділити один компонент на більш дрібні функції в залежності від того, які частини пов’язані (наприклад, налаштування підписки або вибірка даних), замість примусового поділу на основі методів життєвого циклу.

По-третє, хуки дозволяють використовувати більше можливостей React без класів. Класи складні для людей і для машин. У спостереженні facebook класи є великою перешкодою при вивченні React. Необхідно зрозуміти, як працює this, а він не працює так, як в інших мовах програмування. Так само слід пам’ятати про прив’язку обробників подій. Без стабільних пропозицій синтаксису код виглядає дуже багатослівно. Також класи не дуже добре мінімізуються, і вони роблять гаряче перезавантаження (hot reload) ненадійним.

Hooks API

Базові:

Додаткові:

Напишемо кілька рядків

На основі хуків реакту напишемо власний (custom hook), який буде реагувати на зміну ширини області перегляду вікна браузера і буде повертати значення екрану. Такий хук буде корисним для адаптивного дизайну (responsive design).

Важливо! Угода про іменування призначених для користувача хуків: Сustom Hook — це функція JavaScript, ім’я якої починається з «use» і яка може викликати інші хуки.

Отже, створюємо функціональний компонент з локальним станом:

import { useState } from 'react'
 
export default function useBreakpoints() {
  const [points, setPoints] = useState(window.innerWidth)
 
  return points
}

Функція useState повертає масив, у якому під індексом 0 знаходиться зміна, що буде зберігати state та під індексом 1 повертає функцію, що буде змінювати state. Зверніть увагу, що points буде мати ініційоване значення, яке ми передали в хук useState, а саме window.innerWidth. Якщо порівняти ці рядки з компонентом на основі класу, то ми маємо аналог ініціації змінної points в методі constructor, setPoints — це аналог setState.

Створюємо обробник, який буде записувати актуальну ширину екрану:

…
const handleResize = () => setPoints(window.innerWidth)
…

Підписуємо обробник handleResize на подію зміни документу:

import { useState, useEffect } from 'react'
 
export default function useBreakpoints() {
…
  useEffect(() => {
    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  })
…
}

Хук useEffect буде викликатись декілька разів протягом життєвого циклу компонента — один раз після монтування (одразу як компонент буде додано в дерево) та кожного разу, коли є необхідність оновлення. Якщо знову провести аналогію з компонентом на основі класу, то ми маємо componentDidMount, componentDidUpdate, componentWillUnmount в одному місці. Зверніть увагу: щоб уникнути memory leak, необхідно відписатись, коли компонент буде відмонтовано і знищено. Для цього необхідно повернути функцію «відписки» в контексті анонімної функції, що була передана в хук useEffect, як це наведено в прикладі. Готово!

Порада

Щоб викликати функцію лише один раз після монтування, скористуйтеся таким трюком. Передайте другим параметром пустий масив:

…
useEffect(() => handleResize(), [])
…

Другим аргументом хук приймає масив об’єктів, що необхідно відстежувани. Такий механізм потрібен для контрольованих викликів. Тобто побічні ефекти в контексті хука будуть виконанні тільки при зміні значень, переданих в масив об’єктів.

Важливі правила

  1. Викликайте хуки тільки в середині функціональних компонентів.
  2. Викликайте хуки тільки на верхньому рівні функції, тобто на початку блоку. Не викликайте хуки в межах loops, conditions чи nested functions.

Для розробки є плагін eslint-plugin-react-hooks для лінтера ESLint, що буде відстежувати виконання цих правил.

Як щодо тестування

З точки зору React, компонент, який використовує Hooks, — це звичайний компонент. Для тестування кастомних хуків можна використати утиліти react-testing-library. Необхідно лише створити компонент і викликати в ньому хук. Далі можна симулювати подію зміни document view та перевірити «вихлоп» функції (даруйте за жаргон, не стримався ;) ).

import React from 'react'
import { render } from 'react-testing-library'
import useBreakpoints from './useBreakpoints'
 
const sizeSmall = 320
 
void function fireResize(width = sizeSmall) {
    window.innerWidth = width
    window.dispatchEvent(new Event('resize'))
}()
 
function EffecfulComponent() {
    const breakpoint = useBreakpoints()
    return <span>{breakpoint}</span>
}
 
test('useBreakpoints listen to window resize', () => {
    const { container } = render(<EffecfulComponent />)
    const span = container.firstChild
    expect(span.textContent).toBe(sizeSmall.toString())
})

Переваги

  1. Функціональні компоненти на основі хуків більш наочні за класи та мають майже всі можливості реакт компоненту.
  2. Хуки дозволяють розділити один компонент на більш дрібні функції в залежності від того, які частини пов’язані.
  3. Можна повторно використовувати логіку в компонентах та ділитись цілими колекціями хуків, наприклад через npm.js

Недоліки

  1. Немає можливості викликати хуки в компонентах на основі класу (див. правила). Тому неможливо без додаткового пласту додати хуки в поточний проект, що робить нераціональним (дорожчим) їх використання. Тому вважаю, що доцільно використовувати хуки тільки в нових проектах.

Це переваги та недоліки, виявлені на перший погляд. Вважаю, що цей список дещо ширший, але в цілому переваг, як мені вбачається, більше:)

P. S. Хоч я є «адептом» Vue.js, я дуже привітно ставлюся до такого кроку зі сторони реакт-спільноти — не просто тримати планку найпопулярнішої js-бібліотеки, а й робити такі зміни та ще й в кращий бік. Вважаю, що React Hooks API — це майбутнє реакту, хоча підтримка класів залишається.

В репозиторії можете подивитись код більш складного прикладу реалізації цього хуку, разом з демо та прикладом юніт-тесту.

Похожие статьи:
В C++17 додали auto параметри шаблонів. Це значно спростило використання шаблонів. У якості аргументів можна писати, що прийдеться,...
На DOU не было еще интервью с Юрием Антонюком, главой EPAM Украина, поэтому мы решили заполнить этот пробел. Тем более в этом году...
Цього разу DOU Ревізор завітав до Sisense, продуктової компанії, заснованої 2004 року в Ізраїлі. Sisense розробляє бізнес-аналітичну...
16 лютого Міністерство цифрової трансформації оприлюднило список 55 IT-компаній, які стали першими резидентами Дія Сity,...
Время: вторник+четверг, 19:00-21:00Продолжительность: 6 недель 11 октября стартует курс Front-End Pro. Курс предназначен для...
Яндекс.Метрика