React: Suporte para ouvintes de eventos passivos

Criado em 7 abr. 2016  ·  62Comentários  ·  Fonte: facebook/react

https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md

Seria bom ter tudo passivo por padrão e ativar apenas quando necessário. Por exemplo, você pode ouvir os eventos de entrada de texto, mas apenas evitar o padrão ou usar o comportamento controlado quando tiver ouvintes ativos.

Da mesma forma, poderíamos unificar isso com o modelo de threading do React Native. Por exemplo, uma coisa que poderíamos fazer é bloquear de forma síncrona o thread da IU quando houver ouvintes ativos, como manipular pressionamentos de tecla.

cc @vjeux @ide

DOM React Core Team Big Picture Feature Request

Comentários muito úteis

Acabei de receber um aviso no cromo sobre como lidar com o evento wheel, que poderia ser otimizado se fosse registrado como um manipulador de eventos passivo. Portanto, ter isso no React seria ótimo!

Todos 62 comentários

Isso pousou no Chrome 51. Existe algum plano atualizado para oferecer suporte a isso no React? : O

Como isso é possível se o React tem apenas um ouvinte de evento no documento e delega para outros?
@sebmarkbage

Qual é o status atual do problema com eventos passivos?

Acabei de receber um aviso no cromo sobre como lidar com o evento wheel, que poderia ser otimizado se fosse registrado como um manipulador de eventos passivo. Portanto, ter isso no React seria ótimo!

Você também vai querer lidar com opções arbitrárias, como once que já chegou ao Firefox todas as noites: https://twitter.com/mozhacks/status/758763803991474176. Lista completa: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

FWIW, o Facebook ouve eventos de roda ativos para bloquear a rolagem externa quando as barras laterais ou janelas de bate-papo são roladas. Não podemos implementar a IU sem ela. Ainda queremos oferecer suporte a isso como uma opção, mas o espaço do problema ainda está incompleto, então pode haver soluções alternativas para este problema que não envolvam ouvintes passivos de eventos. Portanto, ainda é um espaço de design ativo.

É importante manter os ouvintes ativos e adicionar outros passivos de suporte.
Em aplicativos de desktop você não vê nenhuma diferença, mas em aplicativos móveis, ouvintes de rolagem passivos proporcionam um grande aumento de velocidade.

Pequena sugestão:

<SomeElement
  onScroll={this.onScrollThatCallsPreventDefault}
  onScrollPassive={this.onScrollThatJustListens}
  ...this.props
/>

@romulof sim, é assim que você registra eventos na fase de captura também

<SomeElement
  onClick={this.onClick}
  onClickCapture={this.onClickCapture}
  onScrollPassive={this.onScrollPassive}
/>

portanto, imagino que essa também seja a API adequada para oferecer suporte a eventos passivos.

Nota lateral: uma questão complicada é - como você registraria eventos passivos para a fase de captura? Suponho que isso não seja possível, devido à natureza dos eventos passivos. Já que eles nem mesmo podem chamar event.preventDefault() , então provavelmente isso não é um problema.

@radubrehar , onScrollCapturePassive parece com a Bíblia inteira em um estojo de camelo.

:) Não é o caso, já que não há eventos passivos na fase de captura.

Claro que não faz sentido, mas não contaria com isso. Existem também outros tipos de associação de eventos, como once .

Outra sugestão:

<SomeElement
  onScroll={this.onScrollThatCallsPreventDefault}
/>
<SomePassiveElement
  onScroll={{
    passive: true,
    capture: true,
    handler: this.onScrollThatJustListens,
  }}
/>

Desta forma, o React teria que detectar se o manipulador de eventos é uma função (ligação normal) ou um objeto contendo opções de ligação e a função do manipulador.

Acho que a abordagem do objeto com opções faz mais sentido do que onFooPassive , uma vez que existem outras opções que podem ser necessárias. Se combinado com a sugestão de @sebmarkbage de que os eventos devem ser passivos por padrão, isso provavelmente não seria muito complicado.

