Чому не слід використовувати вбудовані функції стрілок у реквізитах 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 або перетворення властивостей класу до вашої конфігурації бабеля.
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>