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

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

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

Все вышеперечисленные моменты делают управление состоянием в приложении JavaScript несогласованным, что приводит к потенциальным ошибкам, несоответствиям и трудностям в понимании и поддержке поведения системы.

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

Что такое государственная машина?

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

Конечный автомат состоит из набора состояний, набора входных данных и набора переходов.

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

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

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

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

Этот блог посвящен созданию и управлению состояниями (управление состояниями) в JavaScript. Поскольку приложения JavaScript становятся все более сложными, управление состоянием может стать серьезной головной болью. С ростом числа компонентов, взаимодействий с пользователем и асинхронных событий, которые необходимо отслеживать, легко потерять контроль над различными состояниями, в которых может находиться ваше приложение.

Вот тут-то и появляется xState. Это библиотека для создания и управления автоматами с конечным числом состояний и диаграмм состояний в приложениях JavaScript. Он предоставляет простой и интуитивно понятный API для определения состояний и переходов, а также расширенные функции, такие как иерархическое вложение состояний, параллельные состояния и защитные функции.

Что такое xState?

xState — это библиотека с открытым исходным кодом для создания и управления конечными автоматами и диаграммами состояний в приложениях JavaScript. Он был создан Дэвидом Хуршидом, разработчиком и спикером, увлеченным управлением состоянием и дизайном пользовательского интерфейса.

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

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

Некоторые преимущества диаграмм состояний:

  1. Четкая визуализация. Диаграммы состояний предлагают графическое представление состояний, переходов и событий системы. Эта визуальная ясность облегчает дизайнерам, разработчикам и заинтересованным сторонам понимание поведения и логики системы.
  2. Поведение, управляемое событиями. Диаграммы состояний сосредоточены на событиях, вызывающих переходы между состояниями. Этот управляемый событиями подход особенно полезен в системах, где поведение зависит от внешних раздражителей или взаимодействия с пользователем. Это помогает гарантировать, что система соответствующим образом реагирует на события и поддерживает свою целостность.
  3. Поддержка сложной логики. Диаграммы состояний позволяют включать сложные логические условия и принимать решения в состояниях и переходах. Эта гибкость позволяет моделировать сложное поведение, включая параллельное и параллельное выполнение, условное ветвление и события, зависящие от времени.

Как работает xState?

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

Чтобы определить конечный автомат в xState, вы начинаете с определения различных состояний, в которых может находиться ваше приложение. Каждое состояние представлено объектом JavaScript, который содержит набор свойств и методов. Например, вот как можно определить простой конечный автомат, представляющий выключатель света:

const lightSwitchMachine = {
  initial: 'off',
  states: {
    off: {
      on: { target: 'on' }
    },
    on: {
      off: { target: 'off' }
    }
  }
};

В этом примере объект lightSwitchMachine определяет два состояния (off и on) с переходом между ними для каждого входа (on и off). Когда lightSwitchMachine инициализируется, он запускается в состоянии off.

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

import { createMachine, interpret } from "xstate";

const formMachine = createMachine({
  id: "form",
  initial: "idle",
  context: {
    username: "",
    password: "",
    confirmPassword: "",
    error: "",
  },
  states: {
    idle: {
      on: {
        SUBMIT: [
          {
            target: "validating",
            cond: (ctx) => {
              if (!ctx.username || !ctx.password || !ctx.confirmPassword) {
                ctx.error = "Please fill in all fields";
                return false;
              } else if (ctx.password !== ctx.confirmPassword) {
                ctx.error = "Passwords do not match";
                return false;
              } else {
                ctx.error = "";
                return true;
              }
            },
          },
          { target: "idle" },
        ],
      },
    },
    validating: {
      entry: (ctx) => {
        console.log("Validating...", ctx.username, ctx.password);
      },
      on: {
        SUCCESS: "submitted",
        FAILURE: "idle",
      },
    },
    submitted: {
      type: "final",
    },
  },
});

const formService = interpret(formMachine)
  .onTransition((state) => console.log("Current state:", state.value))
  .start();

formService.send("SUBMIT"); // logs "Please fill in all fields"

formService.send({ type: "USERNAME_CHANGE", username: "omkarlanghe" });