Outra abordagem que vem à mente seria anexar propriedades ao manipulador de eventos para permitir que eles optem pelo modo passivo (ou alternem outras opções). Algo assim:

class Foo extends React.Component {
  constructor() {
    this.handleScroll = this.handleScroll.bind(this);
    this.handleScroll.passive = false;
  }

  handleScroll() {
    ...
  }

  render() {
    return <div onScroll={this.handleScroll} />;
  }
}

Em teoria, isso funcionaria muito bem com decoradores, uma vez que pousassem.

Pensando um pouco mais nisso, acho que seria melhor adicionar uma propriedade de opções de evento à função, ao invés de opções individuais. Isso permitiria ao React se preocupar apenas com uma propriedade, em vez de potencialmente com muitas. Então, para ajustar meu exemplo acima:

class Foo extends React.Component {
  constructor() {
    this.handleScroll = this.handleScroll.bind(this);
    this.handleScroll.options = { passive: false };
  }

  handleScroll() {
    ...
  }

  render() {
    return <div onScroll={this.handleScroll} />;
  }
}

Outro pensamento que me ocorreu é como isso poderia parecer se modificássemos a sintaxe JSX de uma forma que permitisse que essas opções fossem transmitidas por meio do JSX. Aqui está um exemplo aleatório em que não pensei muito:

return <div onScroll={this.handleScroll, { passive: false }} />;

Também estive pensando se os eventos deveriam ser passivos por padrão ou não, e estou um pouco em dúvida. Por um lado, isso certamente seria bom para eventos como manipuladores de rolagem, mas temo que causaria muita turbulência e comportamento inesperado para muitos manipuladores de clique. Poderíamos fazer com que alguns eventos sejam passivos por padrão e outros não, mas isso provavelmente acabaria sendo confuso para as pessoas, então provavelmente não é uma boa ideia.

Dessa forma, é muito semelhante ao que propus anteriormente, sem modificar a sintaxe JSX.

return <div onScroll={{ handler: this.handleScroll, passive: true }} />;

E a documentação seria direta:

