Reatividade com RxJS: pressione força

Implementando pressione e segure usando RxJS

Glad Chinda Blocked Unblock Seguir Seguindo 3 de janeiro

O RxJS é uma biblioteca de programação reativa para JavaScript, que aproveita sequências observáveis para compor programas assíncronos ou baseados em eventos. Como parte do projeto Reactive Extensions , a arquitetura do RxJS combina as melhores partes do padrão Observer , o padrão Iterator e a programação funcional .

Se você usou uma biblioteca de utilitários JavaScript como o Lodash antes, então você pode pensar no RxJS como o Lodash para eventos.

O RxJS não é mais uma nova biblioteca JavaScript. Na verdade, no momento em que este artigo foi escrito, a versão mais recente da biblioteca é a 6.3.3 , que é a mais recente de mais de 105 versões.

Neste tutorial, aproveitaremos a programação reativa usando o RxJS para implementar a detecção e o tratamento da pressão de força para eventos comuns de mouse DOM.

Aqui está a demonstração de imprensa de força no Code Sandbox . Navegue até o link e pressione e segure os controles de volume para ver a força pressionar em ação.

Este tutorial não deve ser usado como um substituto para um guia adequado para iniciantes do RxJS, mesmo que ele explique brevemente alguns conceitos e operadores de programação reativa.

Observáveis e operadores

Observables são o núcleo da arquitetura RxJS . Um observável pode ser comparado a um fluxo invocável de valores ou eventos que emanam de uma fonte. As fontes podem ser intervalos de tempo, solicitações AJAX, eventos DOM, etc.

Um observável:

  • é preguiçoso (ele não emite nenhum valor até que tenha sido assinado)
  • pode ter um ou mais observadores ouvindo seus valores
  • pode ser transformado em outro observável por uma cadeia de operadores

Operadores são funções puras que podem retornar um novo observável de um observável . Esse padrão possibilita a cadeia de operadores, já que um observável é sempre retornado no final.

De fato, versões mais recentes do RxJS expõem um método de instância .pipe() na classe <Observable> , que pode ser usado para encadear operadores como chamadas de função.

Um operador basicamente escuta os valores da fonte observável, implementa alguma lógica definida nos valores recebidos e retorna um novo valor de emissão observável baseado na lógica.

Força de imprensa

Forçar imprensa simplesmente se refere a um evento de imprensa DOM como keydown e mousedown , mantido por um período de tempo antes do evento de lançamento do DOM correspondente ser ativado, como keyup e mouseup nesse caso.

Em termos simples, uma imprensa de força é sinônimo de pressionar e segurar.

Existem muitas áreas nas interfaces do usuário nas quais uma força de impressão pode ser aplicável. Imagine ter um conjunto de controles de volume para um widget de player de música e você deseja aumentar o volume de 30 para 70.

Basicamente, você pode conseguir isso de duas maneiras:

  1. pressione o botão VOLUME UP várias vezes até atingir o volume desejado – esta impressora pode ser feita 40 vezes
  2. force pressione (mantenha pressionado) o botão VOLUME UP até chegar ou próximo ao volume desejado e, em seguida, ajuste até atingir o volume desejado

Aqui está uma demonstração simples desta ilustração:

Comparando várias impressoras para forçar pressione

Forçar imprensa com baunilha JavaScript

Imprensa de força de implementação com baunilha JavaScript, semelhante ao que temos acima, não é uma tarefa hercúlea. Essa implementação exigirá:

  • escutando eventos de mousedown no botão de controle de volume
  • usando setInterval() para ajustar continuamente o volume até que um evento de mouseup aconteça

Digamos que a marcação para nossos controles de volume se pareça com o seguinte:

O trecho de código a seguir mostra como a implementação da força de impressão será semelhante ao JavaScript vanilla. Por uma questão de brevidade, as implementações das funções increaseVolume() e decreaseVolume() foram omitidas:

Esta aplicação de força de imprensa usando JavaScript baunilha parece muito simples, portanto, uma biblioteca como o RxJS não parece necessária.

Uma observação rápida do snippet de código mostrará que o volume será continuamente ajustado por um valor igual em intervalos de tempo iguais até que um evento mouseup seja disparado. Esta é uma progressão linear .

No entanto, a implementação começa a se tornar complexa quando queremos um controle mais avançado sobre a força de impressão. Por exemplo, digamos que queremos alguma forma de progressão exponencial do volume. Isso significa que o volume deve estar mudando mais rapidamente para uma força maior.

