Относительно недавно вышла версия React.js 16.8, с которой нам стали доступны хуки. Концепция хуков позволяет писать полноценные функциональные компоненты, используя все возможности React, и позволяет делать это во многом более удобно, чем мы это делали с помощью классов.
Многие восприняли появление хуков с критикой, и в этой статье я хотел бы рассказать о некоторых важных преимуществах, которые нам дают функциональные компоненты с хуками, и почему нам стоит перейти на них.
Я намеренно не буду углубляться в детали использования хуков. Это не очень важно для понимания примеров в этой статье, достаточно общего понимания работы React. Если вы хотите почитать именно на эту тему, информация о хуках есть в документации, и если эта тема будет интересна, я напишу статью подробнее о том когда, какие, и как правильно использовать хуки.
Хуки делают переиспользование кода удобнее
Давайте представим компонент, который рендерит простую форму. Что-то, что просто выведет несколько инпутов и позволит нам их редактировать.
Примерно так, если сильно упростить, этот компонент выглядел бы в виде класса:
class Form extends React.Component {
state = {
// Значения полей
fields: {},
};
render() {
return (
<form>
{/* Рендер инпутов формы */}
</form>
);
};
}
Теперь представим, что мы хотим автоматически сохранять значения полей при их изменении. Предлагаю опустить объявления дополнительных функций, вроде shallowEqual
и debounce
.
class Form extends React.Component {
constructor(props) {
super(props);
this.saveToDraft = debounce(500, this.saveToDraft);
};
state = {
// Значения полей
fields: {},
// Данные, которые нам нужны для сохранения черновика
draft: {
isSaving: false,
lastSaved: null,
},
};
saveToDraft = (data) => {
if (this.state.isSaving) {
return;
}
this.setState({
isSaving: true,
});
makeSomeAPICall().then(() => {
this.setState({
isSaving: false,
lastSaved: new Date(),
})
});
}
componentDidUpdate(prevProps, prevState) {
if (!shallowEqual(prevState.fields, this.state.fields)) {
this.saveToDraft(this.state.fields);
}
}
render() {
return (
<form>
{/* Рендер информации о том, когда был сохранен черновик */}
{/* Рендер инпутов формы */}
</form>
);
};
}
Тот же пример, но с хуками:
const Form = () => {
// Стейт для значений формы
const [fields, setFields] = useState({});
const [draftIsSaving, setDraftIsSaving] = useState(false);
const [draftLastSaved, setDraftLastSaved] = useState(false);
useEffect(() => {
const id = setTimeout(() => {
if (draftIsSaving) {
return;
}
setDraftIsSaving(true);
makeSomeAPICall().then(() => {
setDraftIsSaving(false);
setDraftLastSaved(new Date());
});
}, 500);
return () => clearTimeout(id);
}, [fields]);
return (
<form>
{/* Рендер информации о том, когда был сохранен черновик */}
{/* Рендер инпутов формы */}
</form>
);
}
Как мы видим, разница пока не очень большая. Мы поменяли стейт на хук useState
и вызываем сохранение в черновик не в componentDidUpdate
, а после рендера компонента с помощью хука useEffect
.
Отличие, которое я хочу здесь показать (есть и другие, о них будет ниже): мы можем вынести этот код и использовать в другом месте:
// Хук useDraft вполне можно вынести в отдельный файл
const useDraft = (fields) => {
const [draftIsSaving, setDraftIsSaving] = useState(false);
const [draftLastSaved, setDraftLastSaved] = useState(false);
useEffect(() => {
const id = setTimeout(() => {
if (draftIsSaving) {
return;
}
setDraftIsSaving(true);
makeSomeAPICall().then(() => {
setDraftIsSaving(false);
setDraftLastSaved(new Date());
});
}, 500);
return () => clearTimeout(id);
}, [fields]);
return [draftIsSaving, draftLastSaved];
}
const Form = () => {
// Стейт для значений формы
const [fields, setFields] = useState({});
const [draftIsSaving, draftLastSaved] = useDraft(fields);
return (
<form>
{/* Рендер информации о том, когда был сохранен черновик */}
{/* Рендер инпутов формы */}
</form>
);
}
Теперь мы можем использовать хук useDraft
, который только что написали, в других компонентах! Это, конечно, очень упрощенный пример, но переиспользование однотипного функционала — очень полезная возможность.
Хуки позволяют писать более интуитивно-понятный код
Представьте компонент (пока в виде класса), который, например, выводит окно текущего чата, список возможных получателей и форму отправки сообщения. Что-то такое:
class ChatApp extends React.Component {
state = {
currentChat: null,
};
handleSubmit = (messageData) => {
makeSomeAPICall(SEND_URL, messageData)
.then(() => {
alert(`Сообщение в чат ${this.state.currentChat} отправлено`);
});
};
render() {
return (
<Fragment>
<ChatsList changeChat={currentChat => {
this.setState({ currentChat });
}} />
<CurrentChat id={currentChat} />
<MessageForm onSubmit={this.handleSubmit} />
</Fragment>
);
};
}
Пример очень условный, но для демонстрации вполне подойдет. Представьте такие действия пользователя:
- Открыть чат 1
- Отправить сообщение (представим, что запрос идет долго)
- Открыть чат 2
- Получить сообщение об успешной отправке:
- "Сообщение в чат 2 отправлено"
Но ведь сообщение отправлялось в чат 1? Так произошло из-за того, что метод класса работал не с тем значением, которое было в момент отправки, а с тем, которое было уже на момент завершения запроса. Это не было бы проблемой в таком простом случае, но исправление такого поведения во-первых, потребует дополнительной внимательности и дополнительной обработки, и во-вторых, может быть источником багов.
В случае с функциональным компонентом поведение отличается:
const ChatApp = () => {
const [currentChat, setCurrentChat] = useState(null);
const handleSubmit = useCallback(
(messageData) => {
makeSomeAPICall(SEND_URL, messageData)
.then(() => {
alert(`Сообщение в чат ${currentChat} отправлено`);
});
},
[currentChat]
);
render() {
return (
<Fragment>
<ChatsList changeChat={setCurrentChat} />
<CurrentChat id={currentChat} />
<MessageForm onSubmit={handleSubmit} />
</Fragment>
);
};
}
Представьте те же действия пользователя:
- Открыть чат 1
- Отправить сообщение (запрос снова идет долго)
- Открыть чат 2
- Получить сообщение об успешной отправке:
- "Сообщение в чат 1 отправлено"
Итак, что же поменялось? Поменялось то, что теперь для каждого рендера, для котрого отличается currentChat
мы создаем новый метод. Это позволяет нам совсем не думать о том, поменяется ли что-то в будущем — мы работаем с тем, что имеем сейчас. Каждый рендер компонента замыкает в себе все, что к нему относится.
Хуки избавляют нас от жизненного цикла
Этот пункт сильно пересекается с предыдущим. React — библиотека для декларативного описания интерфейса. Декларативность сильно облегчает написание и поддержку компонентов, позволяет меньше думать о том, что было бы нужно сделать императивно, если бы мы не использовали React.
Несмотря на это, при использовании классов, мы сталкиваемся с жизненным циклом компонента. Если не углубляться, это выглядит так:
- Монтирование компонента
- Обновление компонента (при изменении
state
илиprops
) - Демонтирование компонента
Это кажется удобным, но я убежден в том, что это удобно исключительно из-за привычности. Этот подход не похож на React.
Вместо этого, функциональные компоненты с хуками позволяют нам писать компоненты, думая не о жизненном цикле, а о синхронизации. Мы пишем функцию так, чтобы ее результат однозначно отражал состояние интерфейса в зависимости от внешних параметров и внутреннего состояния.
Хук useEffect
, который многими воспринимается как прямая замена componentDidMount
, componentDidUpdate
и так далее, на самом деле предназначен для другого. При его использовании мы как бы говорим реакту: "После того, как отрендеришь это, выполни, пожалуйста, эти эффекты".
Вот хороший пример работы компонента со счетчиком кликов из большой статьи про useEffect:
- React: Скажи мне, что отрендерить с таким состоянием.
- Ваш компонент:
- Вот результат рендера:
<p>Вы кликнули 0 раз</p>
. - И еще, пожалуйста, выполни этот эффект, когда закончишь:
() => { document.title = 'Вы кликнули 0 раз' }
.
- Вот результат рендера:
- React: Окей. Обновляю интерфейс. Эй, брайзер, я обновляю DOM
- Браузер: Отлично, я отрисовал.
- React: Супер, теперь я вызову эффект, который получил от компонента.
- Запускается
() => { document.title = 'Вы кликнули 0 раз' }
- Запускается
Намного более декларативно, не правда ли?
Итоги
React Hooks позволяют нам избавиться от некоторых проблем и облегчить восприятие и написание кода компонентов. Нужно просто поменять ментальную модель, которую мы на них применяем. Функциональные компоненты по сути — функции интерфейса от параметров. Они описывают все так, как оно должно быть в любой момент времени, и помогают не думать о том, как реагировать на изменения.
Да, иногда нужно научиться их использовать правильно, но точно так же и компоненты в виде классов мы научились применять не сразу.
JustDont
Я конечно понимаю, что в это может быть сложно поверить, но — если вы из класса вытащите всю отчуждаемую логику, то вы точно так же будете и с классами переиспользовать код.
Классы не заставляют вас прибивать гвоздями вашу логику к ним. Особенно учитывая, что классы компонентов — это голый view, которому и следует быть лишенным всякой логики.
kshshe Автор
А если заменить на «Хуки позволяют удобнее переиспользовать код»?
Зачастую ведь компоненты это не только view, взять хотя бы пример с сохранением формы.
JustDont
Я не вижу никаких существенных отличий между тем, дернете ли вы класс модели и метод, выполняющий некую логику, или же сделаете то же самое, но функцией.
Скажем так, если усиленно бороться за чистоту MVC, можно и форму представить как чистый view, а всю логику сделать отчуждаемой. Другое дело, что на практике подобным заниматься, если только у нас не какой-то очень особенный проект про формы — это такое себе занятие, и никто в здравом уме не будет выпиливать состояние из формы просто ради соблюдения паттернов.
kshshe Автор
Основное отличие в
this
, к которому все привязано в классах. То есть да, отделить можно, но так же ли удобно, как просто вырезать несколько строк кода из компонента?JustDont
… который собственно создаёт контекст для операций. Впрочем, статика тоже никуда не делась.
Это уже уровень холивара на тему объектного и функционального программирования: я вам скажу, что у вас получится спагетти-код, где данные сложены в одну кучу, а действия — в другую, и поди разберись, что там и как должно делаться и над какими данными. Вы, если зайдете с позиций функциональщины — тоже найдете много теплых слов в мой адрес.
Но в общем я это всё начал как раз к тому, чтоб продемонстрировать, что разговоры «хуки лучше классов» почти кругом сводятся к «функциональное программирование лучше ООП». То есть, на самом деле, всё примерно то же самое, и упирается во вкусовщину того, у кого как мысли текут. ООПшно или функционально.
staticlab
А в хуке фактически этот же this на компонент, но только неявный и скрытый в реализации реактовских хуков.
kshshe Автор
Хорошо, что-то, определяющее компонент есть, но разве не важно то, что здесь оно скрыто от нас и нам совсем не нужно об этом думать?
staticlab
Это плохо. Хотя бы и потому, что весьма неочевидно. Вангую, что в сложном приложении это будет потенциальный источник ошибок. Вдобавок, выглядит функция хука как обычная функция, но просто так запустить её не получится — будет ошибка. Значит, даже чтобы протестировать, придётся костылять тестовый компонент в отличие от классов-сервисов и обсерверов rxjs.
faiwer
Так и есть. Сложные ситуации всплывают довольно скоро. Самый такой частый и простой use case это когда в useEffect или в useCallback у нас какой-нибудь async-method есть. На момент его вызова его нутро цепляется к тому замыканию в котором оно было запущено, и соответственно к замороженным иммутабельным state-данным из useState. Как-то их обновить при этом уже невозможно, ибо замыкание есть замыкание. Внутри async-метода может быть несколько асинхронных await, и каждый из них будет в итоге обращаться к потенциально устаревшим данным.
Решается такое путём использования ручных useRef. Это аналог classes-properties. Но в целом момент не совсем очевидный и может очень многих поставить во временный тупик.
kshshe Автор
Просто предположение: а не помогут для такого возвращаемые из эффектов unsubscribe-функции? В них можно было бы сделать что-то вроде отмены.
faiwer
До тех пор пока у вас замыкания и иммутабельные данные ? ничего вам не поможет, включая unsubscribe.
Что вы туда поместите? Ну допустим какой-нибудь флаг вида isUnmounted: bool. Ок. Как вы его туда поместите?
setState
? Ну ок. Вы вunsubscribeHandler
вы сделалиsetIsUnmounted(true)
, и ваш компонент принудительно пере-render-ился. ВашcallbackHandler
вuseCallback
пересоздался. Ок. Теперь у нас есть флагisUnmounted === true
. Помогло ли нам это хоть на грамм? Нет. Ведь внутри уже ранее запущенного async-метода (читайте конечного автомата в замыкании) видны только те значения которые были актуальны на момент его запуска (того самого замыкания на момент запуска). А у нас же иммутабельность и замыкания каждый раз новые. Fail. Приехали :)Собственно почему
useRef
и ручноеref.current = ...
решает проблему? А он мутабелен и сохраняет ссылочную целостность. Он вне контекста замыкания, т.к. useRef всегда возвращает одно и тоже значение. Бинго :) Приветclass-properties
:-Dkshshe Автор
Я понял мысль, мутабельные свойства иногда действительно нужны, но я не совсем об этом. Я имел в виду создание флага в том же замыкании, вроде такого:
faiwer
Я пробовал так это обыграть. Но обломался. isCanceled будет выставлен в true после первого же setState в doThing. Будет rerender, а это в свою очередь приведёт к unsubscribe-у. А вернуть isCanceled в true будет уже не кому.
lexey111
Довольно спорно… эта логика невизуальная? Она должна жить в отдельном сервисе без привязки к компоненту. Асинхронная? Имеем события, подписки или какой другой стандартный путь для оповещений. Причём скорее всего даже вообще не-реакт, а чистый js/ts, переиспользуй-не хочу. RxJs, pub/sub, Redux…
Лично у меня ощущения от использования хуков — «как бы получить возможности классовых компонентов и „взрослой“* архитектуры и при этом поменьше вникать в эту самую архитектуру». Не знаю, как по мне рано или поздно всё равно придётся разбираться, только наворочено к этому моменту будет больше и рефакторить придётся тоже больше.
* взрослой = явное управление стейтом и потоками, разделение ответственности, DI, прочий далеко не всем нужный хлам.
Koneru
DI назвать хламом без аргументации. Сильно.
lexey111
В следующий раз не забуду поставить тэги «ирония», «сарказм» и «шутка».
nsinreal
В typescript например, все не так радужно с ООП. Реюз возможен через HoC, а он порождает огромные проблемы с типизацией. Реюз возможен через миксины, но миксины заставляют дублировать интерфейс.
JustDont
Да ну здрасьте. Что вам мешает наследовать классы в TS и делать всё без HOC? Там очень обычный ООП.
Если вы про конкретно реакт-компоненты, то там наследование тоже никто не запрещает, если чо. Просто не рекомендуется, потому что через наследование у вас выйдет тесное связывание компонентов, в то время как через композицию можно сделать и не тесное.
nsinreal
Здрасьте.
Например, то, что в TS нет поддержки мульти-наследования. Не считая миксинов, но см. выше.Наследование — это пример идеи хорошей в теории, но почему-то на практике редко используемой. Как вы думаете, почему?
Потому что джаваскриптеры не умеют в ООП.Потому что наследование на практике не работает.Мне однако безынтересно обсуждать код без кода. Если вы верите в наследование, то попрошу вас предоставить сэмпл «реюзабельного кода» на наследовании. Так, чтобы его аналогом был компонент с четырьма хукам (hooks) или четырьма хоками (HoC). Например: connect, использование двух контекстов и мемоизация коллбека.
JustDont
Так вот именно. Вы уж сначала там у себя разберитесь, что то ли HOC ужасны, то ли наследование на практике не работает. А то вы почему-то пишете про ужас-ужас, а в следующем посте у вас уже получается, что ужас-ужас — на самом деле наименьший из возможных ужасов.
Угу, а концептуально мульти-наследование — штука очень интересная и способная породить бесконечно количество граблей для последующего наступания.
Странно, чё это люди так не любят мульти-наследование в языки прикручивать?
Мне не слишком ясен ваш изначальный посыл. В ООП плохо? А где хорошо-то тогда? Везде плохо. В TS с ООП плохо? А где на фронтэнде с ООП хорошо тогда?
Выводы-то какие?
nsinreal
Я обожаю ситуацию, когда не понимаешь: твой собеседник тролль или вы просто друг друга не поняли?
1-е сообщение (вы): «Я конечно понимаю, что в это может быть сложно поверить, но — если вы из класса вытащите всю отчуждаемую логику, то вы точно так же будете и с классами переиспользовать код.»
Ну вот вроде бы я должен был понять это сообщение как «реюз можно и на ООП строить, хуки не нужны».
2-е сообщение (я): «В typescript например, все не так радужно с ООП. Реюз возможен через HoC, а он порождает огромные проблемы с типизацией. Реюз возможен через миксины, но миксины заставляют дублировать интерфейс.»
Ну вот вроде бы вы должны были понять это сообщение как «в typescript реюз нельзя на ООП строить».
3-е сообщение (вы): «Да ну здрасьте. Что вам мешает наследовать классы в TS и делать всё без HOC? Там очень обычный ООП.»
Ну вот вроде я должен был понять это сообщение как «да ну шо вы говорите, в typescript на ООП отлично делает реюз»
4-е сообщения (я): «Например, то, что в TS нет поддержки мульти-наследования. Не считая миксинов, но см. выше»
Ну вот вроде бы вы должны были понять, что «я крайне несогласен с тем, что TS можно делать реюз на ООП, потому что отсутствует мульти-наследование»
5-е сообщение (вы): «Угу, а концептуально мульти-наследование — штука очень интересная и способная породить бесконечно количество граблей для последующего наступания.»
Я вот должен это как понять? Что если мульти-наследование — штука плохая, то это значит, что в typescript круто делается реюз через ООП?
JustDont
Я отвечаю на сообщения, а не на ветки, особенно когда времени прошло уже порядочно, и контекст забылся. Поэтому я, читая ваше «в typescript c ООП плохо» — понял его именно так. Что в TS с ООП плохо. А у вас плохо не в TS, а в реакте с TS, что всё-таки большая разница.
Если вам интересно именно о реакте — то на мой взгляд в голом реакте вообще в любом случае плохо с стейт менеджментом, и надо искать сторонние средства или же строить свой велосипед, если хочется написать на реакте что-то сложнее пары формочек и лендинга.
И когда вы стейт менеджмент будете делать не на lifecycle-методах, а сторонними средствами — вот тогда вы логику от компонентов сможете спокойно унести (весь необходимый вам стейт менеджмент будет делаться не в компонентах, а отдельно) и не париться всеми этими довольно грустными проблемами модульности голого реакта. Реакт — это не фреймворк, в конце концов, без дополнительных велосипедов для него при написании чего-то сложного так просто не обходится.
Будет ли проще сделать свой стейт менеджмент на хуках, чем на HOC? Ну да, скорее всего. А еще проще будет вообще готовый взять, даже если это не к ночи будь помянутый редакс.
nsinreal
Да, некоторые люди используют реактовский state (без state management).
Да, статья написана с примерами кода без state management.
Но это не значит, что хуки не решают каких-то проблем.
Реакт занимается всем view, а не только рендерингом. Сложную view-логику вы не сможете переложить на state management, потому что там ей не месте.
Не говоря уже о том, что react hooks упрощают использование connect, contexts и прочих штук.
JustDont
А я бы сказал, что именно сложную логику и нужно нафиг утаскивать из компонентов, даже если с точки зрения сферической архитектуры в вакууме это нечто такое, что безусловно и полностью относится к view (стейт всегда можно разбить сколь угодно малыми частями, главное не создавать себе проблем со сложными зависимостями между этими частями).
Если б они не решали никаких проблем — их бы никто и не написал, не?
Я вроде бы обратного и не утверждал. Я лишь комментировал чрезмерные обобщения а-ля «хуки позволяют переиспользовать код», хотя на самом деле там должно быть «хуки позволяют переиспользовать код*», а под * — много мелкого текста о границах применимости и о том, что во многих случаях с этим и без хуков всё нормально.
rzcoder
Сначала добавляют в реакт функциональные компоненты, которые вроде как лишены бизнес логики, чистые как слеза младенца и легко тестируемы, и вообще сказка просто.
А теперь вдруг функция вызывает какие-то неведомые эффекты, читает из чертегознает где хранящегося стейта, создает неопределенное количество замыканий… И все это на каждый вызов рендера.
defusioner
Похоже на Вью
nuit
Создание замыканий не такая уж и проблема, а вот то что многие начнут писать код с утечками памяти из-за closure context sharing'а — это печально.
zompin
Мне очевиднее componentDidMount и componentWillUnmount, а не useEffect(() => {}). Во тором случае есть нюансы, если не передать список переменных, то он будет вообще работать как componentDidUpdate. Дело привычки конечно, но не очень очевидно.
kshshe Автор
Тут важно понимать, что
useEffect
не является заменой для жизненного цикла компонента. Это в принципе другая концепция, когда важен не компонент, а конкретный рендер.inv2004
Почему во всех подобных статьях сравнение хуков с чистым реактом на стейтах, а не с react-redux?, в этом случае и стейты исчезают и всё что пишется — одна строка биндинга компонента и редукса.
kshshe Автор
Скорее всего, потому что без стейта компоненты на классах не имели смысла, и такие статьи обращены к тем, кто их использует по каким-либо причинам. Ну и не все и не всегда выносится в redux.
nsinreal
Потому что хуки заменяют в том числе и connect.
inv2004
По-этому и хотелось бы сравнить именно эти два подхода.
nsinreal
Ну вот примеры для redux-react-hook. Без прямых сравнений, увы. Просто, чтобы вы могли понять как это примерно выглядит.
Выдергивание стейта:
Выдергивание стейта по параметризированному селектору:
Диспатчинг:
gibson_dev
Это же на каждый рендер будет создваться новая функция в замыкании(
kshshe Автор
Не совсем так. На каждый, где отличается
props.id
, а это редкие ситуации.faiwer
Что вы, что вы. Абсолютно на каждый. Никакие
props.id
тут ничего не поменяют. Другое дело, чтоuseCallback
(а лучшеuseAutoCallback
) вернёт ранее созданный метод, а не новосозданный, еслиprops.id
не был изменён.Все эти
useEffect
,useLayoutEffect
,useCallback
и что там ещё есть, таки действительно порождают новые методы в замыкании раз за разом. Другой вопрос, что это обычно не является критичным с точки зрения производительности.kshshe Автор
Если говорить строго, то да, на каждый, конечно. Просто дальше она никуда не попадет и ни на что не повлияет.
nsinreal
Да, действительно, будет каждый рендер создаваться новая функция в замыкании — это лишняя нагрузка на GC, что совсем не радует.
НО! То, что функция создается — еще не значит, что именно она будет использоваться. Здесь useCallback используется для мемоизации. И поэтому дочерние компоненты не будут зазря перерендериваться.
theaklair
Зачем у вас в примерах кода после
extends React.Component
идет знак "="?
kshshe Автор
Писал все в текстовом редакторе без обработки синтаксиса, на автомате поставил
=
. Спасибо за замечание, поправлю.theaklair
Код ревью :)
А по делу — спасибо, что делитесь с русскоязычным сообществом.
botyaslonim
Хуки позволяют писать более интуитивно-понятный код
— ну конечно, всякий новый функционал, заметно отличный от того, к чему мы привыкли за годы, всегда объявляется «интуитивно-понятным»
Монтирование компонента
Обновление компонента (при изменении state или props)
Демонтирование компонента
Это кажется удобным, но я убежден в том, что это удобно исключительно из-за привычности. Этот подход не похож на React.
— что???!?! Не похож на React? Базовый функционал самого Реакта?
staticlab
Так лишь бы не классы, ведь классы — это злое зло :) (Неважно, что в 2015 они презентовались как «Наконец-то в JS появятся классы как во взрослых языках, теперь можно будет делать ООП!»)
justboris
Не вижу противоречия. Люди, продвигавшие классы в Javascript, и авторы React-хуков – это разные люди, с разными целями и предпочтениями.
taujavarob
Более точно — у нас появился React.js 2.0 (если кто ещё НЕ заметил сам).
Произошло тоже что с Angular — когда появился Angular 2.0
Всё что вы знали и писали до этого (до React.js 16.8) можно забыть и выкинуть в мусорку.
Никто в здравом уме НЕ будет мешать хуки и классы в одном коде.
А нам НЕ обещают, что классы будут вообще развивать в будущем.
Точнее, — обещают НЕ развивать классы в React.js вообще.
Нет, их не выкинут — но смысл использовать "классовый подход" в новых проектах — исчезает. До нуля.
Вывод — выкинуть все учебники и патерны и учить по новой хуки.
Это будущее React.js которое уже наступило, хотите вы этого или нет.