Чистые компоненты (Pure Components) — это компоненты React, которые автоматически реализуют поверхностное сравнение props и state для оптимизации производительности. Они перерендериваются только при изменении входных данных.
✅ Основные характеристики:
❌ Ограничения:
Ключевое правило: Используйте чистые компоненты только с иммутабельными данными и при правильной работе с состоянием.
Чистые компоненты — это специальный тип компонентов React, которые автоматически оптимизируют производительность за счет реализации поверхностного сравнения props и state. Они перерендериваются только в том случае, если изменились входные данные.
Чистые компоненты реализуют метод shouldComponentUpdate с поверхностным сравнением (shallow comparison) props и state:
// Обычный компонент - перерендеривается всегда
class RegularComponent extends Component {
render() {
console.log('RegularComponent рендерится');
return <div>{this.props.value}</div>;
}
}
// Чистый компонент - перерендеривается только при изменении props/state
class PureComponentExample extends PureComponent {
render() {
console.log('PureComponent рендерится');
return <div>{this.props.value}</div>;
}
}
// Использование
function App() {
const [count, setCount] = useState(0);
// Эти данные не изменяются
const data = { name: 'Александр' };
return (
<div>
<button onClick={() => setCount(count + 1)}>
Увеличить: {count}
</button>
{/* Обычный компонент будет перерендериваться при каждом рендере App */}
<RegularComponent value={data.name} />
{/* Чистый компонент перерендерится только если data.name изменится */}
<PureComponentExample value={data.name} />
</div>
);
}Чистые компоненты используют поверхностное сравнение props и state:
// Пример поверхностного сравнения
function shallowEqual(objA, objB) {
if (objA === objB) {
return true;
}
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Сравниваем только свойства первого уровня
for (let i = 0; i < keysA.length; i++) {
if (!objB.hasOwnProperty(keysA[i]) ||
objA[keysA[i]] !== objB[keysA[i]]) {
return false;
}
}
return true;
}// Обычный компонент
class RegularCounter extends Component {
render() {
return (
<div>
<p>Счетчик: {this.props.count}</p>
<button onClick={this.props.onIncrement}>
Увеличить
</button>
</div>
);
}
}
// Чистый компонент
class PureCounter extends PureComponent {
render() {
return (
<div>
<p>Счетчик: {this.props.count}</p>
<button onClick={this.props.onIncrement}>
Увеличить
</button>
</div>
);
}
}
// Демонстрация разницы
function App() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState('');
// Объект, который не изменяется
const config = { theme: 'light' };
const handleIncrement = () => {
setCount(count + 1);
};
return (
<div>
<input
value={otherState}
onChange={(e) => setOtherState(e.target.value)}
placeholder="Другое состояние"
/>
{/* RegularCounter перерендерится при изменении otherState */}
<RegularCounter count={count} onIncrement={handleIncrement} />
{/* PureCounter перерендерится только при изменении count */}
<PureCounter count={count} onIncrement={handleIncrement} />
</div>
);
}С появлением хуков, функциональные компоненты могут использовать мемоизацию:
import { memo, useState } from 'react';
// Обычный функциональный компонент
function RegularComponent({ name, age }) {
console.log('RegularComponent рендерится');
return (
<div>
<h1>{name}</h1>
<p>Возраст: {age}</p>
</div>
);
}
// Мемоизированный компонент (аналог PureComponent)
const MemoizedComponent = memo(function MemoizedComponent({ name, age }) {
console.log('MemoizedComponent рендерится');
return (
<div>
<h1>{name}</h1>
<p>Возраст: {age}</p>
</div>
);
});
// С пользовательской функцией сравнения
const CustomMemoizedComponent = memo(
function CustomMemoizedComponent({ user }) {
console.log('CustomMemoizedComponent рендерится');
return (
<div>
<h1>{user.name}</h1>
<p>Возраст: {user.age}</p>
</div>
);
},
// Пользовательская функция сравнения
(prevProps, nextProps) => {
return prevProps.user.name === nextProps.user.name &&
prevProps.user.age === nextProps.user.age;
}
);
// Использование
function App() {
const [count, setCount] = useState(0);
// Эти данные не изменяются между рендерами
const userData = { name: 'Александр', age: 25 };
return (
<div>
<button onClick={() => setCount(count + 1)}>
Счетчик: {count}
</button>
<RegularComponent name={userData.name} age={userData.age} />
<MemoizedComponent name={userData.name} age={userData.age} />
<CustomMemoizedComponent user={userData} />
</div>
);
}// 1. Компоненты с примитивными props
function UserInfo({ name, age, email }) {
return (
<div>
<h2>{name}</h2>
<p>Возраст: {age}</p>
<p>Email: {email}</p>
</div>
);
}
const PureComponentUserInfo = memo(UserInfo);
// 2. Компоненты с иммутабельными объектами
function TodoItem({ todo, onToggle, onDelete }) {
return (
<div className={todo.completed ? 'completed' : ''}>
<span>{todo.text}</span>
<button onClick={() => onToggle(todo.id)}>Переключить</button>
<button onClick={() => onDelete(todo.id)}>Удалить</button>
</div>
);
}
const PureComponentTodoItem = memo(TodoItem);
// 3. Компоненты с массивами (если массив пересоздается при изменении)
function TodoList({ todos, onToggle, onDelete }) {
return (
<div>
{todos.map(todo => (
<PureComponentTodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
onDelete={onDelete}
/>
))}
</div>
);
}// 1. Компоненты с мутирующими объектами
class BadExample extends PureComponent {
render() {
// ❌ Если parentData мутирует, компонент не перерендерится
return <div>{this.props.parentData.value}</div>;
}
}
// 2. Компоненты с функциями, создаваемыми в родителе
function Parent() {
const [count, setCount] = useState(0);
// ❌ Новая функция при каждом рендере
const handleClick = () => {
console.log('Кликнули!');
};
return (
<div>
<button onClick={() => setCount(count + 1)}>
Счетчик: {count}
</button>
{/* Компонент будет перерендериваться из-за новой функции */}
<PureComponentWithFunction onClick={handleClick} />
</div>
);
}
// ✅ Правильный подход - мемоизация функции
function GoodParent() {
const [count, setCount] = useState(0);
// ✅ Функция создается один раз
const handleClick = useCallback(() => {
console.log('Кликнули!');
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Счетчик: {count}
</button>
{/* Компонент не будет перерендериваться */}
<PureComponentWithFunction onClick={handleClick} />
</div>
);
}import { memo, useState, useCallback } from 'react';
// Чистый компонент для элемента списка
const ListItem = memo(function ListItem({ item, onUpdate, onDelete }) {
console.log(`ListItem ${item.id} рендерится`);
return (
<div className="list-item">
<span>{item.text}</span>
<button onClick={() => onUpdate(item.id, `${item.text} (обновлено)`)}>
Обновить
</button>
<button onClick={() => onDelete(item.id)}>
Удалить
</button>
</div>
);
});
// Компонент списка
const List = memo(function List({ items, onUpdate, onDelete }) {
console.log('List рендерится');
return (
<div>
{items.map(item => (
<ListItem
key={item.id}
item={item}
onUpdate={onUpdate}
onDelete={onDelete}
/>
))}
</div>
);
});
// Родительский компонент
function App() {
const [items, setItems] = useState([
{ id: 1, text: 'Элемент 1' },
{ id: 2, text: 'Элемент 2' },
{ id: 3, text: 'Элемент 3' }
]);
const [otherState, setOtherState] = useState('');
// Мемоизированные функции
const handleUpdate = useCallback((id, newText) => {
setItems(prevItems =>
prevItems.map(item =>
item.id === id ? { ...item, text: newText } : item
)
);
}, []);
const handleDelete = useCallback((id) => {
setItems(prevItems =>
prevItems.filter(item => item.id !== id)
);
}, []);
return (
<div>
<input
value={otherState}
onChange={(e) => setOtherState(e.target.value)}
placeholder="Другое состояние"
/>
{/* List перерендерится только при изменении items */}
<List
items={items}
onUpdate={handleUpdate}
onDelete={handleDelete}
/>
</div>
);
}import { memo, useState } from 'react';
// ❌ Проблема с вложенными объектами
const UserProfileBad = memo(function UserProfileBad({ user }) {
return (
<div>
<h2>{user.profile.name}</h2>
<p>Возраст: {user.profile.age}</p>
<p>Email: {user.contact.email}</p>
</div>
);
});
// ✅ Правильный подход - деструктуризация или пользовательское сравнение
const UserProfileGood = memo(
function UserProfileGood({ user }) {
return (
<div>
<h2>{user.profile.name}</h2>
<p>Возраст: {user.profile.age}</p>
<p>Email: {user.contact.email}</p>
</div>
);
},
// Пользовательская функция сравнения для вложенных объектов
(prevProps, nextProps) => {
return (
prevProps.user.profile.name === nextProps.user.profile.name &&
prevProps.user.profile.age === nextProps.user.profile.age &&
prevProps.user.contact.email === nextProps.user.contact.email
);
}
);
// Альтернативный подход - передача отдельных props
const UserProfileBest = memo(function UserProfileBest({ name, age, email }) {
return (
<div>
<h2>{name}</h2>
<p>Возраст: {age}</p>
<p>Email: {email}</p>
</div>
);
});// ❌ Неправильно
class BadParent extends Component {
constructor(props) {
super(props);
this.state = {
data: { value: 0 }
};
}
handleClick = () => {
// Мутируем существующий объект
this.state.data.value += 1;
this.setState({ data: this.state.data }); // ❌ Не создаем новый объект!
};
render() {
return (
<div>
<button onClick={this.handleClick}>
Увеличить
</button>
{/* PureComponent не перерендерится, так как объект тот же */}
<PureChild data={this.state.data} />
</div>
);
}
}
// ✅ Правильно
class GoodParent extends Component {
constructor(props) {
super(props);
this.state = {
data: { value: 0 }
};
}
handleClick = () => {
// Создаем новый объект
this.setState({
data: { value: this.state.data.value + 1 } // ✅ Новый объект
});
};
render() {
return (
<div>
<button onClick={this.handleClick}>
Увеличить
</button>
{/* PureComponent перерендерится, так как объект новый */}
<PureChild data={this.state.data} />
</div>
);
}
}// ❌ Неправильно
function BadParent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Счетчик: {count}
</button>
{/* Новая функция при каждом рендере - PureComponent перерендерится */}
<PureComponent onClick={() => console.log('Клик!')} />
</div>
);
}
// ✅ Правильно
function GoodParent() {
const [count, setCount] = useState(0);
// Функция создается один раз
const handleClick = useCallback(() => {
console.log('Клик!');
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Счетчик: {count}
</button>
{/* Та же функция при каждом рендере - PureComponent не перерендерится */}
<PureComponent onClick={handleClick} />
</div>
);
}Чистые компоненты — это компоненты React, которые автоматически оптимизируют производительность через поверхностное сравнение props и state:
✅ Преимущества:
❌ Ограничения:
Ключевые моменты:
memoПонимание чистых компонентов помогает создавать более производительные React-приложения и избегать ненужных перерендеров.
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