Aqui está uma ilustração simples mostrando a diferença:

Uma implementação como a da progressão exponencial de volume será bastante desafiadora usando JavaScript vanilla, já que você pode ter que acompanhar quanto tempo a força pressiona para determinar a rapidez com que o volume deve mudar.

Casos como esse são mais adequados para a biblioteca RxJS. Com o RxJS, há ainda mais poder para compor sequências observáveis para lidar com tarefas assíncronas complexas.

Forçar imprensa com RxJS

Vamos em frente e reimplementar a força de pressão com progressão de volume linear usando RxJS. Aqui está como ficaria:

Uma observação cuidadosa deste snippet de código mostrará que importamos algumas funções e operadores da biblioteca RxJS. A suposição é que você já tenha o RxJS instalado como uma dependência para o seu projeto.

Existem algumas partes importantes do trecho de código que merecem destaque.

Linha 7

 const documentMouseup$ = fromEvent(document, 'mouseup'); 

A função auxiliar fromEvent cria um novo observável que emite toda vez que o evento especificado é acionado em um nó DOM.

Por exemplo, na linha acima, fromEvent cria um observável que emite um objeto de evento toda vez que um mouseup é acionado no nó do document . A função fromEvent também é usada na Line 21 para escutar eventos de mousedown em um botão de controle de volume.

Observe que o observável é armazenado em uma constante denominada documentMouseup$ . É uma prática comum anexar um $ após o nome de uma variável usada para armazenar um observável.

Linhas 9 a 15

 forcepress constante = fn => { 
temporizador de retorno (500, 100).
startWith (fn ()),
takeUntil (documentMouseup $),
mapa (fn)
);
};

A função forcepress() usa uma função de manipulador fn como seu argumento e retorna um observável. O retornável observável é criado a partir de um temporizador usando a função timer() e transformado usando uma cadeia de operadores.

Vamos dividir o código linha por linha:

 timer(500, 100) 

Essa chamada de função timer() cria um novo observável que emite um inteiro count começando de zero ( 0 ). O primeiro inteiro é emitido após 500ms 100ms e, em seguida, os inteiros subsequentes são emitidos em intervalos de 100ms .

O método .pipe() em um observável é usado para encadear operadores aplicando-os como funções regulares da esquerda para a direita.

começar com

 temporizador (500, 100) .pipe ( 
startWith (fn ())
)

O operador startWith() recebe um valor como um argumento que deve ser emitido primeiro pelo observável. Isso é útil para emitir um valor inicial de um observável.

Aqui, o operador startWith() é usado para executar o manipulador fn e emitir o valor retornado.

takeUntil

 temporizador (500, 100) .pipe ( 
takeUntil (documentMouseup $)
)

O operador takeUntil() é usado para parar de emitir valores da fonte observável com base em outro observável. Recebe um argumento observável como seu argumento. No momento em que este observável emite seu primeiro valor, nenhum valor mais é emitido da fonte observável.

Em nosso trecho de código, o documentMouseup$ observable é passado para o operador takeUntil() . Isso garante que nenhum valor seja emitido a partir do temporizador no momento em que um evento mouseup é acionado no nó do document .

mapa

 temporizador (500, 100) .pipe ( 
mapa (fn)
)

O operador map() é muito semelhante ao Array.map() para matrizes JavaScript. Ele usa uma função de mapeamento como seu argumento que recebe o valor emitido da origem observável e retorna um valor transformado.

Aqui, simplesmente passamos a função fn como a função de mapeamento para o operador map() .

Linhas 21–26

 fromEvent (botão $, 'mousedown'). pipe ( 
switchMap (evt => {
evt.preventDefault ();
força de retorno (fn);
})
).se inscrever();

Estas linhas simplesmente mapeiam o evento mousedown em um botão de controle de volume para a ação force press usando o operador switchMap() .

Ele primeiro cria um evento observável de mousedown no elemento button. Em seguida, ele usa o operador switchMap() para mapear o valor emitido para um observável interno cujos valores serão emitidos. Em nosso trecho de código, o interno observável é retornado da execução da função forcepress() .

Observe que passamos fn para a função forcepress() conforme definido. Também é muito importante notar que subscrevemos o observável usando o método subscribe() . Lembre-se de que os observáveis são preguiçosos. Se eles não estão inscritos, eles não emitem nenhum valor.

