https://a.storyblok.com/f/270183/40160/e632ba3eb2/blog_xstate_videoapi_1200x600.png

Aprenda e aplique o XState com o Vonage Video

Publicado em May 4, 2021

Tempo de leitura: 14 minutos

Nos últimos meses, tenho ouvido falar mais sobre máquinas de estados usadas no desenvolvimento de front-end. A ideia por trás de uma máquina de estados é que ela possui apenas um número finito de estados e só pode estar em um estado por vez. Conceitualmente, isso faz todo o sentido para o desenvolvimento de aplicativos — há apenas um certo número de estados disponíveis.

O conceito de máquinas de estados e diagramas de estados não é novo, nem tem origem no desenvolvimento front-end. Trata-se de um modelo matemático, utilizado em muitas coisas ao nosso redor. Por exemplo, uma luz pode ser OFF ou ON. É possível descrever qualquer coisa com uma máquina de estados, mesmo que este seja um exemplo simples.

On and off states of lightbulbOn and off states of lightbulb

Introdução ao XState

O uso de uma máquina de estados no desenvolvimento de front-end tornou-se muito menos complicado com a criação do pacote XState. O XState nos ajuda a definir máquinas de estados, criar eventos e efeitos e controlar todo o fluxo da aplicação. O XState utiliza métodos e objetos JavaScript para descrever a máquina de estados.

O exemplo da lâmpada mencionado acima seria escrito da seguinte forma:

const lightBulb = Machine({
  id: 'lightBulb',
  initial: 'off',
  states: {
    off: {
      on: {
        TURN_ON: 'on'
      }
    },
    on: {
      on: {
        TURN_OFF: 'off',
      }
    }
  }
});

A máquina de estados definida aqui mostra os dois estados da lâmpada, OFF e ON, bem como as transições a partir dos eventos TURN_ON e TURN_OFF.

O objeto em si não é muito complexo de ler, mas, à medida que a máquina de estados se torna mais complexa, pode ficar mais difícil de entender. O XState criou uma ferramenta para ajudar nisso — o visualizador do XState.

Usar o visualizador XState ajuda a entender como as máquinas de estados funcionam e como interagem entre si, por isso é divertido brincar com elas. Se você quiser dar uma olhada no código da máquina, também pode clicar no botão “código” para conferir.

Criação de um gráfico de estados de Video da Vonage

Quando comecei a estudar máquinas de estados e o XState, meu objetivo geral era criar um aplicativo semelhante ao Google Meet usando o Vonage Video. O aplicativo permitiria que um usuário criasse uma sala de reunião, compartilhasse a URL e realizasse uma reunião com várias transmissões. Para chegar a esse ponto, tive que aprender alguns dos diversos Concepts relacionados a diagramas de estados e como representá-los no XState.

Descobri que analisar os possíveis estados de aplicação não é uma tarefa fácil. Há muitas possibilidades a serem exploradas e, no fim das contas, encontrar a solução certa exige um pouco de tentativa e erro.

O restante deste artigo abordará alguns conceitos básicos e apresentará a criação de uma visualização em diagrama de estados que simulará a futura máquina de estados. Também fornecerei alguns links e recursos adicionais ao longo do texto para que você possa explorar por conta própria.

Estados e nós de estado

A estado é uma representação de uma máquina em um determinado momento. Esse momento pode ser definido e, em seguida, transformado em um nó de estado no XState, registrado como uma configuração.

No meu aplicativo Vonage Video, existem algumas soluções possíveis para isso, mas descobri que descrever os estados da forma mais simples possível é a melhor maneira de chegar a um resultado útil.

Criando uma máquina segue o seguinte padrão:

const machine = Machine(state_nodes, options)

Pensando em uma máquina de estados de Video, existem dois estados compostos — connected e disconnected.

Dois nós de estado podem parecer uma simplificação excessiva, mas, após algumas tentativas e erros, percebe-se que existem apenas dois estados. Cada um desses estados, no entanto, é mais complexo do que um nó atômico (sem filhos). Em vez de criar todos os estados possíveis no nível superior, o XState nos ajuda a nos organizar por meio de nós de estado hierárquicos e paralelos.

Nós de estado hierárquicos

O XState oferece a opção de criar estados aninhados chamados hierarchical nós de estado. Ao iniciar a máquina pela primeira vez, podemos configurá-la para idle primeiro, já que a máquina estará pronta, mas sem realizar nenhuma ação. Por que não criar simplesmente outro nó de estado atômico de nível superior?

A adição de estados ao nível superior é chamada de “explosão de estados” e é um efeito colateral típico das máquinas de estados finitos. Como o Vonage Video ainda está, tecnicamente, disconnected, o aninhamento idle faz sentido, já que o Video está tanto desconectado quanto inativo. Outro disconnected subestado deve ser ready. O disconnected.ready estado ocorreria logo antes de passar para o connected estado. A máquina de estados também possui um nó de estado entre idle e ready para que tudo fique configurado. Esse estado intermediário pode ser chamado de init fase.

A máquina de estados agora ficaria assim:

É importante observar que, no momento, não temos como nos deslocar entre os dois nós. Abordaremos eventos e ações daqui a pouco.

Nós de estado paralelos

Um parallel nó de estado permite que o aplicativo esteja em todos os seus subestados ao mesmo tempo. A máquina de estados do Vonage Video é altamente orientada a eventos, por isso precisamos gerenciar vários estados simultaneamente.

Para especificar que um nó de estado é paralelo, usamos type:parallel na configuração. Após a transição para connected, ocorrerão três estados paralelos — session, publishere subscribers. Cada um desses estados definirá eventos e ouvintes de eventos para controlar as respostas do serviço Vonage Video.

A visualização resultante fica assim:

Com esses estados principais, podemos controlar o que nosso aplicativo exibe em determinados momentos. No momento, porém, não conseguimos alternar entre os estados. Vamos dar uma olhada nos eventos e nas transições.

Eventos e Transições

Como um nó de estado é apenas a configuração de um estado específico, não há, por natureza, uma maneira de passar de um estado para outro sem declarar isso no nó de estado.

Cada nó fica à espera de um evento evento para transição para o próximo estado. No exemplo da lâmpada, o TURN_ON evento “sent” instrui a máquina a fazer a transição para on.

As transições ocorrem apenas entre nós de nível superior e dentro de nós hierárquicos. Não é permitido que nós paralelos realizem transições entre si. Para o nosso aplicativo Vonage Video, isso significa o seguinte:

  1. Quando a página estiver pronta, podemos enviar um START evento. Esse evento fará a transição do estado para disconnected.init.

  2. O disconnected.init estado será desativado assim que o VIDEO_ELEMENT_CREATED evento for disparado.

  3. Assim que chegarmos a disconnected.ready, podemos permitir que o usuário se conecte, enviando o CONNECT evento e fazer a transição para connected.

  4. Se o aplicativo fosse aprovado no DISCONNECT evento, a máquina de estados se desconectaria.

A declaração de um evento e de uma transição no XState segue o seguinte padrão:

on: {
  EVENT_DESCRIPTOR: 'nextState'
}

Você também pode adicionar ações específicas à transição. Recomendo que você leia as seções sobre transições internas e externas na documentação. Elas abordam com grande detalhe os vários tipos de transições possíveis.

Transições controladas

Você deve ter notado que uma das transições possui um cond nó na transição. Essa condição é o que se chama de guarded transição. As transições condicionais ajudam a impedir que a máquina passe para um estado que não seja permitido com base em determinadas condições. Neste caso, não quero fazer a transição para ready até que o token e o elemento de Video tenham sido criados.

on: {
  'VIDEO_ELEMENT_CREATED': {
    target: 'ready',
    cond: 'checkToken'
  }
}

A condição de guarda checkToken é uma referência nomeada ao objeto `guards` no parâmetro `options` enviado à máquina:

const video = Machine(
  state_nodes,{
  guards: {
    checkToken: () => true
  }
});

Contexto

Para ser mais útil a uma aplicação, nossa máquina de estados precisará de um estado com duração mais longa, chamado de extended state, ou context. O objeto de contexto é atualizado por meio de vários efeitos com o assign() método.

Por falar em ações, vamos dar uma olhada nelas agora e concluir o restante do esboço.

Ações e Serviços

