Учебник по React: компоненты, хуки и производительность
Опубликовано: 2022-03-11Как было указано в первой части нашего руководства по React, начать работу с React относительно просто. Начните с использования Create React App (CRA), создайте новый проект и приступайте к разработке. К сожалению, со временем вы можете столкнуться с ситуацией, когда ваш код станет довольно сложно поддерживать, особенно если вы новичок в React. Компоненты могут стать излишне большими, или вы можете получить элементы, которые могли бы быть компонентами, но таковыми не являются, поэтому вы можете в конечном итоге написать повторяющийся код здесь и там.
Именно с этого вы должны по-настоящему начать свое путешествие в React — начать думать о решениях для разработки React.
Всякий раз, когда вы подходите к новому приложению, новому дизайну, который вам нужно преобразовать в приложение React позже, сначала попытайтесь решить, какие компоненты будут в вашем эскизе, как вы можете разделить эскиз, чтобы упростить управление ими, и какие элементы повторяющиеся (или их поведение, по крайней мере). Старайтесь не добавлять код, который может оказаться «полезным в будущем» — это может быть заманчиво, но это будущее может никогда не наступить, и вы сохраните эту дополнительную общую функцию/компонент с множеством настраиваемых параметров.
Также, если компонент длиннее, скажем, 2-3 высоты окна, возможно, его стоит выделить (если это возможно) — так его будет легче читать потом.
Контролируемые и неконтролируемые компоненты в React
В большинстве приложений требуется ввод данных и некоторая форма взаимодействия с пользователями, позволяющая им вводить что-либо, загружать файл, выбирать поле и т. д. React имеет дело с взаимодействием с пользователем двумя разными способами — контролируемые и неконтролируемые компоненты.
Значение контролируемых компонентов, как следует из их названия, контролируется React путем предоставления значения элементу, взаимодействующему с пользователем, в то время как неконтролируемые элементы не получают свойства значения. Благодаря этому у нас есть единственный источник правды, который является состоянием React, поэтому нет несоответствия между тем, что мы видим на экране, и тем, что мы имеем в настоящее время в нашем состоянии. Разработчику необходимо передать функцию, которая будет реагировать на взаимодействие пользователя с формой, которая будет менять свое состояние.
class ControlledInput extends React.Component { state = { value: "" }; onChange = (e) => this.setState({ value: e.target.value }); render() { return ( <input value={this.state.value} onChange={this.onChange}/> ); } }
В неуправляемых компонентах React нас не волнует, как меняется значение, но если мы хотим узнать точное значение, мы просто обращаемся к нему через ref.
class UncontrolledInput extends React.Component { input = React.createRef(); getValue = () => { console.log(this.input.current.value); }; render() { return ( <input ref={this.input}/> ); } }
Итак, что следует использовать, когда? Я бы сказал, что в большинстве случаев лучше всего использовать управляемые компоненты, но есть и исключения. Например, одним из случаев, когда вам нужно использовать неконтролируемые компоненты в React, является ввод типа file
, поскольку его значение доступно только для чтения и не может быть установлено программно (требуется взаимодействие с пользователем). Кроме того, я считаю, что контролируемые компоненты легче читать и с ними легче работать. Выполнение проверки для контролируемых компонентов основано на повторном рендеринге, состояние может быть изменено, и мы можем легко указать, что с входными данными что-то не так (например, с форматом или пустым).
ссылки
Мы уже упоминали refs
, которые являются специальной функцией, доступной в компонентах класса, пока в 16.8 не появились хуки.
Ссылки могут предоставить разработчику доступ к компоненту React или элементу DOM (в зависимости от типа, к которому мы прикрепляем ссылку) через ссылку. Считается хорошей практикой избегать их и использовать только в обязательных сценариях, так как они затрудняют чтение кода и нарушают поток данных сверху вниз. Тем не менее, есть случаи, когда они необходимы, особенно для элементов DOM (например, изменение фокуса программно). При присоединении к элементу компонента React вы можете свободно использовать методы внутри этого компонента, на который вы ссылаетесь. Тем не менее, этой практики следует избегать, поскольку есть лучшие способы справиться с ней (например, поднятие состояния и перемещение функций в родительские компоненты).
Рефы также могут быть выполнены тремя различными способами:
- Использование строкового литерала (устаревшего и его следует избегать),
- Используя функцию обратного вызова, которая устанавливается в атрибуте ref,
- Создавая ref как
React.createRef()
и привязывая его к свойству класса и получая к нему доступ через него (имейте в виду, что ссылки будут доступны из жизненного цикла componentDidMount).
Наконец, бывают случаи, когда ссылки не передаются, и случаи, когда вы хотите получить доступ к более глубокому элементу ссылки из текущего компонента (например, у вас есть компонент <Button>
, который имеет внутренний элемент DOM <input>
, и прямо сейчас вы находятся в компоненте <Row>
, и из компонента строки вы хотите иметь доступ для ввода функции фокуса DOM. Вот где вы должны использовать forwardRef
).
Один из случаев, когда ссылка не передается вниз, — это когда в компоненте используется компонент более высокого порядка — причина вполне понятна, поскольку ref
НЕ является prop
(аналогично key
), поэтому он не передается, поэтому он будет ссылаться на HOC
, а не на компонент, обернутый им. В таком случае мы можем использовать React.forwardRef
, который принимает props и refs в качестве аргументов, которые затем могут быть назначены prop
и переданы компоненту, к которому мы хотим получить доступ.
function withNewReference(Component) { class Hoc extends React.Component { render() { const {forwardedRef, ...props} = this.props; return <Component ref={forwardedRef} {...props}/>; } } return React.forwardRef((props, ref) => { return <Hoc {...props} forwardedRef={ref} />; }); }
Границы ошибки
Чем сложнее вещи, тем выше вероятность, что что-то пойдет не так. Вот почему границы ошибок являются частью React. Так как же они работают?
Если что-то пойдет не так и в качестве родителя нет границы ошибки, это приведет к сбою всего приложения React. Лучше не отображать информацию, чем вводить пользователей в заблуждение и отображать неверную информацию, но это не обязательно означает, что вы должны свернуть все приложение и показать белый экран. С границами ошибок у вас есть дополнительная степень гибкости, которую вы можете использовать. Вы можете либо использовать его во всем приложении и отображать сообщение об ошибке, либо использовать его в некоторых виджетах и просто не отображать их, либо вместо этих виджетов отображать небольшое количество информации.
Помните, что речь идет только о проблемах с декларативным кодом, а не с императивным кодом, который вы пишете для обработки некоторых событий или вызовов. Для них вы все равно должны использовать обычный подход try/catch .
Границы ошибок также являются местом, где вы можете отправлять информацию в используемый вами регистратор ошибок (в методе жизненного цикла componentDidCatch
).
class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, info) { logToErrorLogger(error, info); } render() { if (this.state.hasError) { return <div>Help, something went wrong.</div>; } return this.props.children; } }
Компоненты высшего порядка
Компоненты высшего порядка (HOC) часто упоминаются в React и являются очень популярным шаблоном, который вы, вероятно, будете использовать (или уже использовали). Если вы знакомы с HOC, вы, вероятно, видели withNavigation, connect, withRouter
во многих библиотеках.
HOC — это просто функции, которые принимают компонент в качестве аргумента и возвращают новый компонент с расширенными возможностями по сравнению с компонентом без оболочки HOC. Благодаря этому вы можете получить некоторые легко расширяемые функции, которые могут улучшить ваши компоненты (например, доступ к навигации). HOC также могут принимать несколько форм, вызываемых в зависимости от того, что у нас есть, единственным аргументом, который всегда требуется, является компонент, но он может принимать дополнительные аргументы — некоторые параметры или, как в connect
, вы сначала вызываете функцию с конфигурациями, которые позже возвращают функцию. который принимает компонент аргумента и возвращает HOC.
Есть несколько вещей, которые вы могли бы добавить и которых следует избегать:
- Добавьте отображаемое имя для функции HOC-оболочки (чтобы вы знали, что это на самом деле HOC, изменив отображаемое имя компонента HOC).
- Не используйте HOC внутри метода рендеринга — вы уже должны использовать внутри него расширенный компонент, вместо того, чтобы создавать там новый компонент HOC из-за его постоянного перемонтирования и потери его текущего состояния.
- Статические методы не копируются, поэтому, если вы хотите иметь некоторые статические методы внутри вашего вновь созданного HOC, вам нужно скопировать их на себя.
- Упомянутые
React.forwardRef
, как упоминалось ранее, для решения таких проблем.
export function importantHoc() { return (Component) => class extends React.Component { importantFunction = () => { console.log("Very Important Function"); }; render() { return ( <Component {...this.props} importantFunction={this.importantFunction} /> ); } }; }
Стайлинг
Стиль не обязательно связан с самим React, но его стоит упомянуть по ряду причин.
Во-первых, обычные CSS/встроенные стили применяются здесь как обычно, и вы можете просто добавить имена классов из CSS в атрибут className, и все будет работать правильно. Встроенный стиль немного отличается от обычного стиля HTML. Строка передается не со стилями, а с объектами с правильными значениями для каждого. Атрибуты стиля также имеют верблюжий регистр, поэтому border-radius становится borderRadius и так далее.
React, кажется, популяризировал несколько решений, которые стали обычным явлением не только в React, например, модули CSS, которые недавно были интегрированы в CRA, где вы можете просто импортировать name.modules.css
и использовать его классы, такие как свойства, для стилизации вашего компонента (некоторые IDE , например, WebStorm, для этого также есть автозаполнение, которое сообщает вам, какие имена доступны).
Еще одно решение, которое также популярно в React, — это CSS-in-JS (например, библиотека emotion
). Еще раз отметим, что CSS-модули и эмоции (или CSS-in-JS в целом) не ограничиваются React.
Хуки в React
Хуки , возможно, являются самым долгожданным дополнением к React с момента его перезаписи. Соответствует ли продукт рекламе? С моей точки зрения, да, поскольку они действительно являются отличной функцией. По сути, это функции, которые открывают новые возможности, такие как:
- Позволяет удалить множество компонентов
class
, которые мы использовали только потому, что не могли иметь, например, локальное состояние или ссылку, поэтому код компонента выглядит более читабельным. - Позволяет использовать меньше кода для того же эффекта.
- Упрощает обдумывание и тестирование функций, например, с помощью библиотеки react-testing-library.
- Также может принимать параметры, и результат одного может быть легко использован другим хуком (например,
setState
изuseState
вuseEffect
). - Минимизирует намного лучше, чем классы, которые, как правило, немного более проблематичны для минификаторов.
- Может удалить HOC и шаблоны реквизитов рендеринга в вашем приложении, которые создали новые проблемы, несмотря на то, что были разработаны для решения других.
- Может быть создан любым опытным разработчиком React.
Есть несколько хуков React, которые включены по умолчанию. Три основных — это useState
, useEffect
и useContext
. Также есть несколько дополнительных, например, useRef
и useMemo
, но пока сосредоточимся на основах.
Давайте взглянем на useState
и воспользуемся им для создания примера простого счетчика. Как это работает? Ну, в принципе, вся конструкция очень проста и выглядит так:
export function Counter() { const [counter, setCounter] = React.useState(0); return ( <div> {counter} <button onClick={() => setCounter(counter + 1)}>+</button> </div> ); };
Он вызывается с initialState
(значение) и возвращает с ним массив с двумя элементами. Благодаря назначению деструктурирования массива мы можем сразу присвоить переменные этим элементам. Первый всегда является последним состоянием после обновления, а другой — функцией, которую мы будем использовать для обновления значения. Кажется довольно легким, не так ли?
Также из-за того, что раньше такие компоненты назывались функциональными компонентами без состояния, такое название больше не подходит, потому что они могут иметь состояние, как показано выше. Следовательно, имена компонентов классов и компонентов функций кажутся более соответствующими тому, что они на самом деле делают, по крайней мере, с 16.8.0.
Функцию обновления (в нашем случае setCounter
) также можно использовать как функцию, которая будет принимать предыдущее значение в качестве аргумента в следующей форме:
<button onClick={() => setCounter(prevCounter => prevCounter + 1)}>+</button> <button onClick={() => setCounter(prevCounter => prevCounter - 1)}>-</button>
Однако, в отличие от компонента класса this.setState
, который выполнял поверхностное слияние, установка функции ( setCounter
в нашем случае) вместо этого переопределяет все состояние.

