Владислав Калачев

Учим useReducer на примерах — React Hooks

3 292

Доброго времени суток, друзья. Данная статья продолжает цикл статей о React Hooks. Сегодня мы поговорим о хуке useReducer. Для чего он нужен? Стоит ли использовать useReducer? Какую пользу он может принести вашему приложение? Обо всем об этом по порядку.

Видео на эту тему.

useState или useReducer?

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

Пример:


const [state, dispatch] = useReducer(reducer, initialState, init)

Хук useReducer принимает три параметра. Первым параметром является функция редьюсер, идея работы которой взята из Redux. Она получает текущее состояние и экшен с типом для его изменения.

Пример:


function reducer(state, action) {
  switch (action.type) {
    case 'plus':
      return {count: state.count + 1};
    case 'minus':
      return {count: state.count - 1};
    default:
      return state
 }
}

Второй параметр, передаваемый useReducer являет начальное состояние, которое и попадает в первый аргумент нашего редьюсера. Третим параметром является функция для «ленивой» инициализации первоначального состояния.

Пример:


function init(initialState) {
  return {count: initialState}
}

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

Пример:


function reducer(state, action) {
  switch (action.type) {
    case 'plus':
      return {count: state.count + 1};
    case 'minus':
      return {count: state.count - 1};
   case 'reset':
      return init(action.payload);
    default:
      return state
 }
}

После вызова useReducer возвращает массив с состоянием и функцией dispatch. Для изменения состояния в редьюсере и получения нового стейта, достаточно передать в функцию dispatch нужный экшен. Как видите, данный механизм повторяет логику работы с Redux.

useReducer на примерах

Давайте реализуем классический пример счетчика, используя useReducer с дополнительной фишкой. Добавим немножко асинхронности, каждые 1000 миллисекунд будет происходить увеличение счетчика на 1 при условии, что у нас остаются кнопки для ручного изменения значения счетчика.

Пример:


function reducer(state, action) {
  switch (action.type) {
    case "plus":
      return {
        ...state,
        count: state.count + 1
      };
    case "minus":
      return {
        ...state,
        count: state.count - 1
      };
    default: {
      return state;
    }
  }
}

Тут ничего интересно. Создаем редьюсер, в который будет попадать экшен. Если тип экшена равен “plus”, то count увеличиваем на 1, если “minus” count уменьшается на 1.

Стоит обратить внимание на использование деструктуризации: запись типа { …state} говорит о том, что прежде чем поменять объект мы клонируем все его содержимое и уже у этого нового объекта изменяем поле. Это важный момент и концепт иммутабельности (на основе данной концепции построен весь Redux). Получается, что каждый раз редьюсер не меняет первоначальное состояние в старом объекте, а возвращает полностью новое состояние в виде нового объекта.

Пример:


function Counter() {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
  });
  const { count } = state;

  useEffect(() => {
    const id = setInterval(() => {
      dispatch({ type: "plus" });
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return (
    <>
      <h1>count: {count}</h1>
      <button onClick={() => dispatch({ type: "plus" })}>INCREMENT</button>
      <button onClick={() => dispatch({ type: "minus" })}>DECREMENT</button>
    </>
  );
}

Counter принимает в хуке useReducer функцию reducer, которую мы рассматривали выше, и вторым параметром получает начальное состояние счетчика. Компонент имеет две кнопки, по клику на которые происходит вызов функции dispatch с передачей ей экшенов и приводящих к изменению стейт компонента.

Для реализации асинхронного эффекта используется хук useEffect в котором создается интервал setInterval с передачей ему функции dispatch и экшена { type: «plus» }. Вторым параметром setInterval указывается время, за которое данный интервал будет повторяться.

Для того, чтобы очистить память после стирания компонента из DOM нужно вызвать clearInterval и передать ему ссылку на уже созданный интервал, что позволит почистить его после unmount состояния компонента.

Тестирование useReducer

Благодаря тому, что хук useReducer принимает редьеюсер, а редьюесер – чистая функция, мы получаем простоту тестирования данного кода. Достаточно просто передать начальное значение стора и экшен и сравнить полученный результат.

Пример:


test("count === 1", () => {
  const newState = reducer({ count: 0 }, { type: "plus" });
  expect(newState.count).toBe(1)
})

Заключение

Сегодня мы рассмотрели на примерах, чем может быть полезен useReducer для вашего React приложения. Благодаря данному хуку работа с состоянием в компонентах стала более гибкой и расширяемой. Надеюсь, что данный материал был вам полезен. Учитесь, думайте, пишите код. Удачного кодинга, друзья!

Полезные материалы:

Офф док

Подписывайтесь на наш канал в Telegram и на YouTube для получения самой последней и актуальной информации. 

Комментарии:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *