Різниця між React Class Components та Functional Components. Розбираємося на прикладі
Усі статті, обговорення, новини про Front-end — в одному місці. Підписуйтеся на телеграм-канал!
Привіт. Мене звати Ігор, я React розробник. Мій шлях програміста почався у 2018 році. З того часу бібліотека React постійно змінювалась, змінився і підхід у використанні як класових, так і функціональних компонентів.
У використанні функціональних компонентів бібліотека отримала значний прогрес. Тепер використання хуків дозволяє робити зі значно меншими зусиллями те, що раніше вимагало написання великих класових компонентів. Чого тільки вартий хук useState, який дозволяє керувати даними, що має компонент, або теж добре відомий хук ефекту useEffect, який викликається при зміні даних, за якими він слідкує.
У вас може виникнути запитання, навіщо читати далі? Ви можете подумати, що класові компоненти стали непотрібні і про них забули, але ні. На жаль, чи на щастя, давніші проєкти, які були написані ще до хуків існують, і їх також потрібно підтримувати та вдосконалювати.
Щоб це не було для вас неочікувано, ми поговоримо про це у статті. Згадаємо, як це було до введення хуків у React та є зараз, коли багато бібліотек вже чудово працюють із хуками.
Трохи історії
Почнімо з того, як використовувались класові та функціональні компоненти до ери хуків.
Насамперед потрібно зрозуміти, що таке класові і функціональні компоненти. Поділ на класові компоненти і функціональні був зумовлений тим, що функціональні компоненти не могли містити у собі даних, а класові могли. Також з’явились контейнери — це ті ж класові компоненти, які у собі мали декілька або один компонент. Завдання контейнера тримати та передавати дані у компоненти, а також реагувати на івенти у компонентах (наприклад, введення тексту у текстове поле або натиск кнопки).
Функціональні використовувались тільки для простих компонент. Наприклад: кнопки, іконки, стилізація тексту, відображення простих або складних елементів. Тобто компонента, що має тільки показати дані, котрі прийшли через пропси. Якщо це текстове поле, то при вводі функціональний компонент реагує на введення даних і повідомляє батьківський контейнер про зміну даних, а батьківський оновляє стейт через функцію setState. Ця функція заставляє перерендеритись дочірні компоненти.
Завдяки хуку useState у функціональних компонентах з’явилась можливість зберігати дані, не використовуючи класові компоненти. Розгляньмо це детальніше.
Класові компоненти
Щоб створити класовий компонент, потрібно успадкувати клас Component із бібліотеки React. Обов’язковим є метод render, який повертає вигляд компоненти. Якщо вам потрібен початковий стан, а також прив’язати функції до класу, то необхідно реалізувати конструктор:
class Biels extends React.Component { constructor(props){ super(props); this.state={amount:0} // не потрібно привязувати функцію до контексту, коли метод класу є //стрілочним this.addAmount = this.addAmount.bind(this); } function addAmount() { //функція додає кожного разу до суми 100 і оновлює стейт this.setState((state, props)=>({ amount: state.amount + 100 })}); } // стрілочна функція використовує зовнішній this const minusAmount = () => { //функція віднімає кожного разу від суми 100 і оновлює стейт this.setState((state, props)=>({ amount: state.amount - 100 })}); } render() { return <div> <h4>Загальна сума {this.state.amounth}</h4> <button onClick={this.addAmount}>Додати 100 до суми</button> <button onClick={this.minusAmount}>Відняти 100 від суми</button> </div> }}
Пропси у класових компонентах і хуках
У класовому компоненті для одержання пропсів використовується об’єкт props. Наприклад, можна отримати колір, який переданий у класовий компонент таким чином this.props.color
<Button color=”red” />
Якщо брати функціональні компоненти, то об’єкт props береться з параметрів функції:
function Button(props) { return (<button style={{color:props.color}}>ОК</button>); }
Як бачимо, використання пропсів у класових та функціональних компонентах має незначні відмінності.
Життєвий цикл компонентів
У класових компонентах для реагування на зміну стану використовують такі основні методи: componentDidMount, componentWillUnmount та componentDidUpdate.
Метод componentDidMount викликається безпосередньо перед рендером. У ньому можна робити запити, асинхронні дії. Можете бути впевнені, що він буде викликаний тільки один раз. Далі під час змін компонентів буде викликатись метод componentDidUpdate (prevProps, prevState, snapshot). Тут ви можете спостерігати за зміною пропсів та стейту і реагувати на це. Наприклад, реагувати на введення даних, і коли всі дані будуть введені, зробити кнопку активною.
Метод componentWillUnmount викликається перед видаленням компонент з DOM дерева. Тут можна видаляти listeners, які ви могли оголосити у componentDidMount. Наприклад, оновлювати сторінку кожні 30 секунд.
У функціональних компонентах для цього використовується хук useEffect. Він поєднує всі три методи, які я згадував раніше. Приймає функцію, яка буде спрацьовувати при зміні другого параметру (масиву залежностей).
Нижче наведено такий самий функціонал, але з використанням класового компоненту:
class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } componentDidMount() { document.title = `Ви натиснули ${this.state.count} разів`; } componentDidUpdate() { document.title = `Ви натиснули ${this.state.count} разів`; } render() { return ( <div> <p>Ви натиснули {this.state.count} разів</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Натисни мене </button> </div> ); } }
Приклад хуку, який реагує на зміну розміру вікна:
useEffect(() => { // функція реагує на зміну розміру вікна function handleResize() { setWindowSize({ width: window.innerWidth, height: window.innerHeight, }); } // додавання обробника подій window.addEventListener("resize", handleResize); handleResize(); // видалення обробника подій return () => window.removeEventListener("resize", handleResize); }, []);
у класових компонентах обов’язковий метод render. Він повинен бути чистим (не можна змінювати у ньому state, оскільки це викличе зациклення). У функціональних компонентах для цього використовується звичайний return.
Хук useEffect асинхронний і виконується після того, як React оновив DOM, але це не означає, що зміни не відображаються відразу.
Хук useLayoutEffect також діє схожим чином з useEffect, за винятком того, що він виконується синхронно, що означає, що він виконується перед тим, як браузер відобразить зміни. Однак, використання useLayoutEffect може призвести до погіршення продуктивності, оскільки він може блокувати відображення змін на екрані до тих пір, поки він не завершить свою роботу. useLayoutEffect має такий самий інтерфейс як і useEffect, тому на цьому не будемо детальніше зупинятись.
Оскільки хуки викликаються кожного разу при рендері, для того щоб уникнути дороговартісних обчислень при кожному перерендері, можна використовувати useMemo та useCallback.
useMemo
const value = useMemo(()=>calculations (a,b,c),[a,b,c];
При першому рендері useMemo викликає функцію calculations та повертає значення value. Якщо аргументи a,b,c не змінились, то при наступних рендерах useMemo поверне мемонізоване значення (тобто функція calculations викликатись не буде). Це важливо при довгих обчисленнях, але зловживати цим не варто, бо useMemo можете витратити зайві ресурси.
useCallback
Мемонізована функція не зміниться, доки не зміниться якась із її залежностей (a,b,c);
const memoFunctionA= useCallback( () => { functionA(a, b, c); }, [a, b, c], );
Також є багато інших хуків, використання яких має певні спільні правила. Хуки можна використовувати тільки на найвищому рівні. Не можна ставити їх в умовні оператори, цикли тощо.
Хуки можна використовувати тільки в React функціональних компонентах. Вийняток для цього правила — це користувацькі хуки.
На завершення
Класові та функціональні компоненти мають спільні та протилежні риси. Ви зможете керувати станом класового компонента, якщо знаєте методи життєвого циклу. Таке ж правило діє для функціональних компонентів. Знаючи основні принципи роботи компонентів, ви зможете з легкістю їх оновити.
24 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів