Imagine que temos um projeto em andamento, porém, precisamos adicionar uma nova funcionalidade em um de nossos componentes, hoje temos nossa árvore de componentes da seguinte maneira:

<App>
	<ComponenteA>
		<ComponenteFilhoA>
			<OutroFilho>
		</ComponenteFilhoA>
		<ComponenteFilhoB/>
		<ComponenteFilhoC/>
	</ComponenteA>
	<ComponenteB>
		<ComponenteFilhoB/>
	</ComponenteB>
	<ComponenteC/>
	<ComponenteD/>
</App>

Certo, dado nossa árvore atual, sabemos que o responsável por essa nova funcionalidade será o componente OutroFilho que é filho de ComponenteFilhoA e por sua vez é filho de ComponenteA. Além de todos esses relacionamentos entre pais e filhos, também temos os irmãos, sobrinhos, primos, tios, e por aí vai…

A nova funcionalidade criada em OutroFilho deve impactar e atualizar os componentes ComponenteFilhoB, ComponenteC e ComponenteD, como podemos resolver esse problema?

Uma das maneiras de resolver o problema, seria nosso componente App cuidar de tudo, dessa maneira, poderíamos passar via props uma função contendo a nova funcionalidade para o componente ComponenteA, que por sua vez repassaria essa props para seu filho OutroFilho. Assim, quando a função for executada, atualizaríamos o state do componente ComponenteA que por sua vez seria passado para os demais componentes que precisam daqueles dados.

Obs: No código acima, todos os componentes não estão diretamente no componente App, o mesmo apenas tem em sua declaração os componentes: ComponenteA, ComponenteB, ComponenteC e ComponenteD. Os demais componentes são filhos de filhos do componente App, apenas deixei tudo junto para ficar facilitar o entendimento e visualização da arvore final de componentes.

Beleza, será que não existe uma maneira mais elegante de resolver o problema? Sim, para isso temos a biblioteca chamada PubSubJS.

Conhecendo a PubSubJS

A PubSub é uma biblioteca muito pequena, em sua versão minificada e “gzipada” podemos contar com menos de 1kb, responsável por trabalhar com eventos, conseguimos realizar a prática do event driven programming (Programação orientada á eventos). Tudo isso é possível através de publicações (publish) e inscrições (subscribe).

A ideia do nosso cenário é: O componente OutroFilho terá sua nova funcionalidade, quando ela for executada, será disparado um evento para a aplicação e quem estiver ouvindo esse evento, poderá reagir á ele.

Instalando a biblioteca

Podemos realizar a instalação da PubSub de várias maneiras diferentes, sendo elas:

  1. Instalando via gerenciador de pacotes:

Ela pode ser instalada tanto com npm ou yarn:

npm i pubsub-js

ou

yarn add pubsub-js
  1. Também podemos utilizá-la diretamente através de suas CDN’s:
  1. Realizar o download de sua versão “taggeada” através do Github: tagged versions

  2. Antigamente também era possivel instalá-la via bower, porém, a última versão disponível através do mesmo é: 1.5.4, versão um pouco desatualizada comparada com a atual: 1.6.1.

Publicando um evento

Voltando a nossa funcionalidade, como podemos publicar um evento? O primeiro passo é importar a biblioteca dentro de onde o evento será publicado, ou seja, dentro do nosso componente OutroFilho:

import PubSub from "pubsub-js"

Agora que já temos a PubSub importada e disponível para uso, dentro da nossa função, vamos publicar um evento:

novaFuncionalidade = () => {
	PubSub.publish("NOVA_FUNCIONALIDADE")
}

Para realizar a publicação do evento, utilizamos a função publish.

No exemplo acima, apenas foi publicado um evento, sem mandar informações para o mesmo, porém, nossa funcionalidade, exige que buscamos um determinado cliente e repassamos ele para quem ouvir o evento, podemos fazer isso da seguinte maneira:

novaFuncionalidade = () => {
	const cliente = buscarCiente()
	PubSub.publish("NOVA_FUNCIONALIDADE", cliente)
}

Agora, quem ouvir nosso evento conseguirá ter acesso ao cliente encontrado.

Obs: Repare que conseguimos emitir nosso evento através de uma publicação, portanto, não fique confuso, tanto publicar quanto emitir, basicamente são as mesmas coisas dentro desse cenário.

Entendendo a função publish

A função publish pode receber dois parâmetros:

  1. O primeiro é o nosso evento/tópico que estamos publicando, em outras palavras, o nome do evento (esse nome deve ser guardado para depois ser ouvido).
  2. O segundo é alguma informação que precisamos passar para quem ouvir nosso evento.

Tópicos/eventos são publicados de forma assíncrona, dessa maneira, nossa aplicação não irá travar enquanto o evento é emitido e ouvido. A PubSub não fica acoplada (presa) em sua sincronização, assim, ela consegue ajudar a manter a nossa aplicação rodando de forma que os tópicos/eventos não irão bloquear o usuário.