Melhorando a força

Algumas coisas podem ser feitas para melhorar a força de força usando operadores RxJS. Uma melhoria será implementar uma progressão de volume exponencial em vez da progressão linear como vimos anteriormente.

Progressão do volume exponencial

Fazer isso com o RxJS é muito simples. Vamos supor que a implementação atual de nossas funções de ajuste de volume seja assim:

Podemos modificar ligeiramente as funções de ajuste de volume para aceitar um fator de passo de volume. Essas modificações nos permitirão alcançar a progressão exponencial, como veremos em breve.

O trecho de código a seguir mostra as modificações:

Com essas modificações, podemos agora passar um factor para as funções de ajuste de volume para especificar quanto o volume deve ser ajustado. Chamar essas funções sem passar um factor simplesmente ajustará o volume um passo de cada vez.

Agora, podemos modificar a função forcepress() que criamos anteriormente da seguinte forma:

Com essa modificação, implementamos com sucesso o pressionamento de força nos botões de controle de volume com uma progressão de volume exponencial.

computedFactor

Aqui nós adicionamos uma função simples chamada computedFactor para calcular o fator de ajuste de volume. Essa função recebe um argumento inteiro n com o qual ele calcula o fator.

Estamos simplesmente computando essa expressão:

 Math.round(Math.pow(1.25 + n / 10, 1 + n / 5)); 

Aqui, estamos usando o Math.pow() para calcular progressivamente os expoentes com base no valor de n . Essa expressão pode ser modificada para se adequar à progressão exponencial necessária. Por exemplo, pode ser tão simples assim:

 Math.pow(2, n); 

Além disso, observe que estamos usando Math.round() aqui para garantir que obtenhamos um fator inteiro, pois o cálculo envolve muitos números de ponto flutuante.

Aqui está um resumo dos dez primeiros valores retornados pela função computedFactor() . Parece ser a função perfeita para computar os fatores:

 0 => Math.round (Math.pow (1.25, 1.0)) => 1 
1 => Math.round (Math.pow (1.35, 1.2)) => 1
2 => Math.round (Math.pow (1.45, 1.4)) => 2
3 => Math.round (Math.pow (1.55, 1.6)) => 2
4 => Math.round (Math.pow (1.65, 1.8)) => 2
5 => Math.round (Math.pow (1.75, 2.0)) => 3
6 => Math.round (Math.pow (1.85, 2.2)) => 4
7 => Math.round (Math.pow (1,95, 2,4)) => 5
8 => Math.round (Math.pow (2,05, 2,6)) => 6
9 => Math.round (Math.pow (2.15, 2.8)) => 9

withLatestA partir de

Uma observação cuidadosa da função forcepress() mostrará que esta linha:

 map(fn) 

foi substituído por estas linhas:

 withLatestFrom ( 
timer (1000, 500) .pipe (startWith (0))
)
map (([t, n]) => fn (fator calculado (n)))

Aqui, introduzimos outro operador withLatestFrom() com withLatestFrom() . É preciso outro observável como seu primeiro argumento. Esse operador é útil para emitir valores de vários observáveis como uma matriz de valores.

No entanto, ele só emite toda vez que a fonte observável emite, emitindo os valores mais recentes de todos os observáveis em ordem a cada vez.

Em nosso exemplo, passamos em outro observável criado com a função timer() para o operador withLatestFrom() .

O temporizador observável emite um inteiro pela primeira vez após 1000ms e posteriormente a cada 500ms . O operador startWith() é canalizado para o temporizador observável, fazendo com que ele inicie com um valor inicial de 0 .

A função de mapeador passada para o operador map() espera uma matriz como seu primeiro argumento, já que o operador withLatestFrom() emite uma matriz de valores.

Aqui está o operador do mapa novamente:

 map(([t, n]) => fn(computedFactor(n))) 

Neste trecho de código, o t representa o valor emitido pelo primeiro observável, que neste caso é a fonte observável. O n representa o valor emitido pelo segundo observável, que é o temporizador.

Finalmente, chamamos fn() como antes, só que desta vez nós passamos um fator de ajuste de volume computado derivado de chamar a função computedFactor() com n .

Agora, aqui está a comparação entre as progressões linear e exponencial mostrando a duração do aumento do volume de 0 a 100 :

Terminação de pressão de força aprimorada

