Dlaczego nie powinieneś używać wbudowanych funkcji strzałek w właściwościach JSX
Używanie funkcji strzałkowych lub powiązań w JSX to zła praktyka, która obniża wydajność, ponieważ funkcja jest odtwarzana przy każdym renderowaniu.
Za każdym razem, gdy tworzona jest funkcja, poprzednia funkcja jest usuwana z pamięci. Rrenderowanie wielu elementów może powodować szarpanie w animacjach.
Korzystanie z funkcji inline strzałki spowoduje PureComponent
S i komponenty, które Zastosowanie shallowCompare
w shouldComponentUpdate
metodzie i tak rerender. Ponieważ właściwość funkcji strzałki jest odtwarzana za każdym razem, płytkie porównanie zidentyfikuje ją jako zmianę właściwości, a komponent ponownie się wyłączy.
Jak widać na poniższych 2 przykładach - gdy używamy funkcji inline arrow, <Button>
komponent jest renderowany za każdym razem (konsola wyświetla tekst „render button”).
Przykład 1 - PureComponent bez wbudowanej obsługi
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>
Przykład 2 - PureComponent z wbudowaną obsługą
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>
Wiązanie metod this
bez wbudowanych funkcji strzałek
Ręczne wiązanie metody w konstruktorze:
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>
);
}
}
Powiązanie metody przy użyciu pól klasy propozycji z funkcją strzałki. Ponieważ jest to propozycja etapu 3, musisz dodać ustawienie wstępne etapu 3 lub transformację właściwości klasy do konfiguracji 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>
);
}
}
Komponenty funkcyjne z wewnętrznymi wywołaniami zwrotnymi
Kiedy tworzymy funkcję wewnętrzną (na przykład procedurę obsługi zdarzeń) wewnątrz komponentu funkcji, funkcja ta zostanie odtworzona za każdym razem, gdy komponent jest renderowany. Jeśli funkcja zostanie przekazana jako właściwości (lub przez kontekst) do komponentu potomnego ( Button
w tym przypadku), to dziecko również zostanie ponownie wyrenderowane.
Przykład 1 - składnik funkcji z wewnętrznym wywołaniem zwrotnym:
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>
Aby rozwiązać ten problem, możemy zawinąć wywołanie zwrotne useCallback()
zaczepem i ustawić zależności na pustą tablicę.
Uwaga:useState
funkcja generowane przyjmuje funkcję Updater, który zapewnia aktualny stan. W ten sposób nie musimy ustawiać bieżącego stanu na zależność useCallback
.
Przykład 2 - składnik funkcji z wewnętrznym wywołaniem zwrotnym opakowanym za pomocą metody 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
, więc nie ma nic do powiązania. Zwykle metody są dostarczane przez inteligentny komponent opakowujący.onClick={() => { onTodoClick(todo.id) }
cb() { onTodoClick(this.props.todo.id); }
.useCallback
z wartością dynamiczną. stackoverflow.com/questions/55006061/…Dzieje się tak, ponieważ funkcja strzałki najwyraźniej utworzy nową instancję funkcji przy każdym renderowaniu, jeśli zostanie użyta we właściwości JSX. Może to spowodować ogromne obciążenie dla garbage collectora, a także utrudnić przeglądarce optymalizację jakichkolwiek „gorących ścieżek”, ponieważ funkcje będą wyrzucane zamiast ponownie wykorzystywane.
Możesz zobaczyć całe wyjaśnienie i więcej informacji na https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
źródło
bind
funkcji lub strzałek jest tutaj strzeleniem sobie w stopę. Nie jest to jednak dobrze udokumentowane, szczególnie w przypadku pracy zmap
tablicami ping w ramach List itp.Aby uniknąć tworzenia nowych funkcji z tymi samymi argumentami, możesz zapamiętać wynik wiązania funkcji, oto proste narzędzie o nazwie,
memobind
aby to zrobić: https://github.com/supnate/memobindźródło
Używanie takich funkcji wbudowanych jest w porządku. Zasada lintingu jest nieaktualna.
Ta reguła pochodzi z czasów, gdy funkcje strzałkowe nie były tak powszechne, a ludzie używali .bind (this), który był powolny. Problem z wydajnością został rozwiązany w Chrome 49.
Zwróć uwagę, że nie przekazujesz funkcji inline jako właściwości do komponentu potomnego.
Ryan Florence, autor React Router, napisał o tym świetny artykuł:
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
źródło
Możesz używać funkcji strzałkowych za pomocą biblioteki podręcznej obsługi reagowania , nie musisz się martwić o wydajność ponownego renderowania:
Inne funkcje:
źródło
Dlaczego rekwizyty JSX nie powinny używać funkcji strzałek ani bindować?
Głównie dlatego, że funkcje wbudowane mogą zakłócić zapamiętywanie zoptymalizowanych komponentów:
Mniej dotyczy dodatkowych kosztów tworzenia funkcji:
Kiedy
react/jsx-no-bind
reguła jest przydatna?Chcesz mieć pewność, że zapamiętane komponenty działają zgodnie z przeznaczeniem:
React.memo
(dla komponentów funkcyjnych)PureComponent
lub niestandardoweshouldComponentUpdate
(dla komponentów klasowych)Przestrzegając tej reguły, przekazywane są odniesienia do stabilnych obiektów funkcji. Dlatego powyższe komponenty mogą optymalizować wydajność, zapobiegając ponownemu renderowaniu, gdy poprzednie właściwości nie uległy zmianie.
Jak rozwiązać błąd ESLint?
Klasy: Zdefiniuj procedurę obsługi jako metodę lub właściwość klasy do
this
powiązania.Haczyki: użyj
useCallback
.Kompromis
W wielu przypadkach funkcje wbudowane są bardzo wygodne w użyciu i absolutnie dobre pod względem wymagań wydajnościowych. Niestety, ta reguła nie może być ograniczona tylko do zapamiętanych typów komponentów. Jeśli nadal chcesz go używać w różnych miejscach, możesz na przykład wyłączyć go dla prostych węzłów DOM:
źródło