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, a 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(), []) …
Другим аргументом хук приймає масив об’єктів, що необхідно відстежувани. Такий механізм потрібен для контрольованих викликів. Тобто побічні ефекти в контексті хука будуть виконанні тільки при зміні значень, переданих в масив об’єктів.
Важливі правила
- Викликайте хуки тільки в середині функціональних компонентів.
- Викликайте хуки тільки на верхньому рівні функції, тобто на початку блоку. Не викликайте хуки в межах 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()) })
Переваги
- Функціональні компоненти на основі хуків більш наочні за класи та мають майже всі можливості реакт компоненту.
- Хуки дозволяють розділити один компонент на більш дрібні функції в залежності від того, які частини пов’язані.
- Можна повторно використовувати логіку в компонентах та ділитись цілими колекціями хуків, наприклад через npm.js
Недоліки
- Немає можливості викликати хуки в компонентах на основі класу (див. правила). Тому неможливо без додаткового пласту додати хуки в поточний проект, що робить нераціональним (дорожчим) їх використання. Тому вважаю, що доцільно використовувати хуки тільки в нових проектах.
Це переваги та недоліки, виявлені на перший погляд. Вважаю, що цей список дещо ширший, але в цілому переваг, як мені вбачається, більше:)
P. S. Хоч я є «адептом» Vue.js, я дуже привітно ставлюся до такого кроку зі сторони реакт-спільноти — не просто тримати планку найпопулярнішої js-бібліотеки, а й робити такі зміни та ще й в кращий бік. Вважаю, що React Hooks API — це майбутнє реакту, хоча підтримка класів залишається.
В репозиторії можете подивитись код більш складного прикладу реалізації цього хуку, разом з демо та прикладом юніт-тесту.