Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Композиція проти наслідування в React». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Composition vs inheritance в React**: React використовує composition (композицію), а не ієрархії класів. Компоненти обгортають інші компоненти через `children` або іменовані props. ```tsx function Card({ title, children }: { title: string; children: React.ReactNode }) { return <div className="card"><h2>{title}</h2>{children}</div>; } // <Card title="Профіль"><Avatar /></Card> ``` **Ключове:** React docs рекомендують не створювати власні ієрархії класів. Використовуй `children`, render props або hooks.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Composition vs inheritance в React** - composition (композиція) збирає UI з вкладених компонентів через props типу `children`, а inheritance (наслідування) розширює ієрархії класів через `extends`. ## Теорія ### TL;DR - Inheritance - як матрьошка: одна фігурка всередині іншої у фіксованому порядку. Composition - як LEGO: з'єднуєш будь-які блоки. - Головна різниця: composition дає змогу повторно використовувати компоненти без жорстких ланцюжків класів. - React docs прямо кажуть: не створюй власні ієрархії наслідування, крім `React.Component`. - Правило вибору: у 99% випадків використовуй composition. Решту 1% де раніше потрібна була спільна логіка закривають hooks. ### Швидкий приклад ```tsx // Inheritance (уникати): жорстко і крихко class Popup extends React.Component { render() { return <div className="popup">{this.props.children}</div>; } } class Dialog extends Popup { // прив'язаний до структури Popup // будь-яка зміна в Popup ламає Dialog } // Composition (краще): гнучко function Popup({ children }: { children: React.ReactNode }) { return <div className="popup">{children}</div>; } function Dialog({ title, children }: { title: string; children: React.ReactNode }) { return ( <Popup> <h2>{title}</h2> {children} </Popup> ); } ``` `Dialog` обгортає `Popup`, не наслідуючи його. Можеш замінити `Popup` на будь-що без змін у `Dialog`. ### Ключова різниця Inheritance класів створює один ланцюжок прототипів. Зміни в базовому класі ламають усіх нащадків. Composition передає дані через props і тримає компоненти незалежними. Архітектура fiber в React обходить JSX-дерево зверху вниз, монтуючи кожен дочірній компонент окремо зі своїм станом. Inheritance плющить це в один об'єкт. Тому composition добре поєднується з hooks, а команда React перестала рекомендувати ієрархії класів після появи hooks у версії 16.8. ### Коли що використовувати - Загальний контейнер для довільного контенту (модалка, картка, лейаут) - prop `children`. - Кілька поведінок на одному компоненті (стан завантаження плюс tooltip на кнопці) - composition через HOC або hooks. - Спільний стан по всьому застосунку (авторизація, тема, локаль) - Context API, не inheritance. - Просте візуальне перевикористання (картка, бейдж, лейаут) - composition завжди. ### Таблиця порівняння | Аспект | Composition | Inheritance | |---|---|---| | **Гнучкість** | Довільна комбінація компонентів | Фіксований ланцюжок класів | | **Перевикористання** | Обгортання будь-яких UI-елементів | Тільки прямі нащадки | | **Підтримка коду** | Незалежна зміна компонентів | Зміна базового класу ламає все | | **Підтримка React** | Офіційний патерн, hooks-friendly | Не рекомендовано після `React.Component` | | **Коли використовувати** | 99% випадків | Тільки legacy-код з класами | ### Типові помилки **Помилка: власні ієрархії класів** ```tsx // Неправильно: крихкий ланцюжок class ThemedButton extends React.Component { /* prop теми */ } class PrimaryButton extends ThemedButton { /* ламається при зміні теми */ } // Виправлення: prop variant + Context для теми function Button({ variant, children }: { variant: string; children: React.ReactNode }) { return <button className={`btn-${variant}`}>{children}</button>; } ``` Ланцюжок `ThemedButton -> PrimaryButton -> DangerButton` виглядає охайно до першої зміни базового класу. Потім ламається все. **Помилка: очікування що дочірній компонент отримає дані автоматично** ```tsx // Неправильно: UserInfo не отримує дані <Card><UserInfo /></Card> // Виправлення: render props коли дочірньому потрібні дані батька <Card renderUser={(user) => <UserInfo user={user} />} /> ``` Дочірні компоненти не успадковують стан батька. Дані передаються явно або через Context. **Помилка: виклик hooks у класовому компоненті** ```tsx // Неправильно: неприпустимий виклик hook class MyComponent extends React.Component { render() { const [count, setCount] = useState(0); // Помилка } } ``` Hooks працюють тільки у функціональних компонентах. Для спільної логіки - виноси у custom hook і використовуй його в різних компонентах. ### Де зустрічається в реальних проєктах - Material-UI v5: `<Modal><Form /></Modal>` обгортає будь-який контент без наслідування `Modal`. - Next.js 14 app router: `<Layout><Page /></Layout>` - стандартний патерн composition для лейаутів. - Chakra UI: compound-компоненти на зразок `<Box>`, що приймають будь-який вміст. - React Aria: `<Dialog>` приймає будь-який контент через `children`. ### Питання на співбесіді **Q:** Чому в React є `React.Component`, якщо inheritance не рекомендується? **A:** `React.Component` - тонкий базовий клас для інтеграції з lifecycle. Це єдиний дозволений випадок inheritance в React. Власні ієрархії понад це порушують принцип єдиної відповідальності. **Q:** Покажи як ділити логіку між компонентами без inheritance. **A:** Виноси у custom hook. `useModal()` повертає стан і обробники. Будь-який компонент може його викликати без жодного `extends`. **Q:** Коли брати Context замість composition? **A:** Composition явна: передаєш props напряму. Context підходить для даних, які потрібні багатьом компонентам на різних рівнях без prop drilling (тема, авторизація, локаль). Спочатку composition, Context - коли ланцюжки props стають завеликими. **Q:** Що замінило mixins і чому вони були гірші за inheritance? **A:** Hooks замінили mixins. Mixins патчили методи прямо в прототип компонента, що спричиняло колізії імен і робило код непрослідковуваним. Hooks компонують чисті функції без побічних ефектів. Це відповідь рівня senior, яку чекають на співбесіді. ## Приклади ### Базовий: картка з довільним вмістом ```tsx function Card({ title, children }: { title: string; children: React.ReactNode }) { return ( <div className="card"> <h3>{title}</h3> <div className="card-body">{children}</div> </div> ); } // Один Card, два різні варіанти вмісту <Card title="Профіль"> <Avatar src={user.avatar} /> <UserInfo name={user.name} /> </Card> <Card title="Налаштування"> <SettingsForm /> </Card> ``` Один компонент `Card`. Два різні варіанти вмісту. Жодного `extends`. ### Середній рівень: патерн слотів для лейауту сторінки ```tsx // Іменовані слоти через props - саме так працює app router у Next.js function Layout({ header, sidebar, content, }: { header: React.ReactNode; sidebar: React.ReactNode; content: React.ReactNode; }) { return ( <div className="layout"> <header>{header}</header> <aside>{sidebar}</aside> <main>{content}</main> </div> ); } // Кожен слот отримує свій компонент <Layout header={<Navbar user={currentUser} />} sidebar={<Sidebar links={navLinks} />} content={<DashboardPage />} /> ``` `Layout` не знає як виглядають `Navbar`, `Sidebar` або `DashboardPage` - лише розставляє їх. Я використовую цей патерн у кожному серйозному React-проєкті: він добре тримається коли дизайн змінюється, бо кожен слот можна замінити незалежно.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.