Por que os adereços JSX não deveriam usar funções de seta ou vincular?

103

Estou executando o lint com meu aplicativo React e recebo este erro:

error    JSX props should not use arrow functions        react/jsx-no-bind

E é aqui que estou executando a função de seta (dentro onClick):

{this.state.photos.map(tile => (
  <span key={tile.img}>
    <Checkbox
      defaultChecked={tile.checked}
      onCheck={() => this.selectPicture(tile)}
      style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
    />
    <GridTile
      title={tile.title}
      subtitle={<span>by <b>{tile.author}</b></span>}
      actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
    >
      <img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
    </GridTile>
  </span>
))}

Essa é uma prática ruim que deve ser evitada? E qual é a melhor maneira de fazer isso?

KadoBOT
fonte

Respostas:

170

Por que você não deve usar funções de seta em linha em adereços JSX

Usar funções de seta ou vinculação em JSX é uma prática ruim que prejudica o desempenho, porque a função é recriada em cada renderização.

  1. Sempre que uma função é criada, a função anterior é coletada como lixo. Renderizar muitos elementos pode criar problemas nas animações.

  2. Usar uma função de seta embutida fará com que PureComponents e componentes que usam shallowCompareno shouldComponentUpdatemétodo sejam renderizados novamente. Como a função de seta prop é recriada a cada vez, a comparação superficial irá identificá-la como uma alteração em uma prop e o componente será renderizado novamente.

Como você pode ver nos 2 exemplos a seguir - quando usamos a função de seta embutida, o <Button>componente é renderizado novamente a cada vez (o console mostra o texto do 'botão de renderização').

Exemplo 1 - PureComponent sem manipulador embutido

Exemplo 2 - PureComponent com manipulador embutido