formService.send({ type: "PASSWORD_CHANGE", password: "testsecret123" });

formService.send({ type: "CONFIRM_PASSWORD_CHANGE", confirmPassword: "testsecret123" });

formService.send("SUBMIT"); // logs "Validating... omkarlanghe testsecret123"
// logs "Current state: submitted"

Этот код определяет formMachine, который имеет три состояния: idle, validating и submitted. Когда форма находится в состоянии idle, она прослушивает событие SUBMIT. Если форма действительна, она переходит в состояние validating, где выводит сообщение на консоль и ожидает события SUCCESS или FAILURE. Если форма недействительна, она остается в состоянии idle и устанавливает сообщение об ошибке в контексте.

formService создается путем интерпретации formMachine и его запуска. Затем он отправляет событие SUBMIT, которое запускает переход в состояние idle, и регистрирует сообщение об ошибке. После этого он отправляет три события с типами USERNAME_CHANGE, PASSWORD_CHANGE и CONFIRM_PASSWORD_CHANGE для обновления контекста. Наконец, он отправляет еще одно событие SUBMIT, которое инициирует переход в состояние validating, записывает сообщение в консоль и переходит в состояние submitted.

Давайте рассмотрим еще несколько вариантов использования, используя несколько примеров для xState в Node.js.

Конечный автомат аутентификации

Одним из распространенных вариантов использования xState в Node.js является управление состоянием статуса аутентификации пользователя. Вот пример того, как вы можете определить в нем конечный автомат аутентификации:

const { Machine } = require("xstate");

const authMachine = Machine({
  id: "auth",
  initial: "loggedOut",
  states: {
    loggedOut: {
      on: {
        LOGIN: "loggedIn",
      },
    },
    loggedIn: {
      on: {
        LOGOUT: "loggedOut",
      },
    },
  },
 });
 // Usage:
 const authState = authMachine.initialState;
 console.log(authState.value); // 'loggedOut'
 
const nextState = authMachine.transition(authState, "LOGIN");
 console.log(nextState.value); // 'loggedIn'

В этом примере authMachine конечный автомат имеет два состояния (loggedOut и loggedIn), переход между которыми инициируется событиями LOGIN и LOGOUT. Переменная authState представляет начальное состояние машины, а переменная nextState представляет состояние машины после запуска события LOGIN.

Пример использования бесплатной доставки

Приведенный ниже код Javascript описывает базовый вариант использования для бесплатной доставки покупателю, если цена продукта меньше 1000 долларов США.

import { Machine } from 'xstate';

// Define the shipping state machine
const shippingMachine = Machine({
  id: 'shipping',
  initial: 'initial',
  states: {
    initial: {
      on: {
        SET_PRICE: [
          {
            cond: 'isFreeShipping',
            target: 'freeShipping',
            actions: 'setPrice',
          },
          {
            target: 'standardShipping',
            actions: 'setPrice',
          },
        ],
      },
    },
    standardShipping: {
      on: {
        SET_PRICE: [
          {
            cond: 'isFreeShipping',
            target: 'freeShipping',
            actions: 'setPrice',
          },
          {
            target: 'standardShipping',
            actions: 'setPrice',
          },
        ],
      },
    },
    freeShipping: {},
  },
}, {
  actions: {
    setPrice: (context, event) => {
      // Update the price in the context
      context.price = event.price;
    },
  },
  guards: {
    isFreeShipping: (context, event) => {
      // Check if the price is less than $1000
      return event.price < 1000;
    },
  },
});

// Initialize the shipping machine
const shipping = shippingMachine.initialState;

// Send events to the shipping machine
const shippingWithPrice1 = shippingMachine.transition(shipping, { type: 'SET_PRICE', price: 999 });
const shippingWithPrice2 = shippingMachine.transition(shipping, { type: 'SET_PRICE', price: 1500 });

В этом примере shippingMachine имеет три состояния: initial, standardShipping и freeShipping. Состояние initial — это начальное состояние, в котором машина переходит либо в standardShipping, либо в freeShipping в зависимости от условия события SET_PRICE. Охранник isFreeShipping проверяет, меньше ли указанная в событии цена 1000 долларов, и если да, автомат переходит в состояние freeShipping; в противном случае он переходит в состояние standardShipping. Действие setPrice обновляет цену в контексте для справки в охранниках.

Пример использования корзины покупок

Приведенный ниже код JavaScript определяет конечный автомат с использованием библиотеки xState, которая моделирует поведение системы как набор состояний и переходов между ними. Конечный автомат предназначен для определения того, соответствует ли группа товаров определенным условиям. Это вариант использования для ограничения продукта в зависимости от количества и цены.

"use strict";

const { createMachine, interpret } = require("xstate");
 
const context = {
  products: [
    { name: "T-Shirt", id: 1, quantity: 2, price: 3200 },
    { name: "Nike Air", id: 2, quantity: 5, price: 31200 },
    { name: "Ear pods", id: 3, quantity: 11, price: 4300 },
    { name: "Ear pods Large", id: 4, quantity: 6, price: 3300 },
  ],
 };
 
const machine = createMachine({
  predictableActionArguments: true,
  id: "machine",
  initial: "pending",
  states: {
    pending: {
      always: [
        {
          target: "sucess",
          cond: (ctx) => ctx.products.length === 1,
        },
        {
          target: "restrictProduct",
        },
      ],
    },
    restrictProduct: {
      always: [
        {
          target: "success",
          cond: (ctx) => {
            ctx.products.every(({ price, quantity }) => {
              if (quantity > 10 && price > 2000) {
                return true;
              }
              return false;
            });
          },
        },
        { target: "failure" },
      ],
    },
    success: {
      type: "final",
    },
    failure: {
      type: "final",
    },
  },
  context,
 });
const machineService = interpret(machine)
  .onTransition((state) => console.log("-->", state.value))
  .onDone(() => console.log("done"));

machineService.start();
 
console.log("==> result", machineService.getSnapshot().value); // success or failure

Давайте разберем код построчно,

"use strict";

const { createMachine, interpret } = require("xstate");

Эта строка импортирует две функции, createMachine и interpret, из библиотеки xstate.

const context = {
  products: [
    { name: "T-Shirt", id: 1, quantity: 2, price: 3200 },
    { name: "Nike Air", id: 2, quantity: 5, price: 31200 },
    { name: "Ear pods", id: 3, quantity: 11, price: 4300 },
    { name: "Ear pods Large", id: 4, quantity: 6, price: 3300 },
  ],
 };

Этот код определяет объект с именем context, который содержит массив объектов продукта. Каждый объект продукта имеет такие свойства, как name, id, quantity и price.

const machine = createMachine({
  predictableActionArguments: true,
  id: "machine",
  initial: "pending",
  states: {
    pending: {
      always: [
        {
          target: "success",
          cond: (ctx) => ctx.products.length === 1,
        },
        {
          target: "restrictProduct",
        },
      ],
    },
    restrictProduct: {
      always: [
        {
          target: "success",
          cond: (ctx) => {
            ctx.products.every(({ price, quantity }) => {
              if (quantity > 10 && price > 2000) {
                return true;
              }
              return false;
            });
          },
        },
        { target: "failure" },
      ],
    },
    success: {
      type: "final",
    },
    failure: {
      type: "final",
    },
  },
  context,
 });

Этот код определяет конечный автомат с помощью функции createMachine. Машина имеет идентификатор "machine", начальное состояние "pending" и четыре состояния: "pending", "restrictProduct", "success" и "failure".

В состоянии "pending" машина может перейти либо в состояние "success", либо в состояние "restrictProduct". Если количество продуктов в объекте context равно 1, автомат переходит в состояние "success". В противном случае он переходит в состояние "restrictProduct".

В состоянии "restrictProduct" автомат переходит в состояние "success", если каждый продукт в context объекте имеет количество больше 10 и цену больше 2000. В противном случае он переходит в состояние "failure".

Состояния "success" и "failure" являются конечными состояниями, что означает, что машина останавливается после перехода в любое из этих состояний.

const machineService = interpret(machine)
  .onTransition((state) => console.log("-->", state.value))
  .onDone(() => console.log("done"));

machineService.start();
console.log("==> result", machineService.getSnapshot().value); // success or failure

Заключение

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

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