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 — це майбутнє реакту, хоча підтримка класів залишається.

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

Похожие статьи:
Дайджест создан в соавторстве с Егором Папышевым. 00h > Интро В выпуске: фейковый мобильный банкинг, таргеритующий пользователей...
Українська компанія Stetman підписала угоду зі шведською компанією Requtech про ліцензійне серійне виробництво супутникових терміналів...
Ссылки, на которые лучше таки нажать (по мнению автора), отмечены знаком (!) Микросервисы и монолиты (!) In Defence of Monoliths. Краткий...
Найди верные ответы на вопросы викторины, ответь на творческий вопрос, и получи шанс выиграть прекрасный приз – элегантные...
Смартфон — це не зручно. Він не є органічним продовженням нашого тіла. Коли я їду в таксі й водій опускає погляд...
Яндекс.Метрика