Я писал о конечных автоматах в прошлом и я упомянул Государство . В этом посте я хочу представить эту популярную библиотеку 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/”