Existem os chamados “efeitos colaterais” nas máquinas de estados, que o XState classifica em uma das duas categorias a seguir:

  1. "Disparar e esquecer" — quando o efeito não envia eventos

  2. Invocado — quando é necessário enviar eventos

Ações

Ações são efeitos únicos e tendem a ser um dos efeitos mais comuns na máquina de estados de Video. Você pode usar ações ao entrar ou sair de um nó, ou durante uma transição. Entender a ordem das ações é extremamente importante.

Um excelente recurso para aprender a ordem de ação (e todos os tópicos sobre o XState) é um Video publicado por @kyleshvlin no site Egghead.io. Ele me ajudou a entender como as ações são acionadas.

A maior parte das ações dessa máquina gira em torno da atualização do contexto como uma transição. Quando os eventos são acionados, podemos usar a ação para executar o assign() método:

on: {
  'SOME_EVENT': {
      actions: assign({'someContext': (ctx, e) => e.someValue})
  } 
}

Serviços

Serviços invocados são o outro efeito principal na máquina de estados de Video. Para usar promessas e ouvintes de eventos, é preciso invocar um serviço. Esse conceito foi, de longe, o mais difícil para mim de entender. A principal dificuldade que tive de superar foi entender que um serviço invocado é interrompido quando o estado é encerrado. Se você fizer a transição muito rapidamente, sua promessa ou callback desaparecerá.

Existem dois serviços principais que estou utilizando na máquina de estados de Video: promessas e callbacks. O invoke promises serviço permite que a máquina de estados use uma promessa para resolver ou rejeitar e, então, agir de acordo. Usei isso para interagir com o servidor de forma assíncrona e, em seguida, atualizar o contexto quando concluído.

A assinatura da função das promessas invocadas é a seguinte:

src: (context, event) => new Promise((resolve, reject) => {
  if (event.error) reject('Rejected')
  resolve('Resolved')
}),
onDone: {/*success transition*/}
onError: {/*error transition*/}

A segunda parte, e provavelmente a mais importante desta máquina, é a invoked callbacks. A arquitetura do Vonage Video depende fortemente de eventos e ouvintes de eventos. Em uma máquina de estados, eles são configurados por meio de um callback. Vou mostrar um exemplo:

invoke: {
  id: 'initPublisher',
  src: (ctx) => (cb) => {
    let publisher = initPublisher(pubOptions);
    publisher.on('videoElementCreated', (e) => {
      cb({ type: 'VIDEO_ELEMENT_CREATED', publisher: publisher })
    })
    return () => publisher.off('videoElementCreated');
  }
}

A assinatura da função de um callback invocado é a seguinte:

src: (context, event) => (callback, onReceive) => {
  
  callback('EVENT');
  onReceive(event) => { callback('OTHER_EVENT') };

  return () => cleanup()
};

Com o uso de ações e serviços, nossa máquina de estados do Vonage Video agora tem a seguinte aparência:

Não se esqueça de explorar o site e clicar na code aba para ver como a máquina de estados se apresenta como uma configuração.

Recursos e conclusão

Ok — então você chegou até aqui.

I'm proud of you!I'm proud of you!

Se esta é a primeira vez que você está conhecendo o XState, pode parecer muita informação de uma só vez. Eu ainda estou explorando e aprendendo novas maneiras de fazer as coisas. Este post é apenas uma pequena parte do que existe por aí. Enquanto você estiver aprendendo, aqui estão alguns recursos excelentes que vale a pena conferir

Fique à vontade para entrar em contato se tiver alguma dúvida sobre o XState; assim, podemos aprender algo novo juntos!

Compartilhar:

https://a.storyblok.com/f/270183/384x384/444c073b5e/kellyjandrews.png
Kelly J AndrewsEx-membro da equipe

Kelly J Andrews é defensora de desenvolvedores da Nexmo e vem se dedicando à informática há mais de 30 anos, tendo usado BASIC pela primeira vez aos 5 anos de idade.

Foi só ao criar sua primeira página da web, em 1997, e ao experimentar o JavaScript pela primeira vez que ele descobriu sua verdadeira vocação. Hoje, Kelly luta pelo JavaScript, pelo código testável e pela entrega rápida.

É possível encontrá-lo cantando no karaokê, fazendo truques de mágica ou torcendo pelos Cubs e pelos Fighting Irish.