Почему вы не должны использовать встроенные стрелочные функции в JSX-свойствах
Использование стрелочных функций или привязки в JSX - плохая практика, которая снижает производительность, потому что функция воссоздается при каждой визуализации.
Всякий раз, когда функция создается, предыдущая функция собирает мусор. Повторный рендеринг многих элементов может привести к искажению анимации.
Использование встроенной стрелочной функции в любом случае приведет к повторной отрисовке PureComponent
s и компонентов, которые используются shallowCompare
в shouldComponentUpdate
методе. Поскольку свойство стрелочной функции воссоздается каждый раз, поверхностное сравнение идентифицирует его как изменение свойства, и компонент будет повторно отображен.
Как вы можете видеть в следующих двух примерах - когда мы используем встроенную стрелочную функцию, <Button>
компонент каждый раз повторно визуализируется (на консоли отображается текст кнопки рендеринга).
Пример 1 - PureComponent без встроенного обработчика
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>
Пример 2 - PureComponent со встроенным обработчиком
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>
Привязка методов к this
функциям стрелок без встраивания
Связывание метода вручную в конструкторе:
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>
);
}
}
Связывание метода с использованием полей класса предложения с функцией стрелки. Поскольку это предложение этапа 3, вам необходимо добавить предустановку этапа 3 или преобразование свойств класса в конфигурацию 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>
);
}
}
Компоненты функций с внутренними обратными вызовами
Когда мы создаем внутреннюю функцию (например, обработчик событий) внутри функционального компонента, функция будет воссоздаваться каждый раз, когда компонент визуализируется. Если функция передается как реквизиты (или через контекст) дочернему компоненту ( Button
в данном случае), этот дочерний элемент также будет повторно отрисован.
Пример 1 - Функциональный компонент с внутренним обратным вызовом:
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>
Чтобы решить эту проблему, мы можем обернуть обратный вызов useCallback()
хуком и установить зависимости в пустой массив.
Примечание:useState
генерируются функция принимает функцию получения обновлений, что обеспечивает текущее состояние. Таким образом, нам не нужно устанавливать зависимость от текущего состояния useCallback
.
Пример 2 - Функциональный компонент с внутренним обратным вызовом, заключенный в 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
, поэтому связывать нечего. Обычно методы предоставляются интеллектуальным компонентом оболочки.onClick={() => { onTodoClick(todo.id) }
cb() { onTodoClick(this.props.todo.id); }
.useCallback
с динамическим значением. stackoverflow.com/questions/55006061/…Это связано с тем, что стрелочная функция, очевидно, будет создавать новый экземпляр функции при каждом рендеринге, если она используется в свойстве JSX. Это может создать огромную нагрузку на сборщик мусора, а также помешать браузеру оптимизировать любые «горячие пути», поскольку функции будут выброшены, а не использованы повторно.
Вы можете увидеть все объяснение и дополнительную информацию на https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
источник
bind
функций или стрелок здесь означает выстрел себе в ногу. Однако это плохо документировано, особенно в случае работы сmap
массивами ping в списках и т. Д.Чтобы избежать создания новых функций с теми же аргументами, вы можете запомнить результат привязки функции, вот простая утилита, названная
memobind
для этого: https://github.com/supnate/memobindисточник
Использование таких встроенных функций совершенно нормально. Правило линтинга устарело.
Это правило пришло из тех времен, когда стрелочные функции были не так распространены, и люди использовали .bind (this), который раньше был медленным. Проблема с производительностью исправлена в Chrome 49.
Обратите внимание, что вы не передаете встроенные функции в качестве свойств дочернему компоненту.
Райан Флоренс, автор React Router, написал об этом отличную статью:
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
источник
Вы можете использовать стрелочные функции с помощью библиотеки react-cached-handler , не нужно беспокоиться о производительности повторного рендеринга:
Другие свойства:
источник
Почему JSX-реквизиты не должны использовать стрелочные функции или привязку?
В основном потому, что встроенные функции могут нарушить мемоизацию оптимизированных компонентов:
Речь идет о меньших затратах на создание дополнительных функций:
Когда это
react/jsx-no-bind
правило полезно?Вы хотите убедиться, что мемоизированные компоненты работают должным образом:
React.memo
(для функциональных компонентов)PureComponent
или пользовательскийshouldComponentUpdate
(для компонентов класса)Подчиняясь этому правилу, передаются стабильные ссылки на функциональные объекты. Таким образом, указанные выше компоненты могут оптимизировать производительность, предотвращая повторную визуализацию, когда предыдущие свойства не изменились.
Как решить ошибку ESLint?
Классы: определите обработчик как метод или свойство класса для
this
привязки.Крючки: Использование
useCallback
.Середина
Во многих случаях встроенные функции очень удобны в использовании и абсолютно подходят с точки зрения требований к производительности. К сожалению, это правило не может быть ограничено только мемоизированными типами компонентов. Если вы все еще хотите использовать его повсеместно, вы можете, например, отключить его для простых узлов DOM:
источник