от Джубой Джонсон

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

В этой статье мы рассмотрим хук useReducer и почему он лучше подходит для управления сложными состояниями в React, чем хук useState. Это руководство подходит для начинающих, и вам необходимо установить Node.js и React.

Государственное управление

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

Что такое государственное управление? Согласно Википедии, «управление состоянием относится к управлению состоянием одного или нескольких элементов управления пользовательского интерфейса, таких как текстовые поля, кнопки OK, переключатели и т. д.».

useReduce против useState

Можно ли управлять состоянием без использования useState? Распространенный вопрос с более чем 300 000 результатов в Google:

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

Настройка области разработки

Нам нужно запустить это:

npx create-react-app counter
cd counter
npm start

После установки у нас должно получиться вот это.

использованиередьюсер

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

UseReducer возвращает массив, первый элемент которого является состоянием, а второй элемент — функцией отправки, которая вызывает useReducer.

Для сборки приложения-счетчика нам нужны четыре файла: основной App.js для рендеринга наших компонентов; Counter.js для нашего счетчика приложений; Reducer.js где мы будем управлять состоянием приложения, используя нашу логику useReducer; и наш Styles.css. Возникают вопросы: что мы делаем? Как мы управляем государством? Какие преимущества это даст по сравнению с useState? А вопросы продолжаются. На все эти вопросы я отвечу в этой статье.

Повтор сеанса с открытым исходным кодом

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

Начните получать удовольствие от отладки — начните использовать OpenReplay бесплатно.

Приложение счетчика

Вот как мы могли бы начать это.

const [count, dispatch] = useReducer(reducer, 0);

Вместо сеттера в useState мы использовали dispatch. «Отправка» здесь имеет буквальное значение, больше похожее на то, что вы хотите что-то отправить: вы можете сказать «отправить действие». Мы обработаем его функцией редуктора. Как мы видим, у нас есть состояние 0. Давайте начнем создавать приложение-счетчик.

//counter.js
import React, { useReducer } from "react";
import reducer from "./Reducer";

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

  return (
    <div className="container">
      <div className="card">
        <h1>Counter Application</h1>
        <h3>{count}</h3>
        <div>
          <button className="btn1" onClick={() => dispatch("increment")}>
            increment
          </button>

          <button className="btn2" onClick={() => dispatch("decrement")}>
            decrement
          </button>

          <button className="btn3" onClick={() => dispatch("reset")}>
            Reset
          </button>
        </div>
      </div>
    </div>
  );
}

export default Counter;

У нас также есть:

//reducer.js

const reducer = (state, action) => {
  if (action === "increment") {
    return state + 1;
  } else if (action === "decrement") {
    return state - 1;
  } else if (action === "reset") {
    return 0;
  } else {
    throw new Error();
  }
};

export default reducer;

И стиль:

//styles.css

.container {
  display: flex;
  align-items: center;
  justify-content: center;
}

h3 {
  display: flex;
  align-items: center;
  justify-content: center;
}

.btn1 {
  background-color: blue;
  margin: 20px;
  color: beige;
}

.btn2 {
  background-color: red;
  margin: 20px;
  color: beige
}

.btn3 {
  background-color: green;
  margin: 20px;
  color: beige
}

Наконец, наш основной файл App.

//App.js

import React from "react";
import "./styles.css";
import Counter from "./Counter";

function App() {
  return (
    <div>
      <Counter />
    </div>
  );
}

export default App;

В приведенном выше коде показано приложение-счетчик, состояние которого управляется хуком useReducer. Счетчик мало что говорит о сложном управлении состоянием, но затем я объясню использованную выше логику. Редуктор принимает наше состояние и отправленное действие. В Reducer.js функция редуктора принимает наше состояние, и действие отправляется, затем мы используем наш условный оператор. У нас было if-else, а прошло action.type. Мы передали increment, decrement и reset функции onclick в JSX.

Теперь мы можем протестировать наше приложение, если оно работает хорошо. Изначально имеем:

После некоторых приращений получаем:

Именно здесь мы начинаем видеть, как useReducer проливает на нас свой свет. Возможно, мы не заметили, что полностью отделили логику обновления нашего состояния от нашего компонента. Теперь мы сопоставляем действия с переходами состояний, и теперь мы можем отделить то, как обновляется состояние, от произошедших действий. (Мы рассмотрим более практические преимущества этого позже.) А пока давайте добавим в наше приложение более сложные функции, чтобы лучше объяснить, насколько удобным может быть useReducer.

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

import React, { useState } from "react";

function Slider({ onchange, min, max }) {
  const [value, setvalue] = useState(1);

  return (
    <div className="slide">
      {value}
      <input
        type="range"
        min={min}
        max={max}
        value={value}
        onChange={(e) => {
          const value = Number(e.target.value);
          onchange(value);
          setvalue(value);
        }}
      />
    </div>
  );
}

