{wcademy}

Введение в React-хуки

June 22, 2020

Всем привет! Продолжаем перетирать за Реакт, и познакомимся с хуками — самой горячей темой в Реакте последнее время. Хоть большинство курсов и туториалов используют классический подход с компонентами на основе классов, уже многие проекты, особенно новые, активно используют исключительно функциональные компоненты с хуками. И их точно нужно знать в 2020. В этой мы познакомимся с двумя самыми часто используемыми хуками: useState и useEffect.

useState

Надеюсь, у вас уже установлена нода. Создадим проект с помощью create-react-app:

npx create-react-app hooks --template typescript

Весь код в этом туториале будет затрагивать исключительно файл src/App.tsx. Начнем с того, что заменим содержимое этого файла простым функциональным компонентом:

import React from 'react'

function App() {
  return (
    <div className='App'>
      <h1>0</h1>
      <button>+1</button>
    </div>
  )
}

export default App

Всё очевидно, мы просто отрендерили немного текста и пока полностью бесполезную кнопку.

внешний вид реакт компонента в браузере с нулевым значением

Теперь давайте импортируем самый первый хук — useState, чтобы научиться работать со стейтом в функциональных компонентах. Для начала сохраним результат вызова функции useState в переменную value, и посмотрим, что выведется в консоль.

import React, { useState } from 'react'

function App() {
  const value = useState()

  console.log(value)

  return (
    <div className='App'>
      <h1>0</h1>
      <button>+1</button>
    </div>
  )
}

export default App

Как видим, в результате у нас массив, с нулевым элементом равным undefined, на первой позиции какая-то функция.

> (2) [undefined, ƒ]

А если попробовать передать какой-нибудь аргумент?

const value = useState(true)
> (2) [true, ƒ]

Теперь на нулевым индексе у нас переданное значение, в нашем случае — true.

В общем, useState принимает дефолтное значение стейта, возвращает массив, где нулевой элемент текущий стейт, а первый — функция setState, вызов которой изменит конкретно этот стейт.

Давайте сразу используем деструктуризацию, чтобы отдельно получить значение стейта и setState, и используем их в коде компонента, оживив его.

Функцию можно называть как угодно, но принято использовать пре́фикс set и имя переменной, которое мы хотим поменять^ в нашем случае setCounter. Ведь в одном компоненте нам может потребоваться больше одного такого «министейта». Использование этой функции элементарно, всё то же самое, что и setState в компонентах на классах. Можно просто передать новое значение стейта, а можно передать функцию, которая принимает старый стейт и должна вернуть новый. Всегда используйте второй вариант, если новое значение стейта зависит от старого! Мы будем вызывать setCounter по клику по кнопке, для этого добавим хэндлер onClick, внутри которого будем менять стейт, прибавляя к counter единицу.

import React, { useState } from 'react'

function App() {
  const [counter, setCounter] = useState(0)

  return (
    <div className='App'>
      <h1>{counter}</h1>
      <button
        onClick={() => {
          setCounter(counter => counter + 1)
        }}
      >
        +1
      </button>
    </div>
  )
}

export default App

Можно немного подчистить код, отделив обработчик клика в отдельную функцию.

import React, { useState } from 'react'

function App() {
  const [counter, setCounter] = useState(0)

  const onClick = () => {
    setCounter(counter => counter + 1)
  }

  return (
    <div className='App'>
      <h1>{counter}</h1>
      <button onClick={onClick}>+1</button>
    </div>
  )
}

export default App

Супер, теперь у нас есть счётчик кликов по кнопке.

внешний вид реакт компонента в браузере со значением равным пяти

useEffect

Хуки облегчили достаточно многое, относительно компонентов на классах. Касаясь тех же методов жизненного цикла (вроде componentDidMount, componentDidUpdate, etc), надо было четко помнить и их список, и когда они вызываются, какой лучше для чего подходит. useEffect всё сделал проще, и запросы к серверу, и ручные манипуляции DOM, добавление обработчиков событий, задание таймаутов и все такие сайд эффекты.

useEffect импортируется так же, как и useState.

import React, { useEffect, useState } from 'react'

Чтобы useEffect сделал что-то, мы должны передать в него функцию. Каждый раз когда Реакт будет перерендеривать компонент, он вызовет функцию, переданную в useEffect.

import React, { useEffect, useState } from 'react'

Так будет выглядеть пример целиком на данный момент:

import React, { useEffect, useState } from 'react'

function App() {
  const [counter, setCounter] = useState(0)

  const onClick = () => {
    setCounter(counter => counter + 1)
  }

  useEffect(() => {
    console.log('useEffect')
  })

  return (
    <div className='App'>
      <h1>{counter}</h1>
      <button onClick={onClick}>+1</button>
    </div>
  )
}

export default App

Для примера, давайте заюзаем библиотечку с npm, которая будет нам возвращать рандомный цвет. В качестве ДЗ, рекомендую написать это самостоятельно, но я просто сделаю npm i randomcolor @types/randomcolor и импортирую её:

import randomcolor from 'randomcolor'

Создадим ещё один стейт для хранения цвета, для начала сделаем его пустой строкой — это не даст цвета, но и ничего не поломает.

const [color, setColor] = useState('')

И зададим цвет счётчику

<h1 style={{ color: color }}>{counter}</h1>

Теперь можно поупражняться с useEffect. Давайте при каждом ререндере компонента менять цвет счётчика. Но здесь есть заковырка. Когда происходит ререндер? Когда меняются пропсы или стейт. Если при изменении стейта от счётчика, мы изменим стейт для цвета, то это вызовет ещё один ререндер и, вуаля, мы в бесконечном цикле повесили браузер. Такой вариант работать не будет:

useEffect(() => {
  setColor(randomColor())
})

Возможно, нам нужно вызывать useEffect, только когда меняется переменная counter? Чтобы сказать useEffect, за какой переменной следить, можно передать ему вторым аргументом массив таких переменных.

useEffect(() => {
  setColor(randomColor())
}, [counter])

внешний вид цветного реакт компонента в браузере

Можно сказать, что эта запись значит «вызови переданную функцию тогда и только тогда, когда изменяется counter». Так мы можем изменять цвет и не скатиться в бесконечный цикл.

Пользуясь этим знанием, мы с помощью useEffect можем сэмулировать один из методов жизненного цикла Реакта componentDidMount (это компонент создался и был помещён на страничку):

useEffect(() => {
  alert('Hi!')
}, [])

Вторым аргументом можно передать пустой массив, это значит, что эффект не зависит ни от чего. А следовательно, раз ничего не может поменяться, будет вызван только один раз при маунте компонента.

Заключение

О хуках можно говорить много, это очень крутая концепция, которую подарили нам разработчики Реакта. Она позволила сделать код приложений намного лаконичнее и функциональнее. Мы рассмотрели только часть фичей, которые нам дают хуки, но и этих основ хватит, чтобы отказаться от использования компонентов-классов и делать довольно сложные приложения. Счастливого кодинга!

🚀  Если узнал из статьи что-то полезное, ставь лайк и подписывайся на наш канал в Телеграм или группу ВК. Обсудить статью можно в нашем уютном чатике 😏

© 2019 - 2022, {wcademy}