Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Різниця між функціональними та класовими компонентми в React». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Функціональний компонент** - JavaScript-функція, яка повертає JSX і використовує хуки для стану та lifecycle. Класовий компонент - ES6-клас з `this.state` і методами lifecycle. ```jsx // Функціональний const Hello = ({ name }) => <h1>Привіт, {name}</h1>; // Класовий class Hello extends React.Component { render() { return <h1>Привіт, {this.props.name}</h1>; } } ``` **Головне:** за замовчуванням використовуй функціональний компонент. Класовий потрібен тільки для error boundaries.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Функціональні компоненти** - це JavaScript-функції, які отримують props і повертають JSX. Класові компоненти - ES6-класи, що розширюють `React.Component`, зберігають стан через `this.state` і реагують на lifecycle через спеціальні методи. ## Теорія ### TL;DR - Функціональний компонент = виклик функції. Класовий = об'єкт із методами, що живе весь час монтування. - З React 16.8 хуки (`useState`, `useEffect`) дають функціональним компонентам все те, що мали класи. - Єдиний виняток: error boundaries. Тільки класові компоненти підтримують `componentDidCatch`. - Новий код: завжди функціональний компонент. Без обговорень. ### Швидкий приклад ```jsx // Функціональний: хук керує станом, мінімум коду function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>{count}</button>; } // Класовий: той самий результат, але більше шаблонного коду class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <button onClick={() => this.setState({ count: this.state.count + 1 })}> {this.state.count} </button> ); } } ``` Функціональна версія зазвичай на 30-50% коротша. Вивід однаковий. ### Головна різниця Коли React рендерить функціональний компонент, він викликає функцію з props і отримує JSX. Кожен рендер - окремий виклик зі своїм scope. Класовий компонент працює інакше: React створює один екземпляр і зберігає його. При кожному оновленні викликається `render()`, але сам об'єкт живе постійно. Саме тому `this.state` зберігається між рендерами без хуків, але водночас ти постійно думаєш про прив'язку методів через `this`. ### Коли що використовувати - **Функціональний:** будь-яка нова фіча, новий проект, будь-який компонент зі станом або side effects - **Класовий:** error boundaries (єдиний спосіб зловити помилки рендеру через `componentDidCatch`), підтримка наявного класового коду ### Таблиця порівняння | Аспект | Функціональний | Класовий | |--------|---------------|----------| | Синтаксис | `function MyComponent(props) {}` | `class MyComponent extends React.Component {}` | | Стан | хук `useState()` | `this.state` + `setState()` | | Lifecycle | `useEffect()` | `componentDidMount()`, `componentDidUpdate()` тощо | | Context | `useContext()` | `this.context` | | `this` | Не потрібен | Обов'язковий, потребує прив'язки | | Розмір коду | На 30-50% менше | Більше шаблонного коду | | Error boundaries | Не підтримуються | Підтримуються через `componentDidCatch()` | | Коли використовувати | За замовчуванням для нового коду | Error boundaries, legacy-код | ### Типові помилки **Умовний виклик хуків** ```jsx // НЕПРАВИЛЬНО: порядок хуків порушується між рендерами function Component({ showEmail }) { if (showEmail) { const [email, setEmail] = useState(""); // зсуває порядок викликів } } // ПРАВИЛЬНО: хуки завжди викликаються на верхньому рівні function Component({ showEmail }) { const [email, setEmail] = useState(""); // відображаємо поле email лише якщо showEmail === true } ``` React відстежує хуки за порядком їх виклику. Умовний виклик зсуває цей порядок між рендерами і прив'язує стан не до того хука. **Відсутній масив залежностей у useEffect** ```jsx // НЕПРАВИЛЬНО: запускається після кожного рендеру, нескінченний цикл useEffect(() => { fetch("/api/data").then(r => r.json()).then(setData); }); // ПРАВИЛЬНО: порожній масив = запускається один раз при монтуванні useEffect(() => { fetch("/api/data").then(r => r.json()).then(setData); }, []); ``` **Забута прив'язка методу в класовому компоненті** ```jsx // НЕПРАВИЛЬНО: this = undefined всередині колбека class Button extends React.Component { handleClick() { console.log(this.state); // TypeError } render() { return <button onClick={this.handleClick}>Click</button>; } } // ПРАВИЛЬНО: стрілкова функція в полі класу зберігає this class Button extends React.Component { handleClick = () => { console.log(this.state); // працює }; render() { return <button onClick={this.handleClick}>Click</button>; } } ``` ### Де зустрічається в реальних проектах - React-документація і офіційні приклади: виключно функціональні компоненти з React 16.8 - Next.js: функціональний за замовчуванням; класовий з'являється тільки як error boundary - Redux: хуки `useSelector` і `useDispatch` замінили патерн з `connect()` HOC - React Query: `useQuery` і інші хуки потребують функціональних компонентів - Error boundaries: тільки класові. Функціонального аналога в поточних версіях React немає. У більшості проектів є один `ErrorBoundary`-клас на рівні кореня, а решта компонентів - функціональні. ### Питання на співбесіді **Q:** Чому error boundaries не можуть бути функціональними компонентами? **A:** Error boundaries потребують `componentDidCatch` і `getDerivedStateFromError`, які є методами lifecycle тільки для класів. React поки не додав хуків для цього. Стандартне рішення - один класовий компонент, що обгортає дерево компонентів. **Q:** Чи дійсно функціональні компоненти з хуками працюють швидше за класові? **A:** Не в базі. Обидва дають подібний скомпільований код. Функціональні компоненти простіше оптимізувати: логіку ділять на окремі хуки і мемоізують кожен шматок через `useMemo` і `useCallback`. У класах для цього потрібен `shouldComponentUpdate` або `PureComponent`. **Q:** Що таке stale closure (застаріле замикання) і чому це торкається тільки функціональних компонентів? **A:** Stale closure виникає, коли колбек захоплює стару версію змінної стану. Класові компоненти обходять це, бо `this.state` завжди вказує на поточний стан екземпляра. У функціональних компонентах вирішується через форму з оновлювачем: `setCount(prev => prev + 1)` замість `setCount(count + 1)`. **Q:** Як замінити методи lifecycle класу на хуки? **A:** Окремий `useEffect` для кожної задачі. `useEffect(() => {...}, [])` замінює `componentDidMount`. `useEffect(() => {...}, [dep])` замінює `componentDidUpdate` для конкретного значення. Функція, повернута з ефекту, замінює `componentWillUnmount` для очищення. ## Приклади ### Форма входу з валідацією ```jsx function LoginForm() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [errors, setErrors] = useState({}); const handleSubmit = (e) => { e.preventDefault(); const newErrors = {}; if (!email.includes("@")) newErrors.email = "Невалідний email"; if (password.length < 8) newErrors.password = "Мінімум 8 символів"; if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } console.log("Відправляємо форму..."); }; return ( <form onSubmit={handleSubmit}> <input value={email} onChange={(e) => setEmail(e.target.value)} /> {errors.email && <span>{errors.email}</span>} <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> {errors.password && <span>{errors.password}</span>} <button type="submit">Увійти</button> </form> ); } ``` Три змінні стану, логіка валідації, жодного `this`, жодної прив'язки. Кожен стан живе поруч із логікою, яка його використовує. ### Error boundary (єдиний випадок для класового компонента) ```jsx class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error, info) { console.error("Перехоплено помилку рендеру:", error, info); } render() { if (this.state.hasError) { return <h1>Щось пішло не так.</h1>; } return this.props.children; } } ``` Це не застарілий патерн. Це єдиний варіант, який React пропонує для перехоплення помилок рендеру. Обгорни ним додаток один раз і забудь.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.