Різниця між 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 функціональних компонентах. Вийняток для цього правила — це користувацькі хуки.

На завершення

Класові та функціональні компоненти мають спільні та протилежні риси. Ви зможете керувати станом класового компонента, якщо знаєте методи життєвого циклу. Таке ж правило діє для функціональних компонентів. Знаючи основні принципи роботи компонентів, ви зможете з легкістю їх оновити.

👍ПодобаєтьсяСподобалось5
До обраногоВ обраному2
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Class Components
2023

Очень интересно.

Гарна стаття для того щоб згадати що таке класові компоненти тим хто має щастя не працювати з ними, але має досвід роботи з ними. Хоча, звісно стаття не покриває всього функціоналу функціональних і класових компонентів та не приводить глибокий аналіз між ними, проте я маю сумніви що це було метою автора. Але згадати цей легасі круто)

FYI Class Components в React 18 вже все... з усіх поточних API’шок їх повидаляли, лишився лиш compat.

Як на мене, стаття дуже поверхнева. Дуже багато чого опущено, наприклад shouldComponentUpdate, getDerivedStateFromProps, componentDidCatch і так далі. Я часто на співбесіді питаю різницю між класовими та функціональними компонентами, та як можна реалізувати методи життєвого циклу класового компонента у функціональному

Взагалі нема аналізу найменуваннь, а вони ж визначають сенс.
Я правильно розумію назву useEffect — «використовувати ефект», тобто задіяти додаткову функціональність? Чому use? Чому не просто «effect», «callback» тощо.

Звичайно, все не можна описати, і немає межі ідеалу, тому є деякі моменти, які не описані в статті.

Та ні, саме з цього треба починати, з аналізу який сенс розробники заклали в ту чи іншу назву.

Я правильно розумію назву useEffect — «використовувати ефект»

Тому що це і є фактично use effect, де effect це функція яка передається в хук

Жуткий жаргон, «пропси». «Властивості» !

Згідно офіційної документації React в термінології використовується слово props для позначення вхідних даних для компоненти. https://uk.reactjs.org/docs/components-and-props.html

props — скорочено properties, шо в перекладі — властивості )

Ось офіційна термінологія. Розробники React коли починали писати свою бібліотеку створили перелік термінів, якими я користувався при написані даної статті. Якщо вам цікаво можете ознайомитись із ними. І ці терміни було вжито для того щоб не було колізій(накладань,чи підміна) певних понять, а також щоб React розробники могли нею користуватись та розуміти одне одного. Якщо вам цікаво можете це прочитати.https://uk.reactjs.org/docs/glossary.html

Щоб не плутатись, наприклад є якийсь классовий компонент, у нього є всякі property як обʼєкта і є props. Тоді в такому перекладі буде і те «властивість» і друге теж «властивість»:)

В даному випадку наводилась аналогія, для того щоб користувач міг зрозуміти як схожі використання хуку useEffect та методів життєвого циклу класу. Тим більше згідно офіційної документації https://uk.reactjs.org/docs/hooks-effect.html вказано що, «якщо ви знайомі з класовими методами життєвого циклу React, то уявляйте хук useEffect, як комбінацію componentDidMount, componentDidUpdate та componentWillUnmount».

Усі хуки є синхронні, і блокують рендер конкретної компоненти. Функція в Useeffect запускається при маунті і потім при кожній зміні депенденсі. Uselayouteffect корисний для того, щоб зробити калькуляції після рендеру компоненти в дом. При цьому декларація(виклик) хука все ще синхронна.

А до чого цей параграф про хуки до назви статті — мені невідомо.

Оскільки хуки викликаються кожного разу при рендері, щоб уникнути цього можна використовувати useMemo та useCallback.

Це речення складається з двох частин які не пов‘язані одна з іншою. Ви не можете уникнути виклику хуків. Хуки викликаються щоразу бо це так працює реакт і жаваскрипт. Функція яка передається в useEffect викликається тільки за певних умов, як я описав вище. Виклик хуків не можна сховати в useMemo, як автор сам і написав. Можливо тут пропущене речення про те що рендер функція фукликається кожен ререндер, тому важливо мемоізувати складні обчислення, для того є useCallback і useMemo.

Для компонентів класу this.setState викликає пакетний виклик усередині event handler. Але що станеться, якщо стан буде оновлено поза event handlerі за допомогою хука useState?А буде ось що,якщо зміни стану запустяться асинхронно (наприклад, загорнуті у проміс), вони не будуть групуватися; якщо вони запускаються безпосередньо, вони будуть групуватися та оновлюватися пакетно.Для цього буде цілком достатньо створити лише дві кнопки, одна запускає зміни стану, загорнуті у проміс, інша запускає зміни стану безпосередньо.Ви можете використовувати useEffect для доступу до останнього стану після його оновлення. Якщо у вас є кілька перехоплювачів стану для підвищення перформенсу ви хочете відстежувати оновлення лише частини з них. Ви можете передати стан у вигляді масиву як другий аргумент функції useEffect.

Так хороше зауваження, хуки useMemo та useCallback дійсно викличуться при кожному рендері, але якщо аргументи їхні не змінились, то повернуть мемонізоване значення, замість того щоб кожного разу виконувати складні обчислення

мемонізоване

А це шо за новояз? Чому не «кешоване»?

ты две недели как в жс и уже статьи обучающие пишешь

this.state={amount =0;}

Це якийсь новий Джаваскрипт

Та ні це скоріж за все неуважність.

Функціональний компонент — це в основному функція JavaScript, яка повертає елемент React. Він приймає властивості як аргумент і повертає JSX
Компоненти класу складніші за функціональні компоненти, включаючи конструктори, методи життєвого циклу, функцію render() і керування станом (даними). Компонентами класу є класи ES6.Скоро відімре...
Хлопці, але ж ви про головне не розповіли. Ваші компоненти набагато легше буде повторно використовувати та аналізувати, якщо ви розділите їх на дві категорії, презаентаційні та конетейнерні, ще їх називають Stateful и Pure. Презентаційні компоненти можуть містити як презентаційні, так і контейнерні компоненти всередині та зазвичай має іншу розмітку DOM і власні стилі.
Вони часть дозволяють отримувати дані через this.props.children.Не мають залежності від інших частин додатків, таких як дії чи сховище Flux, не вказують, як дані завантажуються або змінюються.Отримують дані і зворотні виклики виключно через пропсу.Дуже рідко мають власний стан (коли вони є, це стан інтерфейсу користувача, а не дані). Можуть записуються як функціональні компоненти, якщо тільки їм не потрібно стан, перехоплювачі життєвого циклу або оптимізація продуктивності.Приклади: сторінка, боковая панель, історія, інформація про користувача, список.
До контейнерних компонентів, які можуть містити як презентаційні, так і контейнерні компоненти всередині, але не можуть мати власної розмітки DOM, за винятком деяких розділів-оболонок, і ніколи не має стилів! Вони надають дані та поведінку презентаційним або іншим компонентам контейнера. Викликають Flux і повертають їх як зворотні виклики компонентам представлення.
Часто мають стан, оскільки вони, як правило, є джерелами даних. Зазвичай генеруються з використанням компонентів вищого порядку, таких як connect() з React Redux, createContainer() з Relay або Container.create() з Flux Utils. Наприклад: UserPage, FollowersSidebar, StoryContainer, FollowedUserList.
Отже,функціональні компоненти краще ніж класові, тому що їх легше перевірити.Вони мають кращу продуктивність.Функціональні компоненти легко налагодити.Коду стає менше.
Функціональний підхід допоможе вам використовувати найкращі сучасні практики розробки ПО тіж самі хуки.Функціональні компоненти можуть зменшити зчеплення(reduce coupling).У компоненті класу керування станом виконується в одному об’єкті стану, але у функції ми можемо створити скільки завгодно хуків.
Висновок: Майбутнє за функціональними компонентами!

Так звичайно, є деякі моменти які не були описані в статті. Стаття акцентує увагу на те які зміни принесли з собою хуки та як перейти з класових до функціональних компонент і навпаки. Само собою компоненти задумані в React для повторного використання.

Підписатись на коментарі