Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке React.PureComponent». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**React.PureComponent** - це базовий клас для класових компонентів React, який пропускає ре-рендери через вбудоване поверхневе порівняння (shallow comparison) props і state. ```jsx class MyComponent extends React.PureComponent { render() { return <div>{this.props.value}</div>; } } // Не ре-рендериться, якщо props.value не змінився ``` **Ключове:** тільки поверхневе порівняння - вкладені мутації об'єктів непомітні. Функціональний еквівалент: `React.memo`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**React.PureComponent** - це базовий клас для класових компонентів React, який пропускає зайві ре-рендери через поверхневе порівняння (shallow comparison) props і state перед кожним рендером. ## Теорія ### TL;DR - Уяви перевірку на вході: `PureComponent` дивиться на етикетку коробки (посилання), а не заглядає всередину. - Головна відмінність від `React.Component`: вбудований `shouldComponentUpdate` з поверхневим порівнянням props і state. - Плоскі props (числа, рядки, стабільні посилання) - `PureComponent` оптимізує без зусиль. Вкладені мутації - пропустить зміну. - Для функціональних компонентів використовуй `React.memo`. ### Швидкий приклад ```tsx import React, { PureComponent, Component } from 'react'; class PureChild extends PureComponent<{ count: number }> { render() { console.log('PureChild renders'); return <div>Count: {this.props.count}</div>; } } class RegularChild extends Component<{ count: number }> { render() { console.log('RegularChild renders'); return <div>Count: {this.props.count}</div>; } } class Parent extends Component { state = { count: 0 }; render() { return ( <> <PureChild count={this.state.count} /> <RegularChild count={this.state.count} /> {/* count залишається 0, але батько все одно викликає setState */} <button onClick={() => this.setState({ count: this.state.count })}> Без змін </button> </> ); } } // Клік: тільки "RegularChild renders" в консолі. PureChild пропускає. ``` Обидва компоненти отримують однакове значення `count` після кліку. `RegularChild` рендериться все одно. `PureChild` перевіряє, нічого не знаходить і виходить без рендеру. ### Ключова відмінність `React.Component` ре-рендериться кожного разу, коли батько оновлюється або викликається `setState`, незалежно від того, чи реально змінилися дані. `React.PureComponent` перевизначає `shouldComponentUpdate` з поверхневим порівнянням: перебирає кожен ключ props і state та порівнює значення через `Object.is`. Якщо нічого не змінилось, React пропускає рендер і diff усього піддерева. Поверхнева перевірка коштує невеликих ресурсів CPU, але це майже завжди дешевше за diff цілого піддерева. ### Коли використовувати - Props - примітиви (числа, рядки, булеві значення): `PureComponent` дає безкоштовну оптимізацію без жодного додаткового коду. - Об'єкти в props стабільні (мемоізовані через `useMemo` або створені один раз поза рендером): поверхнева перевірка спрацює правильно. - Список з 50-100+ елементів, де батько часто ре-рендериться, а самі елементи змінюються рідко: `PureComponent` суттєво скорочує зайві рендери. - Вкладені об'єкти мутують на місці: не використовуй `PureComponent`, він пропустить зміну і покаже застарілий UI. - Функціональний компонент: використовуй `React.memo` - та сама логіка, але підтримує хуки. ### Таблиця порівняння | Властивість | React.Component | React.PureComponent | |---|---|---| | Тригер ре-рендеру | Будь-яке оновлення батька або setState | Оновлення батька + реальна зміна props/state | | shouldComponentUpdate | Немає за замовчуванням | Вбудована поверхнева перевірка | | Мутації об'єктів на місці | Помітить (завжди рендериться) | Не помітить (пропустить) | | Підходить для | Будь-якої форми props | Плоских або стабільних props | | Еквівалент для функцій | Немає | React.memo | ### Як це працює всередині `PureComponent` встановлює прапор `isPureReactComponent = true`, який читає reconciler React. Перед викликом `render` React запускає вбудований `shouldComponentUpdate`, що використовує `Object.is` для порівняння кожного ключа в props і state. Якщо всі порівняння збігаються, компонент і все піддерево пропускають reconciliation. У React 16+ з fiber цей bail-out ефективний: React не викликає `render` і переходить до наступного завдання. ### Типові помилки **Мутація об'єктів або масивів на місці:** ```tsx // Неправильно: те саме посилання на масив, PureComponent пропускає ре-рендер this.props.items[0].done = true; this.forceUpdate(); // хак, і все одно не допоможе дочірнім PureComponent // Правильно: нове посилання this.setState({ items: this.state.items.map((item, i) => i === 0 ? { ...item, done: true } : item ) }); ``` `PureComponent` бачить те саме посилання на масив і пропускає рендер. UI залишається застарілим. Це найпоширеніший баг у продакшені з `PureComponent`. **Передача нового об'єктного літерала при кожному рендері:** ```tsx // Неправильно: новий об'єкт при кожному рендері знищує будь-яку користь <PureChild config={{ theme: 'dark' }} /> // Правильно: стабільне посилання const config = useMemo(() => ({ theme: 'dark' }), []); <PureChild config={config} /> ``` Отримуєш витрати на поверхневу перевірку без жодної користі. `PureComponent` ре-рендериться при кожному циклі. **Очікувати перевірки вкладених даних:** Якщо ти розгортаєш об'єкт верхнього рівня, але мутуєш вкладену властивість на місці, поведінка стає непередбачуваною залежно від того, чи змінилось посилання верхнього рівня. Завжди копіюй і вкладені об'єкти: `{ ...user, details: { ...user.details, age: 31 } }`. Все інше - баг, який чекає свого часу. ### Де зустрічається в реальних проектах - Великі таблиці та списки: кожен рядок у `PureComponent`, щоб ре-рендерився тільки змінений елемент. - Material-UI TableRow і таблиця Ant Design використовують `PureComponent` всередині саме з цієї причини. - Redux-підключені списки: `PureComponent` разом з reselect-селекторами, щоб компоненти ре-рендерились тільки при зміні їхнього шматка state. - React DevTools Profiler: додай `PureComponent` до підозрілого компонента і перевір "why did this render?", щоб побачити пропуски рендерів. Особисто я знаходжу `PureComponent` найкориснішим при рендерингу списків. Додай 100 елементів, переключи один - і в Profiler видно, як 99 компонентів пропускають рендер без жодних додаткових зусиль. ### Питання на співбесіді **Q:** В чому різниця між shallow і deep рівністю? **A:** Shallow перевіряє посилання кожного ключа через `Object.is`, лише на один рівень вглиб. Deep рекурсивно порівнює вкладені значення. Deep - дорого і рідко потрібно, якщо дотримуватись іммутабельності. **Q:** Чи можна перевизначити `shouldComponentUpdate` на `PureComponent`? **A:** Так. Твій варіант повністю замінює вбудовану поверхневу перевірку. Роби це тільки якщо потрібна кастомна логіка порівняння для конкретних props. **Q:** Який еквівалент `PureComponent` для функціональних компонентів? **A:** `React.memo`. Обгортає функціональний компонент і виконує те саме поверхневе порівняння props. Другим аргументом можна передати кастомну функцію порівняння. **Q:** Чи впливає `PureComponent` на error boundaries? **A:** Ні. Якщо `render` кинув помилку, вона все одно спливе до найближчого error boundary незалежно від поверхневої перевірки. **Q:** У React 18 concurrent mode чи допомагає `PureComponent` з асинхронним рендерингом? **A:** Перевірка `PureComponent` синхронна. Для переваг асинхронного рендерингу комбінуй `React.memo` з `useDeferredValue`. `PureComponent` сам по собі не взаємодіє з concurrent-функціями. ## Приклади ### Оптимізація рядка списку Реальний сценарій: 100+ елементів todo. Батько ре-рендериться при будь-якій зміні, але кожен рядок має ре-рендеритися тільки якщо змінився власний todo. ```tsx interface Todo { id: number; text: string; done: boolean; } class TodoItem extends React.PureComponent<{ todo: Todo }> { render() { const { todo } = this.props; console.log(`Рендер todo #${todo.id}`); return ( <li style={{ textDecoration: todo.done ? 'line-through' : 'none' }}> {todo.text} </li> ); } } // Батько ре-рендерить список при кожній зміні. // Переключи todo #3 -> тільки "Рендер todo #3" в консолі. Решта 99 пропускають. ``` Кожен `TodoItem` отримує об'єкт todo з іммутабельного оновлення (новий об'єкт тільки для зміненого елемента, ті самі посилання для решти). `PureComponent` бачить незмінені посилання і пропускає ці рендери. ### Пастка з мутацією Цей edge case викликає застарілий UI у продакшені: ```tsx class UserCard extends React.PureComponent<{ user: { name: string; age: number } }> { render() { console.log('UserCard renders'); return <div>{this.props.user.name}, вік {this.props.user.age}</div>; } } class App extends React.Component { state = { user: { name: 'Alice', age: 30 } }; updateAge = () => { // Неправильно: мутуємо той самий об'єкт, потім setState з тим самим посиланням this.state.user.age = 31; this.setState({ user: this.state.user }); // те саме посилання! // PureComponent пропускає - UI показує вік 30, реальне значення 31 }; render() { return ( <> <UserCard user={this.state.user} /> <button onClick={this.updateAge}>День народження</button> </> ); } } // Виправлення: this.setState({ user: { ...this.state.user, age: 31 } }) ``` Поверхнева перевірка бачить те саме посилання `user` і пропускає рендер. UI показує старий вік. Виправлення займає один рядок: розгорни об'єкт, щоб створити нове посилання.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.