Кроме того, initialState
также может быть функцией, а не просто значением. У этого есть свои преимущества, так как эта функция будет выполняться только во время начального рендеринга компонента и после этого больше не будет вызываться.
const [counter, setCounter] = useState(() => calculateComplexInitialValue());
Наконец, если мы собираемся использовать setCounter
с точно таким же значением, которое у нас было в тот же момент в текущем состоянии ( counter
), то компонент не будет перерисовываться.
С другой стороны, useEffect
— это добавление побочных эффектов к нашему функциональному компоненту, будь то подписки, вызовы API, таймеры или почти все, что мы можем найти полезным. Любая функция, которую мы передадим в useEffect
, будет запускаться после рендеринга, и она будет делать это после каждого рендеринга, если только мы не добавим ограничение на то, какие изменения свойств следует повторно запускать в качестве второго аргумента функции. Если мы хотим запускать его только при монтировании и очищать при размонтировании, то нам нужно просто передать в него пустой массив.
const fetchApi = async () => { const value = await fetch("https://jsonplaceholder.typicode.com/todos/1"); console.log(await value.json()); }; export function Counter() { const [counter, setCounter] = useState(0); useEffect(() => { fetchApi(); }, []); return ( <div> {counter} <button onClick={() => setCounter(prevCounter => prevCounter + 1)}>+</button> <button onClick={() => setCounter(prevCounter => prevCounter - 1)}>-</button> </div> ); };
Приведенный выше код будет запущен только один раз из-за пустого массива в качестве второго аргумента. По сути, в данном случае это что-то вроде componentDidMount
, но срабатывает чуть позже. Если вы хотите иметь аналогичный хук, который вызывается перед отрисовкой браузера, используйте useLayoutEffect
, но эти обновления будут применяться синхронно, в отличие от useEffect
.
useContext
кажется самым простым для понимания, так как он указывает, к какому контексту мы хотим получить доступ (объект, который был возвращен функцией createContext
), а взамен предоставляет нам значение для этого контекста.
const context = useContext(Context);
Наконец, чтобы написать свой собственный хук, вы можете просто написать что-то вроде следующего:
function useWindowWidth() { let [windowWidth, setWindowWidth] = useState(window.innerWidth); function handleResize() { setWindowWidth(window.innerWidth); } useEffect(() => { window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return windowWidth; }
По сути, мы используем обычный хук useState
, для которого мы назначаем в качестве начального значения ширину окна. Затем в useEffect,
мы добавляем прослушиватель, который будет запускать handleResize
при каждом изменении размера окна. Мы также очищаем после того, как компонент будет размонтирован (посмотрите на результат в useEffect
). Легкий?
Примечание: использование слова во всех крючках важно. Он используется потому, что позволяет React проверить, не делаете ли вы что-то плохое, например, не вызываете ли вы хуки из обычных функций JS.
Проверка типов
У React была своя собственная проверка реквизита до того, как Flow и TypeScript стали опцией.
PropTypes проверяет свойства (реквизиты), полученные компонентом React, и проверяет, соответствуют ли они тому, что у нас есть. Всякий раз, когда возникает другая ситуация (например, объект вместо массива), мы получим предупреждение в консоли. Важно отметить, что PropTypes проверяются только в режиме разработки из-за их влияния на производительность и вышеупомянутого предупреждения консоли.
Начиная с React 15.5, PropTypes находятся в другом пакете, который необходимо устанавливать отдельно. Они объявляются вместе со свойствами в статическом свойстве, называемом propTypes
(сюрприз), объединяя их с defaultProps
, которые используются, если свойства не определены (единственный случай — undefined ). DefaultProps не связаны с PropTypes, но они могут устранять некоторые предупреждения, которые могут появиться из-за PropTypes.
Два других варианта — это Flow и TypeScript, и в настоящее время они более популярны (особенно TypeScript).
- TypeScript — это типизированный расширенный набор JavaScript, разработанный Microsoft, который может проверять ошибки еще до того, как приложение запустится, и обеспечивает превосходную функциональность автозаполнения для разработки. Это также значительно улучшает рефакторинг. Благодаря поддержке Microsoft, которая имеет большой опыт работы с типизированными языками, это также довольно безопасный выбор.
- Flow — это не язык, в отличие от TypeScript. Это статическая проверка типов для JavaScript, поэтому она больше похожа на инструмент, который вы включаете в JavaScript, чем на язык. Вся идея Flow очень похожа на то, что предлагает TypeScript. Это позволяет вам добавлять типы, поэтому вероятность возникновения ошибок до запуска кода снижается. Как и TypeScript, Flow теперь поддерживается в CRA (Create React App) с самого начала.
Лично я нахожу TypeScript быстрее (практически мгновенно), особенно в автозаполнении, которое кажется немного медленнее с Flow. Стоит отметить, что такие IDE, как WebStorm, которую я использую лично, используют интерфейс командной строки для интеграции с Flow. Однако кажется еще проще интегрировать необязательное использование в файлы, где вы просто добавляете // @flow
в начале файла, чтобы начать проверку типов. Кроме того, из того, что я могу сказать, кажется, что TypeScript в конце концов выиграл битву против Flow — сейчас он намного популярнее, и некоторые из самых популярных библиотек подвергаются рефакторингу с Flow на TypeScript.
Есть еще несколько вариантов, также упомянутых в официальной документации, например Reason (разработанный Facebook и набирающий популярность в сообществе React), Kotlin (язык, разработанный JetBrains) и другие.
Очевидно, что для фронтенд-разработчиков проще всего начать использовать Flow и TypeScript, а не переключаться на Kotlin или F#. Однако для бэкенд-разработчиков, которые переходят на фронтенд, начать с них может быть проще.
Производство и реакция на производительность
Самое основное и очевидное изменение, которое вам нужно сделать для рабочего режима, — это переключиться на «производственный» для DefinePlugin
и добавить UglifyJsPlugin
в случае с Webpack. В случае CRA это так же просто, как использовать npm run build
(который будет запускать сборку react-scripts build
). Имейте в виду, что Webpack и CRA — не единственные варианты, так как вы можете использовать другие инструменты сборки, такие как Brunch. Обычно это описано в официальной документации, будь то официальная документация React или документация по конкретному инструменту. Чтобы убедиться, что режим установлен правильно, вы можете использовать инструменты разработчика React, которые дадут вам представление о том, какую сборку вы используете (производство или разработка). Вышеупомянутые шаги заставят ваше приложение работать без проверок и предупреждений, исходящих от React, а сам пакет также будет свернут.
Есть еще несколько вещей, которые вы можете сделать для своего приложения React. Что вы делаете с созданным файлом JS? Вы можете начать просто с «bundle.js», если размер относительно небольшой, или, может быть, сделать что-то вроде «поставщик + пакет» или, может быть, «поставщик + наименьшая необходимая часть + импорт вещей, когда они нужны». Это полезно, когда вы имеете дело с очень большим приложением и вам не нужно импортировать все в самом начале. Имейте в виду, что объединение некоторого кода JavaScript в основной пакет, который даже не используется, просто увеличит размер пакета и замедлит загрузку приложения в самом начале.
Пакеты поставщиков могут быть полезны, если вы планируете заморозить версии библиотек, осознав, что они могут не меняться в течение длительного времени (если вообще когда-либо). Кроме того, большие файлы лучше сжимаются с помощью gzip, поэтому выгода, которую вы получаете от разделения, иногда может не стоить того. Это зависит от размера файла, и иногда вам просто нужно попробовать это самостоятельно.
Разделение кода
Разделение кода может проявляться в большем количестве способов, чем предложено здесь, но давайте сосредоточимся на том, что у нас есть в CRA и самом React. По сути, чтобы разделить код на разные фрагменты, мы можем использовать import()
, который работает благодаря Webpack ( import
сам по себе является предложением на этапе 3 на данный момент, поэтому он пока не является частью языкового стандарта). Всякий раз, когда Webpack увидит import
, он будет знать, что ему нужно начать разделение кода на этом этапе и не может включить его в основной бандл (тот код, который находится внутри импорта).
Теперь мы можем связать его с React.lazy()
, который требует import()
с путем к файлу, содержащему компонент, который необходимо отобразить в этом месте. Затем мы можем использовать React.suspense()
, который будет отображать другой компонент в этом месте, пока не будет загружен импортированный компонент. Можно задаться вопросом; если мы импортируем один компонент, то зачем он нам нужен?
Это не совсем так, поскольку React.lazy()
будет отображать компонент, который мы import()
, но import()
может получить больший фрагмент, чем этот отдельный компонент. Например, этот конкретный компонент может иметь другие библиотеки, дополнительный код и т. д., поэтому один файл не нужен — это может быть гораздо больше файлов, объединенных вместе. Наконец, мы можем обернуть все это в ErrorBoundary
(вы можете найти код в нашем разделе, посвященном границам ошибок) , который будет служить запасным вариантом, если что-то выйдет из строя с компонентом, который мы хотели импортировать (например, если возникла сетевая ошибка).
import ErrorBoundary from './ErrorBoundary'; const ComponentOne = React.lazy(() => import('./ComponentOne')); function MyComponent() { return ( <ErrorBoundary> <React.Suspense fallback={<div>Loading...</div>}> <ComponentOne/> </React.Suspense> </ErrorBoundary> ); }
Это базовый пример, но вы, очевидно, можете сделать больше. Вы можете использовать import
и React.lazy
для динамического разделения маршрута (например, администратор против обычного пользователя или просто очень большие пути, которые приносят много). Имейте в виду, что React.lazy
поддерживает только экспорт по умолчанию и не поддерживает рендеринг на стороне сервера.
Реагировать на производительность кода
Что касается производительности, если ваше приложение React тормозит, есть два инструмента, которые могут помочь вам разобраться в проблеме.
Первая — это вкладка «Производительность Chrome», которая расскажет вам, что происходит с каждым компонентом (например, монтирование, обновление). Благодаря этому вы сможете определить, какой компонент испытывает проблемы с производительностью, а затем оптимизировать его.
Другой вариант — использовать DevTools Profiler, который стал доступен в React 16.5+, и при сотрудничестве с shouldComponentUpdate (или PureComponent, о котором говорилось в первой части этого руководства) мы можем повысить производительность некоторых критических компонентов.
Очевидно, оптимальным является использование основных лучших практик для Интернета, таких как устранение дребезга некоторых событий (например, прокрутки), осторожность с анимацией (использование преобразования вместо изменения высоты и ее анимации) и так далее. Использование лучших практик можно очень легко упустить из виду, особенно если вы только начинаете осваивать React.
Состояние React в 2019 году и позже
Если бы мы обсуждали будущее React, лично я бы не слишком беспокоился. С моей точки зрения, у React не возникнет проблем с сохранением своего трона в 2019 году и далее.
React имеет такое сильное положение, поддерживаемое большим сообществом, что его будет трудно свергнуть. Сообщество React прекрасно, у него не иссякают идеи, и основная команда постоянно работает над улучшением React, добавляя новые функции и устраняя старые проблемы. React также поддерживается крупной компанией, но проблем с лицензированием больше нет — сейчас у него есть лицензия MIT.
Да, есть несколько вещей, которые, как ожидается, будут изменены или улучшены; например, сделать React немного меньше (одна из упомянутых мер — удаление синтетических событий) или переименовать className
в class.
Конечно, даже эти, казалось бы, незначительные изменения могут вызвать такие проблемы, как влияние на совместимость браузера. Лично мне также интересно, что произойдет, когда WebComponent станет более популярным, поскольку он может дополнить некоторые вещи, с которыми сегодня часто используется React. Я не верю, что они будут полной заменой, но я думаю, что они могут хорошо дополнять друг друга.
Что касается краткосрочной перспективы, то в React просто пришли зацепки. Это, вероятно, самое большое изменение с тех пор, как в React произошла переработка, так как они откроют множество возможностей и улучшат дополнительные функциональные компоненты (и они сейчас действительно раскручены).
Наконец, поскольку это то, чем я занимался в последнее время, есть React Native. Для меня это отличная технология, которая так сильно изменилась за последние пару лет (отсутствие реактивной ссылки было, вероятно, самой большой проблемой для большинства людей, и, очевидно, было много ошибок). React Native переписывает свое ядро, и это должно быть сделано аналогично переписыванию React (это все внутреннее, ничего или почти ничего не должно меняться для разработчиков). Асинхронный рендеринг, более быстрый и легкий мост между нативным и JavaScript и многое другое.
В экосистеме React есть что ожидать, но обновления хуков (и React Native, если кто-то любит мобильные устройства) — вероятно, самые важные изменения, которые мы увидим в 2019 году.