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.
Sempre que uma função é criada, a função anterior é coletada como lixo. Renderizar muitos elementos pode criar problemas nas animações.
Usar uma função de seta embutida fará com que PureComponent
s e componentes que usam shallowCompare
no shouldComponentUpdate
mé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
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
onClick = () => this.setState((prevState) => ({
counter: prevState.counter + 1
}));
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ this.onClick } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Exemplo 2 - PureComponent com manipulador embutido
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ () => this.setState((prevState) => ({
counter: prevState.counter + 1
})) } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Métodos de vinculação this
sem funções de seta embutidas
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>
);
}
}
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 ( Button
neste caso), esse filho também será renderizado novamente.
Exemplo 1 - Componente de função com um retorno de chamada interno:
const { memo, useState } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1); // the function is recreated all the time
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
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 useState
funçã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:
const { memo, useState, useCallback } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => setCounter(counter => counter + 1), []);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
this
, então não há nada para vincular. Normalmente, os métodos são fornecidos por um componente inteligente de invólucro.onClick={() => { onTodoClick(todo.id) }
cb() { onTodoClick(this.props.todo.id); }
.useCallback
com valor dinâmico. stackoverflow.com/questions/55006061/…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
fonte
bind
as 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 commap
matrizes de ping dentro de listas, etc.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
memobind
para fazer isso: https://github.com/supnate/memobindfonte
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
fonte
Você pode usar as funções de seta usando a biblioteca react-cached-handler , sem se preocupar com o desempenho da re-renderização:
Outras características:
fonte
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:
É menos sobre o custo de criação de função adicional:
Quando a
react/jsx-no-bind
regra é útil?Você deseja garantir que os componentes memoized funcionem conforme o esperado:
React.memo
(para componentes de função)PureComponent
ou personalizadoshouldComponentUpdate
(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
this
vinculação.Ganchos: Use
useCallback
.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:
fonte