Написать пост

Разработка пользовательских хуков для React

Аватарка пользователя Николай Русаков

Разбираем написание кастомных хуков на React максимально доступным и понятным языком, чтобы оно не вызывало сложностей даже у новичков.

Тема хуков в React разработке довольно популярна и не единожды разобрана различными специалистами. Цель нашего материала, не столько привнести что-то новое, сколько разобрать достаточно важную и актуальную тему максимально доступным и понятным языком, чтобы она не вызывала сложности даже у новичков в разработке приложений на React.

Впервые появились в React версии 16.8. Структура приложений, построенных на использовании хуков очень понравилась сообществу своей гибкостью и простотой, что позволило ей практически заместить классы. Как это сделать, рассказывает Русаков Николай, начальник отдела SEO-продвижения студии Moeseo.

С хуками проще!

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

Основные хуки React «из коробки»

React из коробки содержит несколько очень важных хуков.

useState — управляем состоянием в функциональных компонентах

Одним из важнейших хуков является useState. Его название сразу дает понять для чего он используется — он отвечает за состояние. Данный хук позволяет воздействовать на состояние подобно методу this.setState(). Для начала давайте рассмотрим простой пример его использования:

			const MyComponent = () => {
  const [counter, setCounter] = useState(1);
  return (
    <button onClick={() => setCounter(counter + 1)}>
      +1 к прежнему значению
    </button>
);
};
		

В данном примере, после объявления функционального элемента, мы вызываем метод useState, передавая ему значение по умолчанию:

			const [counter, setCounter] = useState(1);
		

Метод возвращает массив из 2-х элементов. 1-й элемент — это переменная состояния, 2-й элемент содержит функцию для изменения этого состояния. Для кнопки установлено событие onClick, которое позволяет изменять состояние, увеличивая значение на 1 от предыдущего:

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

useContext — передаем информацию на любые уровни вложенности

Как правило, родительский компонент делится данными с дочерними при помощи props. Однако, бывает необходимость передать эти данные не только ближайшим «детям» этого компоненты, но и более вложенным компонентам. Передавать по всей цепочке «родственников» — ужасно неудобно и может привести к ошибкам. В таком случае удобно использовать useContext.

			import {createContext, useContext} from "react";

const MyContext = createContext("информация отстутствует");

const Bookcase = () =⟩ {
  return (
    ⟨MyContext.Provider value="шкаф #1 "⟩
      ⟨Bookshelf /⟩
    ⟨/MyContext.Provider⟩
  );
};
 
const Bookshelf = () =⟩ {
  return ⟨Book /⟩;
};
 
const Book = () =⟩ {
  const context = useContext(MyContext);
  return `Книга лежит в: "${context}"`;
};
		

В данном примере, благодаря хуку useContext, каждая книга может иметь информацию о том, в каком шкафу она расположена, при этом нет необходимости получать ее он непосредственного родителя (книжной полки).

useEffect — реализуем функционал жизненного цикла

Используются для моделирования жизненного цикла, также как это делают методы componentDidMount, componentDidUpdate, componentWillUnmount при использовании структуры React на классах. В качестве аргументов передается callback-функция, которая выполняет необходимые действия и массив с переменными, за изменением которых необходимо наблюдать и при их изменении вызывать callback.

useRef — связываем переменные напрямую с DOM

Чтобы обратиться напрямую к DOM-элементам в функциональных компонентах необходимо использовать useRef. В примере ниже мы привязываем кнопку к переменной buttonRef и можем использовать эту связь в callback-функции, переданной в качестве 1-го аргумента в useRef.

			const MyButton = () =⟩ {
  const buttonRef = useRef();
  useEffect(() =⟩ {
    console.log(buttonRef.current.innerHTML);
  }, []);
  return ⟨button ref={ref}⟩Моя кнопка⟨/button⟩;
};
		

Больше хуков!

useReducer: позволяет хранить значение состояние независимо от вложенности, является аналогом Redux.

useMemo: позволяет хранить значение и пересчитывать, только при изменении зависимостей.

useCallback: используется для мемоизации функций, позволяет избежать лишних рендерингов и повторного создания функций. Очень полезен для оптимизации.

Пишем собственный хук

По своей сути хуки являются обычными функциями, название которых начинается с приставки «use». Они могут использовать другие хуки, принимать аргументы и возвращать результат.

Собственные хуки удобно создавать, когда требуется вынести логику, часто применяемую в разных компонентах приложения. Нужно помнить про важные ограничения для хуков: нельзя вызывать внутри условных конструкций и циклов — это вызовет ошибку в приложении.

Давайте рассмотрим пример из официальной документации react. Он интересен тем, что описывает пример создания хука useReducer с использованием уже существующего useState.

Допустим, мы работаем с компонентом, который имеет достаточно развитую логику управления состояниями в зависимости от их типа. В таком случа, useState — не очень удобно использовать при централизованной логике управления состояниями и больше подойдет Redux-reducer:

			function todosReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [...state, {
        text: action.text,
        completed: false
      }];
    // ... other actions ...
    default:
      return state;
  }
}
		

Наш хук будет выглядеть так (упрощенно):

			function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState)

  function dispatch(action) {
    const nextState = reducer(state, action)
    setState(nextState)
  }

  return [state, dispatch]
}
		

Здесь мы принимаем в качестве параметров — функцию-редьюсер, отвечающую за логику изменения состояния, начальное состояние, а возвращаем актуальное состояние и функцию для его изменения. При этом, мы можем очень просто модифицировать логику изменения состояний, добавив/изменив типы в функции todosReducer. После чего, удобно использовать эту логику в своем компоненте:

			function Todos() {
  const [todos, dispatch] = useReducer(todosReducer, [])
  function handleAddClick(text) {
    dispatch({ type: 'add', text })
  }
  // ...
}
		

Хуки могут использоваться для совершенно разнообразных целей. Давайте рассмотрим пример хука для работы с локальным хранилищем браузера:

			function getStorageValue(key, defaultValue) {
  const saved = localStorage.getItem(key);
  const initial = !saved || saved === 'undefined' ? null : JSON.parse(saved);
  return initial || defaultValue;
}
export default  function useLocalStorage (key, defaultValue){
  const [value, setValue] = useState(() =⟩ {
    return getStorageValue(key, defaultValue);
  });

  useEffect(() =⟩ {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
};
		

Мы определили функцию работы с localStorage, написав собственную логику получения первичных данных из хранилища. Следом создали хук, который инициализирует состояние, используя результат выполнения данной функции. Также мы используем useEffect, чтобы при изменении значения переменных key или value, произвести запись в localStorage. В своем компоненте мы можем использовать хук таким образом:

			const [storageData, setStorageData] = useLocalStorage(‘my-data’);
		

Так, у нас получилось связать данные в состоянии с данными в локальном хранилище браузера.

На этом пока что все, надеюсь, что данная статья помогла сделать понимание темы хуков для Вас проще!

Следите за новыми постами
Следите за новыми постами по любимым темам
6К открытий6К показов