Рубрики
Без рубрики

Введение в состояние

Обзор библиотеки JavaScript конечных автоматов State

Я писал о конечных автоматах в прошлом и я упомянул Государство . В этом посте я хочу представить эту популярную библиотеку JavaScript.

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

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

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

Вы устанавливаете состояние с помощью npm:

npm install xstate

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

import { Machine, interpret } from 'xstate'

В браузере вы также можете импортировать его напрямую из CDN:

и это создаст глобальную переменную состояния в объекте window .

Затем вы можете определить конечный автомат с помощью функции Machine factory. Эта функция принимает объект конфигурации и возвращает ссылку на вновь созданный конечный автомат:

const machine = Machine({

})

В конфигурации мы передаем строку id , которая идентифицирует конечный автомат, строку начального состояния. Вот простой пример светофора:

const machine = Machine({
  id: 'trafficlights',
  initial: 'green'
})

Мы также передаем объект состояния , содержащий разрешенные состояния:

const machine = Machine({
  id: 'trafficlights',
  initial: 'green',
  states: {
    green: {

    },
    yellow: {

    },
    red: {

    }
  }
})

Здесь я определил 3 состояния: зеленый желтый и красный .

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

Здесь мы переключаемся в желтое состояние, когда мы находимся в зеленом состоянии, и получаем событие ТАЙМЕР :

const machine = Machine({
  id: 'trafficlights',
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: 'yellow'
      }
    },
    yellow: {

    },
    red: {

    }
  }
})

Я назвал это ВРЕМЯ , потому что у светофоров обычно есть простой таймер, который меняет состояние освещения каждые X секунд.

Теперь давайте заполним остальные 2 перехода состояния: мы переходим от желтого к красному и от красного к зеленому:

const machine = Machine({
  id: 'trafficlights',
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: 'yellow'
      }
    },
    yellow: {
      on: {
        TIMER: 'red'
      }
    },
    red: {
      on: {
        TIMER: 'green'
      }
    }
  }
})

Как мы запускаем переход?

Вы можете получить строковое представление начального состояния машины с помощью:

machine.initialState.value //'green' in our case

и мы можем переключиться в новое состояние, используя переход() метод машины (экземпляр конечного автомата, возвращенный Machine() ):

const currentState = machine.initialState.value
const newState = machine.transition(currentState, 'TIMER')

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

const currentState = machine.initialState.value
const newState = machine.transition(currentState, 'TIMER')
console.log(newState.value)

Используя метод transition() , вы всегда должны отслеживать текущее состояние, что, на мой взгляд, вызывает небольшую боль. Было бы здорово, если бы мы могли спросить машину о ее текущем состоянии.

Это делается путем создания диаграммы состояния, которая в состоянии называется сервисом. Мы делаем это, вызывая метод interpretate() , который мы импортировали из xstate , передавая ему объект конечного автомата, а затем вызывая start() для запуска службы:

const toggleService = interpret(machine).start()

Теперь мы можем использовать этот метод службы send() для получения нового состояния без необходимости передавать текущее состояние, как это делается с машиной.переход() :

const toggleService = interpret(machine).start()
toggleService.send('TOGGLE')

Мы можем сохранить возвращаемое значение, которое будет содержать новое состояние:

const newState = toggleService.send('TOGGLE')
console.log(newState.value)

Это просто царапает поверхность государства.

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

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

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

Когда вы входите в дом, вы можете нажать одну из 2 имеющихся у вас кнопок, p1 или p2. Когда вы нажимаете любую из этих кнопок, загорается индикатор l1.

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

Если вы нажмете кнопку p1, l1 выключится, а l2 включится. Вместо этого, если вы нажмете кнопку p2, l1 выключится, а l3 включится.

При повторном нажатии любой из 2 кнопок, p1 или p2, индикатор, который в данный момент горит, погаснет, и мы вернемся в исходное состояние системы.

Вот наш объект государственной машины:

const machine = Machine({
  id: 'roomlights',
  initial: 'nolights',
  states: {
    nolights: {
      on: {
        p1: 'l1',
        p2: 'l1'
      }
    },
    l1: {
      on: {
        p1: 'l2',
        p2: 'l3'
      }
    },
    l2: {
      on: {
        p1: 'nolights',
        p2: 'nolights'
      }
    },
    l3: {
      on: {
        p1: 'nolights',
        p2: 'nolights'
      }
    },
  }
})

Теперь мы можем создать сервис и отправлять ему сообщения:

const toggleService = interpret(machine).start();
toggleService.send('p1').value //'l1'
toggleService.send('p1').value //'l2'
toggleService.send('p1').value //'nolights'

Одна вещь, которую мы здесь упускаем, – это то, как мы что-то делаем когда переходим в новое состояние. Это делается с помощью действий, которые мы определяем во втором параметре объекта, который мы передаем функции Machine() factory:

const machine = Machine({
  id: 'roomlights',
  initial: 'nolights',
  states: {
    nolights: {
      on: {
        p1: {
          target: 'l1',
          actions: 'turnOnL1'
        },
        p2: {
          target: 'l1',
          actions: 'turnOnL1'
        }
      }
    },
    l1: {
      on: {
        p1: {
          target: 'l2',
          actions: 'turnOnL2'
        },
        p2: {
          target: 'l3',
          actions: 'turnOnL3'
        }
      }
    },
    l2: {
      on: {
        p1: {
          target: 'nolights',
          actions: ['turnOffAll']
        },
        p2: {
          target: 'nolights',
          actions: ['turnOffAll']
        }
      }
    },
    l3: {
      on: {
        p1: {
          target: 'nolights',
          actions: 'turnOffAll'
        },
        p2: {
          target: 'nolights',
          actions: 'turnOffAll'
        }
      }
    },
  }
}, {
  actions: {
    turnOnL1: (context, event) => {
      console.log('turnOnL1')
    },
    turnOnL2: (context, event) => {
      console.log('turnOnL2')
    },
    turnOnL3: (context, event) => {
      console.log('turnOnL3')
    },
    turnOffAll: (context, event) => {
      console.log('turnOffAll')
    }
  }
})

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

Мы можем выполнить несколько действий, передав массив строк вместо строки.

И вы также можете определить действие (действия) непосредственно в свойстве действия вместо “централизации” их в отдельный объект:

const machine = Machine({
  id: 'roomlights',
  initial: 'nolights',
  states: {
    nolights: {
      on: {
        p1: {
          target: 'l1',
          actions: (context, event) => {
            console.log('turnOnL1')
          },
          ...

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

Вот и все для этого урока. Я рекомендую вам ознакомиться с Документами штата для более продвинутого использования State, но это только начало.

Оригинал: “https://flaviocopes.com/xstate/”