div.propTypes = {
  ...
  onScroll: React.PropTypes.oneOf([
    React.PropTypes.func,
    React.PropTypes.shape({
      handler: React.PropTypes.func.isRequired,
      capture: React.PropTypes.bool,
      passive: React.PropTypes.bool,
      once: React.PropTypes.bool,
    }),
};

Os eventos de reação são passivos por padrão? Parece ser assim para eventos de toque, pelo menos. Não sou capaz de preventDefault menos que recorra aos ouvintes de eventos de nível de documento vanilla.

Os manipuladores

Não estou muito familiarizado com os detalhes de implementação, mas sei que preventDefault funciona pelo menos _ desde que os manipuladores que você está evitando também sejam manipuladores de evento React_. Essa tem sido minha experiência, de qualquer maneira.

Com stopPropagation é mais provável que você esteja sem sorte (por exemplo, você tem um ouvinte de cliques document que não pode ser vinculado ao React e deseja evitar borbulhar se clicar dentro um determinado elemento). Nesse caso, você pode usar:

function stopPropagation (e) {
  e.stopPropagation();
  e.nativeEvent.stopImmediatePropagation();
}

[[MDN] (https://developer.mozilla.org/en-US/docs/Web/API/Event/stopImmediatePropagation)]

Isso saiu um pouco do tópico principal, mas a resposta curta é que o React não usa eventos passivos, eles apenas às vezes são tratados em uma ordem estranha.

@joshjg @ benwiley4000 @gaearon Recentemente, a equipe do Chrome mudou sua abordagem para eventos de toque em nível de documento, tornando-os passivos por padrão. E como o React anexa eventos no nível do documento, você obtém esse novo comportamento.

Consulte https://www.chromestatus.com/features/5093566007214080

Isso mudou indiretamente a maneira como o React se comporta - suponho que o React não menciona explicitamente passive: false ao anexar eventos - daí a mudança no comportamento.

Acabei de bater nisto também - então você precisa registrar eventos de toque manualmente, com addEventListener

Observe que a intervenção passiva por padrão do Chrome se aplica apenas a touchstart e touchmove , não a wheel . Portanto, um evento wheel sem um {passive: true} explícito ainda forçará a rolagem síncrona, para rolagem com a roda do mouse e com dois dedos no trackpad. (Eu escrevi uma postagem no blog sobre algumas das sutilezas aqui.)

Além disso, nós (a equipe do Edge) não temos a intenção de implementar a mesma intervenção, portanto, quando enviamos ouvintes de eventos passivos, você ainda deseja especificar explicitamente {passive: true} .

Para sua informação, comecei a seguir o caminho passivo: falso para evitar que o corpo role no celular quando há uma div de rolagem, mas é um pouco pesado usar preventDefault () para bloquear a rolagem. Eu poderia adicionar e remover o manipulador, dependendo se o div estiver presente, ou retornar para uma abordagem body.height = 100%. A correção de body.height parece um pouco estranha, mas eu não precisaria de passiva: falsa de jeito nenhum.

Meu caso de uso é que gostaria de usar o método event.preventDefault() para evitar a rolagem do contêiner quando o usuário está arrastando um elemento dentro dele.

Para isso, preciso registrar o ouvinte de evento como não passivo (passivo: falso).
Como os navegadores estão mudando para passivos: verdadeiro por padrão, gostaria de poder fazer o oposto

Infelizmente, não posso usar o estilo touch-action: none; porque ele está sendo aplicado após o início do toque e provavelmente é por isso que não tem nenhum efeito.

Realmente vai ser um problema muito em breve, estou surpreso que em dois anos nenhuma solução foi encontrada. E criar ouvintes de eventos manualmente é um antipadrão no React.

E se isso cria mudanças significativas, então que seja. Posso estar perdendo uma parte da história.

Gosto da nova assinatura do ouvinte de evento proposta por @romulof em https://github.com/facebook/react/issues/6436#issuecomment -254331351.
Além de corrigir o problema descrito aqui, seria possível especificar outras EventListenerOptions, como once

Acabei de encontrar esse problema. Eu tenho uma tela onde o usuário pode desenhar. Ao desenhar no Android, às vezes ele 'puxa para atualizar' em vez de fazer uma pincelada. Isso mostra que é um problema do mundo real. Evitarei onTouch{Start,Move,End} por enquanto e usarei manualmente addEventListener como uma solução alternativa.

Eu gosto muito da abordagem sugerida por @romulof . Parece que a solução também não requer mudanças significativas.

@bobvanderlinden Adicionar touch-action: none; ao seu estilo de tela deve funcionar para você, realmente brilha para esse caso de uso. Os outros valores possíveis também podem ser bastante convenientes.

No entanto, como @ piotr-cz apontou, touch-action não resolve universalmente esse problema de eventos passivos como um todo. Também estou tendo o mesmo problema de impedir que um contêiner role enquanto arrasta um elemento filho. Todas as soluções alternativas são bastante hacky e aumentam o débito técnico.

Infelizmente, a presença de qualquer ouvinte não passivo pode causar trepidação significativa, mesmo se não houver manipuladores de userland realmente conectados a ele. O Chrome irá informá-lo sobre isso em verbose níveis de log: [Violation] Handling of 'wheel' input event was delayed for 194 ms due to main thread being busy. Consider marking event handler as 'passive' to make the page more responsive. (este é o manipulador de nível superior adicionado por https://github.com/facebook/react/blob/92b7b172cce9958b846844f0b46fd7b ppc5140d/packages/ react-dom / src / events / ReactDOMEventListener.js # L155)

@romulof @lencioni @radubrehar Você está ciente do fato de que o sinalizador passivo não se destina ao uso em ouvintes de evento de rolagem? Deve ser usado em eventos como touchmove etc. para não interferir no desempenho de rolagem do navegador. Seus exemplos são muito confusos para mim.

Definir passivo não é importante para o evento de rolagem básico, pois ele não pode ser cancelado, portanto, seu ouvinte não pode bloquear a renderização da página de qualquer maneira.

Fonte: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners

Informações adicionais: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md

Sim, estou ciente disso agora. Eu estava mal informado quando escrevi meu anterior
comentários. Obrigado por esclarecer!

Na quarta-feira, 6 de dezembro de 2017, 8:25 AM Martin Hofmann [email protected]
escreveu:

@romulof https://github.com/romulof @lencioni
https://github.com/lencioni @radubrehar https://github.com/radubrehar
Você está ciente do fato de que o sinalizador passivo não se destina ao uso em
ouvintes de eventos de rolagem? Deve ser usado em eventos como touchmove etc.
para não interferir no desempenho de rolagem do navegador. Seus exemplos
são altamente confusos para mim.

Definir passivo não é importante para o evento de rolagem básico, pois não pode
ser cancelado, para que seu ouvinte não possa bloquear a renderização da página de qualquer maneira.

Fonte:
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners

Informação adicional:
https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md

-
Você está recebendo isso porque foi mencionado.

Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/facebook/react/issues/6436#issuecomment-349691618 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AAL7zgbNCpHNui-TX7r2FYdhVxZfdBX8ks5s9r_4gaJpZM4ICWsW
.

@ el-moalo-loco, tenho quase certeza de que li alguma documentação no site do Google Developers sobre o uso de ouvintes passivos para eventos de rolagem para melhorar o desempenho. Devo ter interpretado mal ou algo mudou ao longo do caminho. Enfim, muito obrigado pelo esclarecimento!

@romulof @lencioni @ el-moalo-loco https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners
Os ouvintes de roda ainda devem ser passivos, mesmo que os ouvintes de rolagem não precisem ser. Acho que pode ser a fonte de confusão.

@sebmarkbage O que você acha? Esse suporte de ouvinte de evento passivo chegará ao React algum dia?

Oi,

Eu só tive que adicionar um ouvinte de evento ativo em componentDidMount assim:

global.addEventListener("touchstart", this.touchStart(), { passive: false })

para que eu possa chamar e.preventDefault() para parar a rolagem padrão do Chrome e mover um elemento em touchStart() .

A fim de saber qual elemento devo mover, tive que adicionar onTouchStart ao elemento JSX assim:

onTouchStart={this.touchStartSetElement(element)}

Em touchStartSetElement() eu defino uma propriedade de estado element que posso ler em touchStart()

Se o React fosse compatível com ouvintes de eventos ativos, isso se resumiria a uma linha.

Obrigado,

Philipp

PS: se você tentar chamar e.preventDefault() em um ouvinte de evento passivo, obterá este erro no Chrome 56:

[Intervenção] Incapaz de prevenir a falha dentro do ouvinte de evento passivo devido ao destino sendo tratado como passivo. Consulte https://www.chromestatus.com/features/5093566007214080

Eventos passivos se tornaram padrão no Chrome 56 por uma " intervenção " do Google e quebrando a web de alguma forma - mas também tornando a rolagem mais rápida.

Isso está se tornando mais um problema, pois o Safari a partir do iOS 11.3 também é padronizado como passivo, e a solução alternativa clássica de touch-action:none não é suportada lá .

Eu propus um RFC https://github.com/reactjs/rfcs/pull/28 que permitiria a criação de manipuladores ref personalizados que funcionam como adereços (ou seja, você usa propriedades como faria onClick mas em vez disso usa computado sintaxe da propriedade e o manipulador obtém informações e atualizações dos valores ref e prop). Eles podem ser usados ​​para criar bibliotecas para praticamente qualquer caso de uso avançado que você tenha.

  • Passivo, explicitamente não passivo, uma vez e eventos de captura são fáceis de fazer com ele.
  • Coisas ainda mais avançadas do que apenas o registro do manipulador de eventos podem ser feitas com eles.
  • Do ponto de vista do usuário, eles são simples de usar, basta passar o que a biblioteca fornece como uma propriedade computada para qualquer elemento. por exemplo, import {onScroll} from 'react-passive-events'; <div [onScroll]={scrollHandler} />

Não creio que esta seja a forma de registrar todos os eventos.

No entanto, em vez de criar maneiras complexas de lidar com o registro de todos os tipos possíveis de eventos (captura, passivo, etc ...), eu recomendo decidir qual deve ser o comportamento padrão para a maioria dos eventos (passivos ou não passivos) e usar esses registros adereços para lidar com casos de uso mais avançados.

Todos os eventos de toque agora são passivos por padrão no iOS 11.3. Assim, chamar event.preventDefault () em qualquer manipulador de eventos de toque agora não é eficaz 😢

https://codesandbox.io/s/l4kpy569ol

Sem sermos capazes de forçar manipuladores de eventos não passivos, estamos tendo dificuldade em trabalhar com as alterações do iOS 11.3 https://github.com/atlassian/react-beautiful-dnd/issues/413

Vim ver como o Vue.js estava lidando com isso e gostei bastante da abordagem deles :

<!-- the click event's propagation will be stopped -->
<a v-on:click.stop="doThis"></a>

<!-- the submit event will no longer reload the page -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- modifiers can be chained -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- just the modifier -->
<form v-on:submit.prevent></form>

Existe esta lista de modificadores:

  • .Pare
  • .evitar
  • .capturar
  • .auto
  • .uma vez
  • .passiva

E você pode compor eventos da maneira que quiser. Talvez isso possa ajudar como inspiração para esse problema.

@KeitIG Estou trabalhando em um fork do Vue-loader projetado para React

https://github.com/stalniy/react-webpack-loader

Isso me deixa triste

selection_028

Não tenho certeza se isso já foi sugerido - ou se faz algum sentido para outra pessoa além de mim, no entanto:

onTouchStart={listener} 

para

onTouchStart={listener, options}

tornaria opções de passagem como { passive, true, once: true } uma maneira natural de fazê-lo e também corresponderia ao esquema addEventListener .

Seguir em direção @phaistonian removeria a necessidade de qualquer um dos manipuladores onEventNameCapture que existem hoje

@alexreardon Eu realmente acho que não é uma opção, já que em js simples, listener, options é uma expressão avaliada em options , então a construção acima não é o que você pretende que seja. Isso exigiria alterar a maneira como jsx compila para js e seria uma alteração significativa. Duvido que a equipe de reação seguiria esse caminho.

Opiniões?

Por ser uma expressão, poderia ser feito de outra forma com o mesmo intuito.

Provavelmente, existem muitas opções. As opções incluem:

import {handler} from 'React';

onTouchStart={handler(listener, options)}
onTouchStart={{listener, options}}

ou

onTouchStart={[listener, options]}

ou

onTouchStart={listener} onTouchStartOptions={options}

Gosto mais da ideia de passar um objeto. De qualquer forma, isso precisa de uma solução.

já existe uma solução agora?

já existe uma solução agora?

Seus addEventListener clássicos e removeEventListener em componentDidMount e componentWillUnmount respectivamente.

Sim, isso é uma merda.

E o que você acha de resolver isso usando um gancho?

...
const onClickPassive = useEventListener((e) => {
 console.log('passive event')
}, { passive: true })

return (
  <button onClick={onClickPassive}>Click me</button>
)

@ ara4n usar gancho é bom, mas ainda é necessária uma solução clássica para reação sem gancho

@sebmarkbage alguma atualização sobre isso? O Chrome acabou de enviar isso e quebrou nosso aplicativo.

https://www.chromestatus.com/features/6662647093133312

[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/6662647093133312
Acontece novamente, quando eu tentei bloquear o comportamento de rolagem padrão em onWheel listener de evento

@kychanbi O mesmo para mim, mas só encontro esse erro no Windows Chrome.

@kychanbi Oh, é um recurso do Chrome 73 que trata ouvintes de evento Wheel / Mousewheel no nível do documento como passivos

Você pode usar a propriedade css em seu contêiner de componente div touch-action: nenhum

.container {
ação de toque: nenhuma;
}

Parece que
Eu finalmente resolvi usando javascript nativo
element.addEventListener("wheel", eventHandler);

Um pequeno trecho para ajudar aqueles que enfrentam esse problema:

import React, { useRef, useEffect } from 'react'

const BlockPageScroll = ({ children }) => {
  const scrollRef = useRef(null)
  useEffect(() => {
    const scrollEl = scrollRef.current
    scrollEl.addEventListener('wheel', stopScroll)
    return () => scrollEl.removeEventListener('wheel', stopScroll)
  }, [])
  const stopScroll = e => e.preventDefault()
  return (
    <div ref={scrollRef}>
      {children}
    </div>
  )
}

const Main = () => (
  <BlockPageScroll>
    <div>Scrolling here will only be targeted to inner elements</div>
  </BlockPageScroll>
)

@madcher

De alguma forma, onWheel props não funciona com css touch-action: none;

    componentRef = React.createRef(null);
    handleWheel = (e) => {
      e.preventDefault();
    }
    render() {
      <Container style={{ touchAction: 'none' }} onWheel={this.handleWheel}>
        ...
      </Container>
    }

ainda recebendo este erro:
[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See <URL>

Versão de Trabalho

alternativa à solução de ganchos de @markpradhan , se ainda estiver usando o componente de estilo antigo, você pode fazer o seguinte:

    componentRef = React.createRef();
    handleWheel = (e) => {
      e.preventDefault();
    }
    componentDidMount() {
      if (this.componentRef.current) {
        this.componentRef.current.addEventListener('wheel', this.handleWheel);
      }
    }
    componentWillUnmount() {
      if (this.componentRef.current) {
        this.componentRef.current.removeEventListener('wheel', this.handleWheel);
      }
    }
    render() {
      <Container ref={this.componentRef}>...</Container>
    }

@Fonger Provavelmente devido a # 14856

Algo assim foi sugerido, mas aqui estão mais algumas idéias.

function MyComponent() {
  function onScroll(event) { /* ... */ }
  onScroll.options = {capture, passive, ...};
  return <div onScroll={onScroll} />;
}

Este permitiria que você optasse facilmente por eventos passivos ou capturasse eventos sem a necessidade de uma alteração significativa. No entanto, fiquei intrigado com a ideia de um ouvinte de evento passivo por padrão. Lembro-me de que o preventDefault é um grande obstáculo (entre outros) que bloqueia a execução do React em um trabalhador.

js function MyComponent() { function onScroll(event) { /* ... */ } onScroll.shouldPreventDefault = (event): boolean => { // some logic to decide if preventDefault() should be called. } onScroll.shouldStopPropagation = (event): boolean => { // some logic to decide if stopPropagation() should be called. } return <div onScroll={onScroll} />; }
Seria difícil garantir que isso não se tornasse uma alteração importante, mas se isso fosse aplicado, todo o código para decidir se um evento precisava ser preventDefault ed seria isolado no código e o React seria capaz de execute apenas essa parte no thread principal e execute todo o resto em um trabalhador separado ou de forma assíncrona.

Até que isso seja resolvido, acho que seria melhor se as referências a event.preventDefault() fossem removidas dos documentos ou pelo menos marcadas com um aviso sobre a incapacidade do Chrome de preventDefault em eventos passivos.

Eu me pergunto sobre as implicações da mudança da delegação de eventos do React v17. O Lighthouse tem uma regra https://web.dev/uses-passive-event-listeners/ que testa contra eventos não passivos.

Anteriormente, <div onTouchStart /> seria registrado no documento, que é passivo por padrão . No entanto, com o React v17, o evento é registrado na raiz da árvore do React, que não é mais passiva sem solicitá-lo especificamente.

Reprodução: https://codesandbox.io/s/material-demo-forked-e2u72?file=/demo.js , ao vivo: https://csb-e2u72.netlify.app/

Capture d’écran 2020-08-19 à 16 11 31

Sim. Parece preocupante. Vou registrar um novo problema.

Arquivado https://github.com/facebook/react/issues/19651 para discussão do React 17.

Esta página foi útil?
0 / 5 - 0 avaliações