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>