export default Slider;

Нам нужно импортировать это в наш Counter.js, чтобы его можно было отобразить в браузере. Мы также будем передавать реквизиты min, max и onchange, присваивая им значения.

import Slide from "./Slide";

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

  return (
    <div className='container'>
        <div className='card'>
            <h1>Counter Application</h1>
            <h3>{state.count}</h3>
            <div >
                <button className='btn1' onClick={() => dispatch('increment')}>increment</button>
                <button className='btn2' onClick={() => dispatch('decrement')}>decrement</button>
                <button className='btn3' onClick={() => dispatch('reset')}>Reset</button>
            </div>
            <div>
                <Slide
                    min={1}
                    max={100}
                    onchange={()=>({})}
                />
            </div>
        </div>
    </div>
  );
}

Вот что мы получаем.

Теперь мы можем получить значение ползунка из реквизита onChange. Это позволяет нам решить, насколько мы можем увеличивать и уменьшать значения. Нам нужно внести несколько изменений, чтобы управлять частью состояния значения нашего ползунка и позволить нашему ползунку определять, что мы будем увеличивать или уменьшать.

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

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

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

const reducer = (state, action) => {
  if (action === "increment") {
    return {
      count: state.count + 1,
      move: state.move,
    };
  } else if (action === "decrement") {
    return {
      count: state.count - 1,
      move: state.move,
    };
  } else if (action === "reset") {
    return {
      count: 0,
      move: state.move,
    };
  } else {
    throw new Error();
  }
};
export default reducer;

Вернувшись в счетчик, нам нужно передать состояние нашему JSX.

<h3>{state.count}</h3>

Это прекрасно работает, но вместо того, чтобы наше состояние было целым числом, мы теперь имеем его как объект, что позволяет нам передавать другие свойства. Теперь возникает вопрос: что вы хотите отправить в onChange для обновления состояния нашего редуктора? До сих пор мы могли отправлять тип произошедшего действия (увеличение, уменьшение или сброс). Это сработало просто отлично, но теперь мы сталкиваемся с его ограничениями. Наряду с action type нужны еще некоторые данные. В частности, нам нужно передать значение слайда, чтобы добавить его к нашему значению состояния и обновить состояние. Вместо того, чтобы передавать наше действие в виде строки, давайте изменим его на объект со свойством типа. Таким образом, мы все еще можем выполнять диспетчеризацию в зависимости от типа действия. Мы сможем передать значение ползунка и любые другие данные в качестве свойства объекта действия. Мы можем перейти к нашей опоре onChange и сделать это прямо сейчас.

<Slide
  min={1}
  max={100}
  onchange={(value) =>
    dispatch({
      type: "stepUpdate",
      step: value,
    })
  }
/>;

Нам нужно внести три изменения в наш Редюсер:

  • Нам нужно обновить инкремент и декремент, чтобы настроить счетчик на основе свойства шага, а не только на 1. Мы делаем это, обновляя его любым move.
  • Нам нужно учесть наш новый тип действия moveUpdate, добавив для него случай в наш редюсер.
  • Нам нужно изменить action, чтобы он был объектом, а не строкой, передав свойство типа только нашему новому случаю.

Давайте сделаем эти быстрые исправления.

//Reducer.js

const reducer = (state, action) => {
  if (action === "increment") {
    return {
      count: state.count + state.move,
      move: state.move,
    };
  } else if (action === "decrement") {
    return {
      count: state.count - state.move,
      move: state.move,
    };
  } else if (action === "reset") {
    return {
      count: 0,
      move: state.move,
    };
  } else if (action.type === "moveUpdate") {
    return {
      count: state.count,
      move: action.move,
    };
  } else {
    throw new Error();
  }
};

export default reducer;

Теперь мы можем обновить значение счетчика с помощью ползунка — например, увеличить от нуля сначала на 31, а затем на 48.

Заключение

Мы подошли к концу статьи, но мне нужно разъяснить кое-что очень важное. Мы увидели невероятное и мощное преимущество useReducer, которое вы, возможно, упустили: функция редюсера передала текущее состояние в качестве первого аргумента. Из-за этого просто обновить одну часть состояния в зависимости от значения другой части состояния. Для этого вам нужно будет использовать крючок useReducer вместо useState. В нашем примере мы могли видеть это при обновлении count на основе значения `move '.

Другие статьи из блога OpenReplay

Голосовые формы в React с помощью Speechly

Узнайте, как заполнить форму, говоря с помощью React и Speechly

4 мая 2022 г. · 4 минуты чтения

Первоначально опубликовано на blog.openreplay.com 5 мая 2022 г.