DevGang
Авторизоваться

Путешествие во времени в React с помощью Immer: Пошаговое руководство

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

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

В этом руководстве мы шаг за шагом расскажем вам, как интегрировать Immer в ваше React-приложение и раскрыть захватывающий потенциал отладки путешествий во времени. Мы рассмотрим такие важные понятия, как неизменяемость, переходы состояний и магию функции Immer's produce. По ходу работы вы увидите, как настроить среду разработки для отладки путешествий во времени, манипулировать снимками состояний и перемещаться по ним, а также воспроизводить прошлые последовательности состояний.

Демонстрация

В представленной демонстрации будет представлено компактное приложение, в котором пользователи смогут создавать, изменять размеры и перемещать поля в интерактивном холсте. Примечательно, что в приложении будет реализована функция отмены действий, позволяющая пользователям легко ориентироваться в своих действиях. Такое гармоничное сочетание функциональных возможностей дает пользователям свободу экспериментов и позволяет без труда вернуться назад или повторить свои действия. Этот интересный опыт продемонстрирует возможности современной фронтэнд-разработки и покажет, как простая на первый взгляд концепция превращается в мощное приложение.

Настройка

На самом деле Immer нам нужен только как обязательная библиотека для этой демонстрации, но я также устанавливаю Theme Ui и react-resizable, чтобы сократить время разработки.

Редуктор

Первое, что нам нужно, - это редуктор, чтобы мы могли отслеживать действия и возвращать желаемые результаты:

import  { produceWithPatches, applyPatches } from "immer";

export const boxAction = (draft, action) => {
  const { width, height, id, color, position } = action;
  let box = draft.boxes[draft.selectBox];

  switch (action.type) {
    case "ADD_BOX":
      draft.boxes[id] = {
        id,
        width,
        height,
        color,
        position,
      };
      break;
    case "SELECT_BOX":
      draft.selectBox = id
      break;
    case "MOVE_BOX":
      if (!box) return;
      box.position = position;
      break;
    case "RESIZE_BOX":
      if (!box) return;
      box.width = width;
      box.height = height;
      box.position = position;
      break;
    case "DELETE":
      delete draft.boxes[draft.selectBox];
      break;
    case "APPLY_PATCHES":
      return applyPatches(draft, action.patches);
  }
};

Мы можем начать рассматривать каждое действие:

  • ADD_BOX: мы добавляем в магазин новое поле
  • SELECT_BOX: мы выбираем поле (для удаления, изменения размера или перемещения)
  • MOVE_BOX: мы перемещаем поле в новое положение
  • RESIZE_BOX: мы изменяем размеры поля
  • DELETE: удаляем выделенное поле
  • APPLY_PATCHES: для этого мы используем функцию Immer applyPatches. Во время работы продюсера Immer может записать все патчи, которые воспроизведут изменения, сделанные редуктором. Эта функция позволяет нам исправлять состояние

Затем создадим producer с помощью команды Immer produceWithPatches и создадим начальное состояние:

export const patchGeneratingBoxesReducer = produceWithPatches(boxAction);

export function getInitialState() {
  return {
    boxes: {},
  };
}

Функция диспетчеризации

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

 const undoStack = useRef([]);
  const undoStackPointer = useRef(-1);

  const dispatch = useCallback((action, undoable = true) => {
    setState((currentState) => {
      const [nextState, patches, inversePatches] = patchGeneratingBoxesReducer(
        currentState,
        action
      );
      if (undoable) {
        const pointer = ++undoStackPointer.current;
        undoStack.current.length = pointer;
        undoStack.current[pointer] = { patches, inversePatches };  
      }
      return nextState;
    });
  }, []);

Не волнуйтесь, я всё подробно объясню:

  • undoStack и undoStackPointer: undoStack - это ссылка на массив, в котором будет храниться история изменений состояния (патчей) для отменяемых действий. undoStackPointer - это ссылка на число, которое отслеживает текущую позицию в стеке отмены.
  • Параметр undoable: Логический флаг, указывающий, следует ли считать данное действие отменяемым.
  • Управление историей отмены: Если действие помечено как отменяемое (undoable равно true), то код добавляет патчи и инверсные патчи в undoStack для потенциальных операций отмены.
  • undoStackPointer увеличивается и указывает на текущую позицию в стеке.
  • Предыдущие действия undoable за указателем удаляются из стека для сохранения линейной истории.

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

Кнопки

В этом приложении у нас будет 4 кнопки: Create - Delete - Undo - Redo (Создать - Удалить - Отмена - Повтор). Давайте разберемся с каждой из них:

Кнопка "Создать":

const createButton = () => {
    const width = Math.floor(Math.random() * (300 - 100 + 1) + 100)
    const height = Math.floor(Math.random() * (300 - 100 + 1) + 100)
    dispatch({
      type: "ADD_BOX",
      width: width,
      height: height,
      id: uuidv4(),
      color:
        `#` +
        Math.floor(16777215 * Math.random()).toString(16),
      position: {
        x: window.innerWidth * 0.8 / 2 - width / 2,
        y: window.innerHeight / 2 - height / 2,
      }
    });
  };

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

Кнопка "Удалить":

const deleteButton = () => {
    dispatch({
      type: "DELETE",
    });
    dispatch({
      type: "SELECT_BOX",
      id: null
    }, false)
  }

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

Кнопки "Отмена" и "Повтор":

const undoButton = () => {
    if (undoStackPointer.current < 0) return;
    const patches = undoStack.current[undoStackPointer.current].inversePatches;
    dispatch({ type: "APPLY_PATCHES", patches }, false);
    undoStackPointer.current--;
    dispatch({
      type: "SELECT_BOX",
      id: null
    }, false)
  };

  const redoButton = () => {
    if (undoStackPointer.current === undoStack.current.length - 1) return;
    undoStackPointer.current++;
    const patches = undoStack.current[undoStackPointer.current].patches;
    dispatch({ type: "APPLY_PATCHES", patches }, false);
    dispatch({
      type: "SELECT_BOX",
      id: null
    }, false)
  };

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

Результаты

Заключение

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

Исходный код: https://github.com/superdev163/immer-square

Живая демонстрация: https://immer-square.web.app/

Источник:

#JavaScript #React #Начинающим
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу