Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Яку проблему вирішують hooks в React?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**React hooks** вирішують три проблеми класових компонентів: складний спосіб ділитися логікою стану між компонентами (HOC і render props утворювали wrapper hell), lifecycle методи, що розкидали пов'язаний код по різних місцях, і складний `this`-синтаксис. З'явилися у React 16.8 і замінюють усе це звичайними функціями.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**React hooks** - це функції, які дозволяють функціональним компонентам мати стан, побічні ефекти та інші можливості React без написання класів. ## Теорія ### TL;DR - До React 16.8 тільки класові компоненти могли мати стан і lifecycle методи - Щоб ділитися логікою між класовими компонентами, використовували HOC або render props, що утворювало глибоку вкладеність - Lifecycle методи розкидали пов'язаний код по різних місцях: setup у `componentDidMount`, очищення у `componentWillUnmount` - `this` у класах постійно провокує помилки - Hooks вирішують всі три проблеми через звичайні функції ### Клас проти хука: порівняння ```jsx // Клас: setup і cleanup у різних lifecycle методах class ChatRoom extends React.Component { componentDidMount() { this.socket = openSocket(this.props.roomId); } componentWillUnmount() { this.socket.close(); } } // Хук: один ефект, обидва разом function ChatRoom({ roomId }) { useEffect(() => { const socket = openSocket(roomId); return () => socket.close(); // очищення поруч із запуском }, [roomId]); } ``` Клас розбиває одну логіку на два методи. Хук тримає її в одному місці. В цьому вся суть. ### Чому класові компоненти не масштабувалися Класовий компонент із середньою складністю розкидав логіку по чотирьох місцях: стан у конструкторі, підписки у `componentDidMount`, реакції на зміни пропсів у `componentDidUpdate`, очищення у `componentWillUnmount`. Чотири місця, одна логіка. Але головна проблема - повторне використання. Якщо двом компонентам потрібна однакова логіка підписки, чистого рішення не було. Використовували HOC або render props. Обидва варіанти робочі. Але накласти кілька HOC, і React DevTools показує `Connect(WithAuth(WithTheme(MyComponent)))`. Це і є wrapper hell. Коли я бачив кодові бази після міграції з класів на hooks, файли компонентів ставали коротшими. Але важливіше інше: логіку стає видно. Читаєш `useEffect` і за кілька рядків бачиш повний lifecycle цього ефекту. ### Custom hooks: де проблема вирішується остаточно `useState` і `useEffect` корисні, але custom hooks (власні хуки) - це місце, де проблеми з повторним використанням вирішуються по-справжньому. Custom hook - це функція, назва якої починається з `use` і яка всередині викликає інші hooks. Виносиш логіку підписки у `useSubscription`, стан форми у `useForm`, запити до API у `useFetch`. Будь-який компонент може це викликати. Без HOC, без вкладеності, без зайвих шарів у дереві. ### Типові помилки **Відсутній масив залежностей:** ```jsx useEffect(() => { fetchUser(userId); }); // запускається після кожного рендеру useEffect(() => { fetchUser(userId); }, [userId]); // запускається тільки при зміні userId ``` **Забуте очищення:** ```jsx // Баг: listener накопичується після кожного рендеру useEffect(() => { window.addEventListener('keydown', handleKey); }, []); // Правильно useEffect(() => { window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, []); ``` **Читання стану одразу після оновлення:** ```jsx setCount(count + 1); console.log(count); // старе значення - React батчує оновлення ``` Оновлення стану асинхронне. Нове значення буде доступне тільки після наступного рендеру. ### Питання на співбесіді **Q:** Чому не можна викликати hooks у умовах або циклах? **A:** React відстежує hooks за порядком виклику. Якщо хук пропустити в одному рендері, порядок зміщується і React читає неправильний стан для кожного наступного хука. Правило підтримує стабільний порядок між рендерами. **Q:** Що таке custom hook? **A:** Функція, назва якої починається з `use` і яка всередині викликає інші hooks. Дозволяє виносити логіку стану в окрему функцію без змін у дереві компонентів. **Q:** Що використовували до hooks для спільного використання логіки? **A:** HOC і render props. Обидва варіанти робочі, але додають зайві шари компонентів. Custom hook вирішує те саме через звичайний виклик функції. **Q:** `useEffect` з `[]` повністю замінює `componentDidMount`? **A:** Майже. Обидва запускаються після першого рендеру. Але `useEffect` запускається після відмальовки браузером, а `componentDidMount` - після оновлення DOM, до відмальовки. Для вимірювань layout краще підходить `useLayoutEffect`. ## Приклади ### Лічильник: клас проти хука ```jsx // Клас: конструктор, bind і this скрізь class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; this.increment = this.increment.bind(this); // легко забути } increment() { this.setState({ count: this.state.count + 1 }); } render() { return <button onClick={this.increment}>{this.state.count}</button>; } } // Хук: та сама поведінка, без зайвого function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>{count}</button>; } ``` Клас потребує конструктора, виклику `bind` і `this` скрізь. Функціональний компонент з `useState` робить те саме в два рядки. ### Custom hook: повторне використання логіки запитів ```jsx // Логіка запиту в одному місці - будь-який компонент може використати function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; fetch(url) .then(res => res.json()) .then(result => { if (!cancelled) setData(result); }) .catch(err => { if (!cancelled) setError(err); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; // запобігає оновленню після unmount }, [url]); return { data, loading, error }; } // Будь-який компонент отримує стан запиту в один рядок function UserProfile({ userId }) { const { data: user, loading } = useFetch(`/api/users/${userId}`); if (loading) return <span>Завантаження...</span>; return <h1>{user.name}</h1>; } ``` Логіка запиту живе в одному місці й тестується один раз. До hooks команди або дублювали цей код у кожному компоненті, або загортали в HOC. Custom hook вирішує це без жодної вкладеності.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.