Se você preferir ou precisar, também é possível publicar/emitir eventos de forma síncrona através da função publishSync, seu funcionamento é semelhante á publish apenas mudando sua forma de assíncrona para síncrona.

Recebendo evento

Legal, conseguimos publicar nosso evento, agora, precisamos de alguma maneira, dentro dos componentes ComponenteFilhoB, ComponenteC e ComponenteD, ouvir e receber o evento quando o mesmo for publicado/emitido, podemos fazer isso através da função subscribe, da seguinte maneira:

import PubSub from "pubsub-js"

componentDidMount = () => {
	PubSub.subscribe("NOVA_FUNCIONALIDADE", this.receberCliente)
}

Repare que para ouvir/receber/subescrever em um evento, devemos utilizar a função subscribe, a mesma recebe dois parâmetros:

  1. O nome do evento/tópico que estamos querendo ouvir.
  2. Uma função de callback que deverá ser executada quando o evento for emitido

Aí eu te pergunto, precisamos receber os dados do cliente que foram passados durante a emissão do evento, como podemos fazer isso? No segundo parâmetro da função subscribe, devemos retornar uma função de callback, porém, essa função pode receber dois parâmetros, sendo eles: O nome do tópico/evento que foi publicado e os dados que foram passados:

componentDidMount = () => {
	PubSub.subscribe("NOVA_FUNCIONALIDADE", (topico, cliente) => this.receberCliente(cliente))
}

Obs: O código de subscribe deve estar em todos os componentes que pretendem ouvir e receber o evento.

Saiba mais

Quando realizamos o subscribe dentro do ciclo de vida componentDidMount, a função de callback não será executada naquele momento, apenas realizamos e dizemos para a PubSub que queremos ouvir aquele evento/tópico. A função de callback apenas será executada quando o evento for emitido, ou seja, enquanto não realizamos o publish nada será feito e chamado.

Assim que a função publish for executada, a PubSub irá disparar esse evento de forma global para nossa aplicação, e todos os lugares que estiverem fazendo um subscribe para o mesmo nome do evento/tópico emitido será executada a função de callback referente á eles.

Cancelando um recebimento

Até agora vimos como emitir/receber eventos, mas, também é possível cancelar esse o recebimento.

componentDidMount = () => {
	const id = PubSub.subscribe("NOVA_FUNCIONALIDADE", (topico, cliente) => this.receberCliente(cliente))
}

Quando realizarmos o subscribe, a função devolve um id para identificar aquela inscrição, em caso de necessidade podemos cancelar o subscribe através da função unsubscribe:

componentDidMount = () => {
	const id = PubSub.subscribe("NOVA_FUNCIONALIDADE", (topico, cliente) => this.receberCliente(cliente))
	PubSub.unsubscribe(id)
}

Como você pode ver a função unsubscribe recebe o id que pretendemos cancelar, se surgir a necessidade de voltar a ouvir o evento, simplesmente realize o subscribe novamente.

Saiba mais

Também é possível cancelar todos as incrições de uma única vez, através da função clearAllSubscriptions:

PubSub.clearAllSubscriptions()

Dessa maneira nenhum evento mais será ouvido até que seja realizado o subscribe novamente.

Dica para ouvir/receber eventos

Em vez de ficar passando a mesma String tanto para emitir quanto para receber, recomenda-se que exista um arquivo de constant em nosso projeto:

const Eventos = {
	NOVA_FUNCIONALIDADE: "NOVA_FUNCIONALIDADE"
}

export { Eventos }

Dessa maneira, podemos realizar o publish e subscribe da seguinte maneira:

import { Eventos } from "constants/eventos.js"

PubSub.publish(Eventos.NOVA_FUNCIONALIDADE)
PubSub.subscribe(Eventos.NOVA_FUNCIONALIDADE)

Com isso, se um dia o nome de nosso evento precisar mudar, não precisaremos sair procurando onde o mesmo está sendo utilizado, basta modificar nossa constante de eventos.

Saiba mais

A biblioteca PubSub é utilizada e voltada para aplicações de processo único (Single process), se sua aplicação é ou tem chances de se tornar uma aplicação de multíplos processos (multi process), então eles recomendam que você faça uso de bibliotecas como redis PubSub.

No exemplo do post utilizei a PubSub dentro de uma aplicação React, porém, é possível utilizá-la em qualquer lugar que pode executar JavaScript (Browser, NodeJS, Angular, Vue, Elm, Ember, Meteor, etc…).

Conclusão

Para esse post é isso, com a PubSub conseguimos simplificar e melhorar muito nossos códigos através de emissões e inscrições de eventos, muitas bibliotecas fazem o isso desse padrão.

E aí, você já conhecia a PubSub? Não deixe de comentar, se gostou desse e outros post’s se inscreva na newsletter para receber novidades por email.

Espero que tenha gostado, até a próxima.