Métodos de vinculação thissem funções de seta embutidas

  1. Vinculando o método manualmente no construtor:

    class Button extends React.Component {
      constructor(props, context) {
        super(props, context);
    
        this.cb = this.cb.bind(this);
      }
    
      cb() {
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }
  2. Vinculando um método usando os campos de classe de proposta com uma função de seta. Como esta é uma proposta de estágio 3, você precisará adicionar a predefinição do Estágio 3 ou a transformação das propriedades de classe à sua configuração de babel.

    class Button extends React.Component {
      cb = () => { // the class property is initialized with an arrow function that binds this to the class
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }

Componentes de função com callbacks internos

Quando criamos uma função interna (manipulador de eventos, por exemplo) dentro de um componente de função, a função será recriada sempre que o componente for renderizado. Se a função for passada como adereços (ou via contexto) para um componente filho ( Buttonneste caso), esse filho também será renderizado novamente.

Exemplo 1 - Componente de função com um retorno de chamada interno:

Para resolver esse problema, podemos envolver o retorno de chamada com o useCallback()gancho e definir as dependências para um array vazio.

Nota: a useStatefunção gerada aceita uma função atualizadora, que fornece o estado atual. Dessa forma, não precisamos definir o estado atual como uma dependência de useCallback.

Exemplo 2 - Componente de função com um retorno de chamada interno envolvido com useCallback:

Ori Drori
fonte
3
Como você consegue isso em componentes sem estado?
lux
4
Componentes sem estado (função) não têm this, então não há nada para vincular. Normalmente, os métodos são fornecidos por um componente inteligente de invólucro.
Ori Drori
39
@OriDrori: Como isso funciona quando você precisa passar dados no retorno de chamada? onClick={() => { onTodoClick(todo.id) }
adam-beck
4
@ adam-beck - adicione-o dentro da definição do método de retorno de chamada na classe cb() { onTodoClick(this.props.todo.id); }.
Ori Drori
2
@ adam-beck Acho que é assim que se usa useCallbackcom valor dinâmico. stackoverflow.com/questions/55006061/…
Shota Tamura
9

Isso ocorre porque uma função de seta aparentemente criará uma nova instância da função em cada renderização, se usada em uma propriedade JSX. Isso pode criar uma grande pressão no coletor de lixo e também impedir que o navegador otimize quaisquer "caminhos ativos", pois as funções serão descartadas em vez de reutilizadas.

Você pode ver toda a explicação e mais algumas informações em https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

Karl-Johan Sjögren
fonte
Não apenas isso. Criar as novas instâncias de função toda vez significa que o estado é modificado e quando o estado de um componente é modificado, ele será renderizado novamente. Como uma das principais razões para usar o React é apenas renderizar elementos que mudam, usar bindas funções de ou de seta aqui é dar um tiro no próprio pé. No entanto, não está bem documentado, especialmente no caso de trabalhar com mapmatrizes de ping dentro de listas, etc.
hippietrail
"Criar novas instâncias de função sempre significa que o estado é modificado", o que você quer dizer com isso? Não há nenhum estado em questão
apieceofbart
4

Para evitar a criação de novas funções com os mesmos argumentos, você pode memorizar o resultado da vinculação da função, aqui está um utilitário simples chamado memobindpara fazer isso: https://github.com/supnate/memobind

supNate
fonte
4

Usar funções embutidas como essa é perfeitamente normal. A regra de linting está desatualizada.

Essa regra vem de uma época em que as funções das setas não eram tão comuns e as pessoas usavam .bind (this), que costumava ser lento. O problema de desempenho foi corrigido no Chrome 49.

Preste atenção para não passar funções embutidas como adereços para um componente filho.

Ryan Florence, o autor de React Router, escreveu um ótimo artigo sobre isso:

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578

Sbaechler
fonte
Você pode mostrar como escrever um teste de unidade em componentes com funções de seta em linha?
krankuba
1
@krankuba Não é disso que se trata. Você ainda pode passar funções anônimas que não são definidas inline, mas ainda não são testáveis.
sbaechler de
-1

Você pode usar as funções de seta usando a biblioteca react-cached-handler , sem se preocupar com o desempenho da re-renderização:

Nota: Internamente, ele armazena em cache suas funções de seta pela tecla especificada, não há necessidade de se preocupar com a re-renderização!

render() {

  return <div>
  {
        this.props.photos.map(photo=>
          <Photo key={photo.url}
            onClick={this.handler(photo.url, (url) => { 
                 console.log(url) })}
          />)
   }
 </div>

}

Outras características:

  • Manipuladores nomeados
  • Lidar com eventos por funções de seta
  • Acesso à chave, argumentos personalizados e o evento original
  • Desempenho de renderização de componentes
  • Contexto personalizado para manipuladores
Ghominejad
fonte
A questão era por que não podemos usá-lo. Não como usá-lo com algum outro hack.
kapil
-1

Por que os adereços JSX não deveriam usar funções de seta ou vincular?

Principalmente, porque as funções inline podem interromper a memoização de componentes otimizados:

Tradicionalmente, as preocupações com o desempenho das funções embutidas no React têm sido relacionadas a como passar novos callbacks em cada renderização quebra shouldComponentUpdateotimizações em componentes filhos. ( docs )

É menos sobre o custo de criação de função adicional:

Os problemas de desempenho com as funções foram Function.prototype.bind corrigidos aqui e as setas são nativos ou são transpilados pelo babel para funções simples; em ambos os casos, podemos assumir que não é lento. ( React Training )

Acredito que as pessoas que afirmam que a criação de funções é cara sempre foram mal informadas (a equipe React nunca disse isso). ( Tweet )

Quando a react/jsx-no-bindregra é útil?

Você deseja garantir que os componentes memoized funcionem conforme o esperado:

  • React.memo (para componentes de função)
  • PureComponentou personalizado shouldComponentUpdate(para componentes de classe)

Obedecendo a esta regra, referências estáveis ​​de objetos de função são passadas. Portanto, os componentes acima podem otimizar o desempenho evitando novas renderizações, quando os adereços anteriores não mudaram.

Como resolver o erro ESLint?

Classes: Defina o manipulador como método ou propriedade de classe para thisvinculação.
Ganchos: UseuseCallback .

Meio termo

Em muitos casos, as funções inline são muito convenientes de usar e absolutamente excelentes em termos de requisitos de desempenho. Infelizmente, esta regra não pode ser limitada apenas a tipos de componentes memoized. Se você ainda deseja usá-lo em todas as áreas, pode, por exemplo, desativá-lo para nós DOM simples:

rules: {
  "react/jsx-no-bind": [ "error", { ignoreDOMComponents: true } ],
}

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
ford04
fonte