Perché non dovresti usare le funzioni freccia in linea negli oggetti di scena JSX
Usare le funzioni freccia o l'associazione in JSX è una cattiva pratica che danneggia le prestazioni, perché la funzione viene ricreata ad ogni rendering.
Ogni volta che viene creata una funzione, la funzione precedente viene raccolta in Garbage Collection. Il rendering di molti elementi potrebbe creare jank nelle animazioni.
L'utilizzo di una funzione freccia in linea provocherà comunque il rendering di PureComponent
s e dei componenti utilizzati shallowCompare
nel shouldComponentUpdate
metodo. Poiché il sostegno della funzione freccia viene ricreato ogni volta, il confronto superficiale lo identificherà come una modifica a un sostegno e il componente verrà riprodotto nuovamente.
Come puoi vedere nei seguenti 2 esempi, quando usiamo la funzione freccia in linea, il <Button>
componente viene rieseguito ogni volta (la console mostra il testo del "pulsante di rendering").
Esempio 1: PureComponent senza gestore inline
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>
Esempio 2: PureComponent con gestore inline
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>
Binding di metodi a this
senza funzioni freccia incorporate
Associazione manuale del metodo nel costruttore:
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>
);
}
}
Associazione di un metodo utilizzando i campi della classe proposta con una funzione freccia. Poiché si tratta di una proposta della fase 3, è necessario aggiungere la preimpostazione della fase 3 o la trasformazione delle proprietà della classe alla configurazione di 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>
);
}
}
Componenti funzione con callback interni
Quando creiamo una funzione interna (ad esempio un gestore di eventi) all'interno di un componente funzione, la funzione verrà ricreata ogni volta che il componente viene renderizzato. Se la funzione viene passata come oggetti di scena (o tramite contesto) a un componente figlio ( Button
in questo caso), anche quel bambino verrà rieseguito.
Esempio 1 - Componente funzione con callback 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>
Per risolvere questo problema, possiamo racchiudere il callback con l' useCallback()
hook e impostare le dipendenze su un array vuoto.
Nota: la useState
funzione generata accetta una funzione di aggiornamento, che fornisce lo stato corrente. In questo modo, non è necessario impostare lo stato corrente come una dipendenza di useCallback
.
Esempio 2 - Componente della funzione con una richiamata interna racchiusa in 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>