Até agora, estamos encerrando a força pressionada de ajuste de volume quando um evento mouseup é disparado no nó do document . No entanto, podemos aprimorá-lo ainda mais para permitir a terminação da força de pressão quando o volume atingir qualquer um dos limites, seja 0 ou 100 .

Podemos criar uma função de operador personalizada que possamos canalizar para a fonte observável para evitar que ela emita o momento em que isso acontece:

  • um evento mouseup é acionado no nó do document
  • o volume atinge 0 ou 100

Aqui está a função de operador personalizada chamada limitVolume() :

Aqui, criamos dois observáveis do temporizador, a saber, timerUntilMouseup$ e timerWithinLimits$ que terminam com base nas duas condições que timerWithinLimits$ respectivamente.

Então nós compusemos o volumeStop$ observável dos dois observáveis usando os operadores zip() e last() para assegurar que este observável apenas emita um valor para o primeiro dos dois observáveis que são terminados.

Finalmente, usamos o takeUntil() operador na limitVolume() função de operador personalizado para garantir que a source$ observável é encerrado quando o volumeStop$ observável emite o seu primeiro valor.

Observe que limitVolume() retorna uma função que considera um argumento como observável e retorna outro observável. Essa implementação é fundamental para ser usada como um operador RxJS.

Com o operador personalizado limitVolume() , podemos agora modificar o forcepress() seguinte maneira:

Mais força de imprensa para o calendário

Muito já foi feito na implementação da força de imprensa. No entanto, vamos considerar outra demonstração de imprensa de força que envolve percorrer os meses e os anos do calendário.

Imagine que você estivesse criando um widget de calendário e desejasse que o usuário percorresse meses e anos no calendário. Isso soa como um bom caso de uso para pressionar a força.

Aqui está uma captura de tela da demonstração:

Nesta demonstração, um pequeno tempero foi adicionado à força de imprensa para ativar a detecção de chaves. Observe que sempre que a tecla SHIFT está sendo pressionada, o ciclo muda de meses para anos.

Além disso, observe que a velocidade do ciclismo ao longo dos meses é mais rápida do que a do ciclismo ao longo dos anos.

Implementar algo como isto com setTimeout() e baunilha JavaScript será bastante complexo. No entanto, é muito mais fácil com o RxJS.

O trecho de código a seguir mostra a implementação. As funções de ciclo de mês e ano foram omitidas por brevidade:

Deixarei você para descobrir como o snippet de código funciona neste exemplo. No entanto, você pode obter uma demonstração ao vivo no Code Sandbox .

Conclusão

O RxJS é uma biblioteca muito poderosa para compor eventos e sequências assíncronas. Ele pode ser usado para construir programas assíncronos complexos que não podem ser construídos facilmente usando apenas JavaScript simples.

Neste tutorial, aprendemos como implementar força aprimorada pressionando ( pressione e segure ) usando RxJS. Embora tenhamos nos concentrado na força pressionando eventos de mouse, o mesmo também pode ser implementado para eventos de teclado.

Aplauda e siga

Se você achou este artigo perspicaz, sinta-se à vontade para dar alguns aplausos se não se importar.

Você também pode me seguir no Medium ( Glad Chinda ) para artigos mais perspicazes que você pode achar úteis. Você também pode me seguir no Twitter ( @gladchinda ).

Desfrute de codificação…

Plug: LogRocket , um DVR para aplicativos da web

https://logrocket.com/signup/

LogRocket é uma ferramenta de registro de front-end que permite que você repita problemas como se eles tivessem ocorrido em seu próprio navegador. Em vez de adivinhar por que os erros ocorrem ou solicitar aos usuários capturas de tela e log dumps, o LogRocket permite que você repita a sessão para entender rapidamente o que deu errado. Ele funciona perfeitamente com qualquer aplicativo, independentemente do framework, e possui plugins para registrar o contexto adicional do Redux, Vuex e @ ngrx / store.

Além de registrar as ações e o estado do Redux, o LogRocket registra logs do console, erros de JavaScript, rastreamentos de pilha, solicitações / respostas de rede com cabeçalhos + corpos, metadados do navegador e logs personalizados. Ele também instrumenta o DOM para gravar o HTML e CSS na página, recriando vídeos com pixels perfeitos até mesmo dos aplicativos de página única mais complexos.

Experimente Grátis.

Texto original em inglês.