Почему вы не должны использовать встроенные стрелочные функции в 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>