Что нового?
-
React 18 (апрель 2022)
Параллельный рендеринг
(Concurrent Rendering)- В обычном поведении, если React начал перерисовывать DOM, все остальные обновления в очереди блокируются и дожидаются окончания обновления. Concurrent rendering должен решить эту проблему. В конкурентном режиме рендеринг не блокируется. Он прерывается. Это улучшает UX и открывает новые возможности.
Automatic batching
(“автоматическое пакетирование”)- улучшение производительности, меняет способ пакетной обработки обновлений React для автоматического выполнения большей пакетной обработки.
- До версии 18, React уже объединял/группировал (batched) несколько обновлений состояния в одно, чтобы уменьшить количество ненужных повторных отрисовок. Однако это происходило только в обработчиках событий DOM, поэтому промисы, тайм-ауты или другие обработчики этим не могли воспользоваться.
- ранее каждый вызов useState (установка нового значения) приводил к перерисовке компонентов. Чуть позже движок оптимизировали и такие вызовы начали группироваться и выполняться за один раз, что должно было сократить количество перерисовок. Теперь данный функционал еще больше оптимизировали.
- Изменения в
Strict Mode
- режим был добавлен в React 16.3 — позволяет React производить доп. проверки, что бы исключить возможные проблемы приложения.
- Более строгий «строгий режим»
- Добавление
<StrictMode>
в приложение React добавляет особое поведение (только в режиме DEV) ко всем компонентам, вокруг которых оно выполняется. Например, при работе в «строгом режиме» React намеренно выполняет двойной рендеринг компонентов, чтобы избавиться от небезопасных побочных эффектов. - в будущем React предоставит функцию, позволяющую компонентам сохранять состояние между анмаунтами. Чтобы подготовиться к этому, React 18 вводит новую проверку только для разработки в строгом режиме. React автоматически анмаунтит и маунтит каждый компонент всякий раз, когда компонент создаётся в первый раз, восстанавливая предыдущее состояние при втором маунте.
Offscreen API
- обеспечивает лучшую производительность, скрывая компоненты вместо их размонтирования, сохраняя состояние и по-прежнему вызывая эффекты монтирования/размонтирования.
- Это сыграет решающую роль в оптимизации таких компонентов, как вкладки, виртуализированные списки и т. д.
Root API
(React DOM Client)- Функция рендеринга (render) — та, которая находится в корне каждого приложения React, будет заменена на createRoot.
- Новый API — это шлюз для доступа к новым функциям React 18. createRoot предоставляется вместе с устаревшим API, чтобы способствовать постепенному внедрению и упрощению возможных сравнений производительности.
createRoot
— новый метод создания корня для рендеринга или анмаунта. Используйте его вместо ReactDOM.render. Без него новые функции в React 18 не работают.hydrateRoot
— новый метод гидратации приложения, отображаемого на сервере. Заменяетhydrate()
. Используется в сочетании с новыми API-интерфейсами React DOM Server. Без него новые функции в React 18 не работают.
React DOM Server API
- имеет полную поддержку потоковой передачи Suspense на сервере:
renderToPipeableStream
— для потоковой передачи в среде Node.renderToReadableStream
— для современных сред выполнения, таких как Deno и Cloudflare.- Существующий метод
renderToString
продолжает работать, но не рекомендуется его использовать.
- Хук
useId
- создание уникальных ID как на клиенте, так и на сервере, избегая hydration несоответствий.
- Наиболее полезно для библиотек компонентов, интегрирующихся с API, для которых требуются уникальные ID.
- Особенно актуально в React 18 т.к. новый рендер сервера доставляет HTML не по порядку.
- Хук
startTransition
иuseTransition
- позволяют помечать некоторые обновления состояния как несрочные
- Другие обновления состояния по умолчанию считаются срочными. React позволит срочным обновлениям состояния (например, обновлению ввода текста) прерывать несрочные обновления состояния (например, отображение списка результатов поиска).
- Хук
useDeferredValue
- позволяет отложить повторный рендеринг несрочной части дерева.
- Похож на
debouncing
, но имеет несколько преимуществ по сравнению с ним. - Фиксированной задержки по времени нет, поэтому React попытается выполнить отложенный рендеринг сразу после того, как первый рендер отобразится на экране.
- Отложенный рендеринг может быть прерван и не будет блокировать ввод данных пользователем.
- Хук
useSyncExternalStore
- позволяет внешним хранилищам поддерживать параллельное чтение. Заставляет обновления в хранилище быть синхронными.
- Устраняет необходимость в useEffect при реализации подписок на внешние источники данных
- Рекомендуется для любой библиотеки, которая интегрируется со сторонним состоянием по отношению к React.
- Хук
useInsertionEffect
- позволяет библиотекам CSS-in-JS решать проблемы с производительностью при внедрении стилей во время рендеринга.
- Если вы не планируете создавать библиотеку CSS-in-JS, мы не ожидаем, что вы когда-либо будете это использовать.
- Запустится после изменения DOM, но до того, как эффекты лейаута узнают об этом.
- Особенно актуален в React 18, поскольку React уступает браузеру во время одновременного рендеринга, давая ему возможность пересчитать лейаут.
Согласованное время useEffect
- React теперь всегда синхронно сбрасывает функции эффектов, если обновление было запущено во время дискретного события пользовательского ввода, такого как щелчок или событие нажатия клавиши.
- Раньше поведение не всегда было предсказуемым или последовательным.
Более строгие ошибки гидратации
- несоответствия гидратации из-за отсутствующего или дополнительного текстового содержимого теперь обрабатываются как ошибки, а не как предупреждения. React больше не будет пытаться «исправлять» отдельные узлы, вставляя или удаляя узел на клиенте в попытке сопоставить разметку сервера, и вернется к рендерингу клиента до ближайшей границы в дереве.
- Это гарантирует согласованность гидратированного дерева и позволяет избежать потенциальных дыр в конфиденциальности и безопасности, которые могут быть вызваны несоответствием гидратации.
Эффекты лейаута с задержкой
- когда дерево повторно приостанавливается и возвращается к резервному варианту, React теперь очищает эффекты лейаута, а затем воссоздает их, когда содержимое внутри границы снова отображается.
- Это устраняет проблему, из-за которой библиотеки компонентов не могли правильно измерить лейаут при использовании с Suspense.
Новые требования к среде JS
- React теперь зависит от современных функций браузеров, включая
Promise
,Symbol
иObject.assign
. - Если вы поддерживаете более старые браузеры и устройства, такие как Internet Explorer, которые изначально не предоставляют современные функции браузера или имеют несовместимые реализации, рассмотрите возможность включения глобального полифилла в приложение.
- React теперь зависит от современных функций браузеров, включая
-
React 17 (2020)
- Прославился малым количеством изменений
- Изменение в
делегировании событий
- теперь все обработчики привязываются к
корневому элементу
, а неdocument
- Реакт использует особый способ привязки событий (onClick и т.д.) к DOM-элементам. Для повышения производительности.
- Использует прием «делегирования событий» и привязывает все события к объекту
document
. - Теперь все обработчики крепятся к
корневому элементу
, а неdocument
. - Это решает ряд проблем если: на странице используется N версий React, есть микрофронтенды или используется jQuery
- теперь все обработчики привязываются к
- Убран костыль с Синтетическим Событием (
SyntheticEvent Even Pooling
)- убрана оптимизация событий, которая более не актуальна в современных браузерах.
- Небольшие изменения на сближение поведения React с браузера
- Событие
onScroll
— больше не всплывает, чтобы избежать текущей путаницы; - События
onFocus
иonBlur
— изменены "под капотом" на нативные focusin и focusout; onClickCapture
и другиеCapture
-события — теперь используют браузерные обработчики событий. Типо onClick? Иил что-то про всплытие/погружение событий?
- Событие
useEffect()
теперь полностью асинхронный- раньше только функция эффекта запускалась асинхронно, тогда как возвращаемая функция для очистки подписок запускалась синхронно, так же как и метод componentWillUnmount(), то теперь и эта функция работает асинхронно, что улучшает производительность в случае перерисовки большого количества элементов, например, когда на сайте присутствуют вкладки, или при переходе со страницы на страницу.
- Для синхронной работы, можно по-прежнему использовать useLayoutEffect(), который остался незатронутым.
- Ошибки при возвращении
undefined
- Если раньше нельзя было возвращать undefined только в обычных компонентах, то теперь такая ошибка будет выбрасываться еще и в React.forwardRef и React.memo.
- Улучшенный
стек вызовов при ошибках
- Когда выбрасывается ошибка, браузер показывает stack trace с названиями функций и их местоположения.
- Теперь используется новый механизм генерации стека вызовов => позволяет увидеть дерево React-компонентов, которое привело к ошибке, даже в production-среде.
Удаление приватных экспортов
— это удаление некоторых внутренних экспортов, которые ранее были открыты наружу.- Например, React Native for Web ранее зависела на некоторых внутренностях системы событий, но эта зависимость не была надежной. В 17-м React эти приватные экспорты были удалены. По большей части из-за того, что только вышеупомянутый проект их использовал, а миграция на более надежные методы уже была произведена.
Базовые понятия
-
Что такое React
- JS-библиотека для создания пользовательских интерфейсов.
- Это не фреймворк! Сам по себе не позволит создать веб-приложение.
- Предназначен для создания представлений (это «V» в MVC). React – это только представление.
- Это язык шаблонов + несколько функций, которые позволяют отрисовать HTML.
- Результат работы React – это HTML.
- Разрабатывается и поддерживается Facebook + Instagram и сообществом отдельных разработчиков и корпораций.
- Открытый исходный код
- Выход первой версии:
- 2009 - Angular
- 2010 - Backbone
- 2011 - Ember
- 2012 - Meteor
- 2013 - REACT
- 2014 - Vue
- 2015 - Polymer
- 2015 - Aurelia
- Также Facebook разработал Flux - архитектурный шаблон, который дополняет React (см. Redux).
- Ссылки
-
Про название
- React получил свое название от того, что он реагирует на изменения состояния. отсылка к концепциям «реактивного программирования».
- Хотя реагирует он и не «реактивно», а по графику. Есть шутка, что React должен был быть назван «Schedule».
-
Ключевые особенности
Компонентный подход
— разбиваем страницы на небольшие фрагменты (компоненты). Их можно переиспользовать в других проектах.Виртуальный DOM
— кэш-структура в памяти, позволяет вычислять разницу между предыдущим и текущим состояниями интерфейса. Оптимизация обновления DOM браузера.Однонаправленная передача данных
— свойства передаются от родительских компонентов к дочерним (сверху-вниз). Компонент не может напрямую изменять свойства родителя, но может вызывать изменения через callback функции.JSX
(JavaScript XML) — расширение синтаксиса JavaScript, "синтаксический сахар" для JS. Позволяет использовать похожий на HTML синтаксис для описания структуры интерфейса. Можно работать с React и без JSX (но не стоит).Декларативный
— описываем не поведение, а состояния компонентов (в зависимости от разных данных) + переключаемся между этими состояниямиФункциональный
— поощряется использование функционального подхода к программированию (не ООП)Реактивный
— является реализацией реактивного подхода к программированию (как бы из названия видно)Типизация
— динамическая слабая неявная (как и весь JS). Т.е: тип присваивается переменной в ммоент присваивания значения; тпи переменной может меняться в процессе работы программы; тип указывать необязательно (JS поймёт сам что это строка, например). Для статической типизации можно использовать propTypes / Flow / TypeScriptТестируемость
— поддерживает js-тестирование (Jest, Enzyme, Mocha...)ES6+
— т.к. всё равно используем Babel, логично использовать возможности ES6 (константы, стрелочные функции, шаблонные строки и т.д.)Изоморфность
— код может выполняться и на клиентской, и на серверной стороне.Быстрый
— в частности, потому что выполняет оптимизацию при компиляции кода в JSX -> JSЛегко интегрировать
— React можно встраивать в приложение по частям: часть уже работает на React, часть без него. Можно подключать через CDN и т.д.React Native
— платформа для разработки мобильных приложений, создает мобильные приложения с помощью React. На основе React JS приложения можно довольно легко создать мобильное приложение.
-
Реактивность
-
Парадигма программирования, подход к созданию кода.
-
Основана на 2 понятиях
- Распространение изменений
- строится на «push стратегии» распространения изменений
- в случае изменения данных эти изменения “проталкиваться”, и зависимые от них данные будут автоматически обновляться.
- у нас есть
a=2
иb=3
иc = a + b
- в императивном подходе, если я ниже строки
c = a + b
, напишуa=3
— значениес
не поменяется (останется равно 5). Надо ещё рах раз писатьc = a + b
- в реактивном стиле — значение
с
изменится автоматически (станет равно 6). Не надо для этого заново пересчитывать значениеc
— оно изменится автоматически
- у нас есть
- использование «потоков» — особая структура данных (массив со спец. возможностями)
- массив данных, отсортированных по времени, который может сообщать о том, что данные изменились.
- Распространение изменений
-
Классический пример «реактивности» — электронные таблицы Excel:
- меняем значение в одной ячейке A1 — все ячейки, которые считают сове значение на основа A1 автоматически пересчитываются, сами.
-
React назван так потому, что реагирует на изменения состояния компонентов.
-
Но делает это не реактивно, а, скорее, по графику – отсюда появилась шутка, что React следовало бы назвать Schedule.
-
Но, если не углубляться внутрь фреймворка, то все что мы видим – это как React реагирует на обновление компонента и автоматически отображает его изменения в дереве документа.
-
Помните, что входными данными для
render()
являются свойства (props
) и внутреннее состояние (state
), которое может быть обновлено в любое время. -
Когда для
render()
меняются входные данные, меняется и результат ее выполнения.ивел лишь с целью показать разницу в подходах. -
Ссылки
-
-
Фреймворки и библиотеки
-
React - не фрэймворк. Это библиотека.
-
При помощи одного React не создашь сайт — надо ещё много библиотек и плагинов.
-
Есть фрэймворки на основе React — содержат React + кучу библиотек и плагинов.
-
Всё это настроено и подогнано.
-
Create React App
— для быстрого создания базового приложения -
Next.js
— поддержка Server-Side Rendering (SSR), TypeScript, роутинга... -
Gatsby
— формирует статический HTML, который можно выложить на сервер -
Blitz.js
— версия Ruby on Rails в духе React и JS. -
Remix
— заточен под SSR (server-side rendering). Opensource -
Библиотека
- обычно решает одну небольшую задачу,
- не такое масштабное решение, можно легко поменять.
- набор функций, которые вы просто вызываете из своей программы
- может быть использована просто как набор программ, не влияет на архитектуру.
- Пример: мебель, компьютер, шторы в офисе... Чтоб не создавать свои стулья - купил готовые, но могут легко заменить на другие
-
Фреймворк
- обычно решает несколько задач
- масштабнее библиотеки, задаёт архитектуру, его сложнее поменять.
- производит инверсию управления (inversion of control, IOC)
- Пример: офис для бизнеса - задаёт инфраструктуру (электричество, вода, канализация, парковка, отопление...)
- каркас для написания веб-приложений. Задает правила построения архитектуры.
- Определяет структуру, задаёт правила и предоставляет необходимый набор инструментов для разработки.
- Формирует начальный «каркас», который нужно расширять и изменять под свои нужды.
-
Особенности веб-фрэймворков
- Веб-кэширование — помогает хранить разные документы и позволяет избежать перегрузки сервера.
- Скаффолдинг (Scaffolding) — могут автоматически сгенерировать типичные части приложения или даже всю структуру проекта.
- Встроенная система веб-шаблонов
- Инструменты безопасности
- Роутинг
-
Определяющими особенностями фреймворков называют
инверсию управления
иинъекцию зависимостей
. -
«Инверсия управления» (Inversion of Control, IoC)
- Библиотека — набор функций, которые вы можете вызывать, обычно они организованы в классы. Каждый вызов выполняет некоторую работу и возвращает управление обратно пользователю.
- Фреймворк — воплощает в себе некоторый абстрактный дизайн со встроенным поведением. Чтобы использовать его, вы должны добавить свой код в различных местах фреймворка, либо через наследование, либо подключив свой собственный класс. Код фреймворка впоследствии будет вызывать ваш код.
Инверсия управления
— ключевое отличие фреймворка и библиотеки.- Абстрактный принцип. Указывает, что поток выполнения контролируется внешней сущностью.
- Инверсия управления означает, что происходящее в программе не находится под управлением текущей области программы — вы передаете управление другой части программы.
- Мартин Фаулер (Martin Fowler) приводит IoC как признак, по которому можно отличить фреймворк от библиотеки: вы вызываете функции библиотеки, а фреймворк вызывает ваши функции (Inversion of Control, Martin Fowler, https://martinfowler.com/bliki/InversionOfControl.html, 26 июня 2005 г.).>
IoC
это:- принцип в разработке ПО, который передает управление объектами или частями программы контейнеру или фреймворку. Т.е. не мой код дергает методы фреймворка (как в случае с библиотекой). Фреймворк управляет работой, и я создаю какой-то код который фреймворк будет выполнять в том или ином случае.
- Фреймворк — это оболочка, которая предоставляет предопределенные точки расширения. Я могу вставить свой собственный код в эти точки расширения, но фреймворк определяет, когда этот код будет вызван.
- если инверсии нет: мой код главный, он (когда ему надо) использует методы библиотеки. Мой код напрямую вызывает код библиотеки.
- абстрактный принцип ООП, набор рекомендаций для написания слабо связанного кода.
- Суть: каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов.
-
Инверсия зависимостей (Dependency inversion, DI).
- Один из способов реализации
IoC
- Принцип из SOLID (буква D).
- конкретный стиль «инверсии управления», одна из реализаций.
- это шаблон, где инвертируемый элемент управления устанавливает зависимости объекта.
- то же самое что
Внедрение зависимостей
?
- Принцип гласит:
- Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба типа модулей должны зависеть от абстракций.
- Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
- Один из способов реализации
-
Ссылки
- Веб-фреймворки для начинающих: простое объяснение с примерами (2018)
- Habr - Remix: руководство по новому open source React-фреймворку (2021)
- Habr - Инверсия управления/Inversion of Control (2011, Ruby)
- Инверсия и внедрение зависимостей
- IT-Kamasutra — #13 Фреймворк vs. Библиотека (it-ликбез из тачилы)
- Инверсия и внедрение зависимостей
-
-
Среда React
- Для работы используется:
- NPM/Yarn - для управления зависимостями. Ну, и чтобы установить Create React App (для работы npm нужен Ruby)
- Babel
- WebPack
- WebPack server
- Create React App - специальная npm/yarn утилита для быстрого разворачивания проекта на React. Содержит Babel, WebPack и прочее
- Можно работать без всего этого добра - добавить в код ссылку на CDN React и всё. Но с «добром» удобнее.
- Для работы используется:
-
Транспилятор — Babel
- Транспилятор. Преобразует JSX в обычный JS
- Компоненты написанные на JSX (HTML и JS) преобразуются в чистый JS с помощью CLI (интерфейс командной строки) инструмента Babel
Транспиляция
— это конвертация кода в другой, похожий язык.- Это важная часть фронтенд-разработки: поскольку в браузерах медленно появляются новые фичи, были созданы языки с экспериментальными возможностями, которые - транспилируются в совместимые с браузерами языки. Превращение одной версии языка в другую версию языка. JSX - это расширение JS, так что JSX->JS = транспиляция.
Компиляция
- перевод на другой язык (чаще всего низкоуровневый = байт-код).
-
Бандлер — Webpack
- Сборщик модулей, bundler. Используется для сборки, компиляции JS-модулей.
- Берёт всё, от чего зависит проект (Css, JS...), и преобразует это в статические ресурсы, которые могут быть переданы клиенту. Позволяет объединять ресурсы и библиотеки, - необходимые для проекта, в один файл - «bundle» (пачка).
Node.js
— это среда исполнения JS, разработанная для запуска на серверах.- На сервере использование node.js-модулей выглядело так: вместо загрузки всего «moment.min.js» в скриптовом теге HTML можно было грузить JS-файл напрямую:
-
var moment = require('moment'); console.log("Hello from JavaScript!"); console.log(moment().startOf('day').fromNow());
- Загрузка модулей в «node.js» работает прекрасно, т.к. «node.js» — серверный язык с доступом к файловой системе.
- Также ему известно расположение всех npm-модулей — вместо
require('./node_modules/moment/min/moment.min.js')
можно писать простоrequire('moment')
. - У браузера нет доступа к файловой системе.
- Если использовать этот код в браузере — получишь ошибку «require не определён».
- В браузере файлы модулей нужно грузить динамически:
- синхронно (замедляет исполнение)
- или асинхронно (могут быть проблемы с синхронизацией).
- Здесь появляется бандлер (bundler).
- Инструмент, имеющий доступ к файловой системе — собирает модули в единые пакеты.
- Эти пакеты совместимы с браузером, которому теперь не нужен доступ к файловой системе.
- В нашем случае бандлер нужен для поиска всех выражений require (имеющих ошибочный, с точки зрения браузера, JS-синтаксис) и замены на настоящее содержимое каждого - требуемого файла.
- В финале мы получаем единый JS-файл без выражений require
- Самым популярным бандлером сначала был
Browserify
, выпущенный в 2011 г. - Он был пионером в использовании node.js-выражений
require
во фронтенде. - Это позволило
npm
стать самым востребованным диспетчером пакетов. - К 2015-му лидером стал
Webpack
- Ему помогла популярность
React
, использующего все возможности этого бандлера.
-
Create React App
- Специальное приложение от разработчиков React.
- По сути - готовая среда для работы, с настроенным Babel, WebPack, live development server, линтерами и всеми чудесами.
- Установил - и можешь сразу начинать работать.
- GitHub
-
Компонент React
- Компонент - класс или функция, которая принимает данные и возвращает элементы React.
- Компонент - js-функция, которая возвращает кусок кода, представляющего фрагмент страницы.
- Описывают DOM-элементы (h1, div, section...). Обычно это части пользовательского интерфейса, которые содержат свою структуру и функциональность. Например: NavBar, LikeButton, или ImageUploader.
- Компонент совмещает в себе разметку и логику (благодаря синтаксису JSX).
- Концептуально, компоненты подобны JS-функциям - принимают данные (props) и возвращают React-элементы, описывающие: что должно появиться на экране.
- Когда компонент создан, автоматически появляется тэг <Имя_Компонента /> - при его помощи мы выводим этот JSX в нужном месте.
- Т.е. компонент (класс/функция) генерирует элемент (js-объект). Этот элемент будет отображён в DOM (с помощью функции ReactDOM.render), и у нас появиться новый DOM узел. А браузер на основе этого DOM-узла отобразит какую-то информацию на экране (кнопку, например)
- Основополагающая концепция React.js – многоразовые компоненты. Разработчик создает небольшие части кода, которые можно объединять, чтобы сформировать более крупные или использовать их как самостоятельные элементы интерфейса.
- Самое главное в этой концепции то, что и большие, и маленькие компоненты можно использовать повторно и в текущем и в новом проекте.
- Компоненты, которые были созданы во время работы над тем или иным проектом, не имеют дополнительных зависимостей. Таким образом, ничто не мешает использовать их снова и снова в проектах разного типа. Весь предыдущий опыт может быть с легкостью применен при работе над новым сайтом или даже при создании мобильного приложения.
- Ссылки:
-
Элемент React
- JS-объект, который описывает узел DOM.
- Объектное представление некоторого пользовательского интерфейса.
-
//Создаём элемент с помощью метода React createElement const element = React.createElement( 'div', {id: 'login-btn'}, 'Login' ) //Получаем такой объект (элемент): { type: 'div', props: { children: 'Login', id: 'login-btn' } } //Когда он будет отображён в DOM (с помощью функции ReactDOM.render), появится узел DOM <div id='login-btn'>Login</div>
- Компонент же — класс или функция, которая принимает данные и возвращает элементы.
-
//компонент Button принимает функцию onLogin и возвращает React элемент function Button ({ onLogin }) { return React.createElement( 'div', {id: 'login-btn', onClick: onLogin}, 'Login' ) }
- Надо учесть: когда мы работаем с JSX - мы не видим
React.createElement()
. - JSX позволяет писать это проще, но по факту, переделывает наш код в вызов
React.createElement()
- Примеры создания React элемента:
React.createElement('div', className: 'container', 'Hello!')
<div className='container'>Hello!</div>
<Hello />
- Т.е. компонент (класс/функция) генерирует элемент (js-объект).
- Этот элемент будет отображён в DOM (с помощью функции ReactDOM.render), и у нас появиться новый DOM узел.
- Браузер на основе этого DOM-узла отобразит какую-то информацию на экране (кнопку, например).
- Ссылки:
-
Props (свойства)
- Определения:
- В React есть функция, которая создаёт/вызывает компоненты. Она принимает параметры. Первый параметр называется
props
. В него прокидывается объект со всеми данными, которые мы передаём при создании/вызове компонента. Если этих данных нет — объект пустой. - Произвольные данные, которые принимают компоненты. Это могут быть и функции.
- Все данные в компонент приходят в первом аргументе.
- Первый аргумент функции, создающей компонент. В него приходят все данные, с которыми мы работаем в компоненте.
- Информация, коллективно используемая родительским компонентом и компонентами-потомками.
- Объект, который позволяет передать в компонент какие-то данные или callback.
callback
= функция, которую компонент потом сможет запустить. Сама функцию создана в файле Х, но если передать её черезprops
- вызывается из файла Y.
- В React есть функция, которая создаёт/вызывает компоненты. Она принимает параметры. Первый параметр называется
- Props от
properties
(свойства). - Props передаются в качестве аргументов компонента. Выглядят как атрибуты HTML.
- Каждый элемент имеет список свойств (атрибутов), как и в HTML. В Реакте это называется props.
- Любой компонент может принимать параметры, которые потом использует внутри себя. Например, текст, который надо вывести внутри JSX-разметки, генерируемой компонентом. Главный такой параметр называется
props
. - Это просто название параметра, оно принято в React.
- React, вызывая компонент, всегда передаёт в этот параметр
props
некий объект (если ничего нет - объект пуст). - Находясь внутри компонента я могу получать данные, которые пришли внутри этого
props
. -
//Тут передаются props imageURL (значение - адрес), caption (текст), isPlaying (функция) <Button imageURL='http:tinyurl.comlkevsb9' caption='New York!' isPlaying={this.state.isMusicPlaying} />
- Чтоб при вызове компонента передать что-то в его
props
- достаточно прописать некий атрибут. - Например
name='Dima'
превратится вprops.name
-
const User = (props) => { return ( <> <p> {props.name} </p> <p> {props.age} </p> </> ) } <User name='Dima' age='30' />
- Правила использования
Props
можно только читать! Компонент никогда не должен что-то записывать в свои пропсы — вне зависимости от того, функциональный он или классовый.- Правильный подход - прокидывать в компоненты как можно меньше
props
. Чтоб компоненты оставались "чистыми", " презентационными". В идеале компонент ничего не знает проstore
, мы никак завязаны наstore
. Тогда, в будущем, можно использовать эти компоненты с другими реализациямиstate-managment
(MobX, ...)
- Props и производительность
- см. раздел «Быстродействие и оптимизация React»
- Ссылки
- Определения:
-
State (состояние)
-
Специальный js-объект внутри компонента. Хранит данные, которые могут изменяться с течением времени.
-
Описывает внутреннее состояние компонента.
-
Props
- входные данные, которые передаются в компонент извне. -
State
- объект для хранения данных, которые создаются в компоненте и полностью зависят от компонента.- Определяется внутри компонента
- Доступно только из компонента. Компонент может передать своё состояние дочерним компонентам в виде props.
- В отличие от props, компонент может менять значения в своём state.
-
У каждого классового компонента может быть свой state, которое находится в свойстве объекта
this.state
. -
У функциональных компонент тоже есть state, доступен через хук
useState
. Работает немного иначе, но суть та же. -
С добавлением Redux всё становится немного интереснее, т.к. Redux вводит свой state.
-
Он собственно и нужен для управления state приложения.(см. Redux).
-
Когда state меняется — React перерисовывает компонент (и, обычно, все дочерние).
-
Как React узнает что state изменился? Мы сообщаем б этом, используя метод
this.setState()
или хукuseState()
. -
Как правильно использовать state
- Не изменяйте состояние напрямую. Вместо этого используйте
setState()
-
// Неправильно this.state.comment = 'Привет'; // Правильно this.setState({comment: 'Привет'});
- Конструктор — единственное место, где вы можете присвоить что-либо
this.state
напрямую.
-
- Обновления state могут быть асинхронными.
- Поскольку
this.props
иthis.state
могут обновляться асинхронно, вы не должны полагаться на их текущее значение для вычисления следующего состояния. - Если вы попытаетесь получить значение
this.state
сразу после вызоваthis.setState
, вероятно он не будет содержать изменений. -
// Неправильно this.setState({ counter: this.state.counter + this.props.increment, }); // Правильно // Используем специальную форму setState - функцию, которая поулчает текущий state и пропс this.setState((currentState , currentProps) =>({ counter: currentState .counter + currentProps.increment, }));
- Если в setState использовать значение текущего state для обновления следующего state, React может выполнить повторный рендер, а может и не выполнить. Это происходит потому, что state и props обновляются асинхронно. То есть DOM не обновляется при вызове setState. Вместо этого React складывает несколько обновлений в одно и затем отрисовывает DOM. Если запросить объект state, можно получить устаревшие значения.
- Поскольку
- Обновления state объединяются.
- Когда мы вызываем
setState()
React объединит новое состояние cо старым. - Состояния объединяются поверхностно!
- При использовании хуков - происходит перезапись объекта state! Если хотим сохранить часть данных и старого стэйта — надо его скопировать в новый, и потом обновить/переписать необходимые данные.
- Когда мы вызываем
- Не изменяйте состояние напрямую. Вместо этого используйте
-
Чаще всего если какой-то объект не используется в рендерниге компонента, то нет смысла сохранять его в state.
-
Впрочем, это касается структуры данных - возможны разные варианты.
-
Ссылки
-
-
Алгоритм мыследеятельности при создании React-приложения
- есть некий дизайн (UI)
- глядя на него, я начинаю общаться с заказчиком, и разбираться - какие данный приходят на ту или иную страницу, какие с ними действия происходят, и т.д.
- на основе этого я формирую state для каждой из страниц. Формирую BLL - Busines Logic Layer
- параллельно решаю вопрос как буду управлять state (state managment) - например, через Redux
- потом начинаю кодить компоненты UI и связывать их со state
- ну, и тестирование
-
Ссылки
- Оф. документация - обучение теоретическое
- Оф. документация - обучение практическое
- IT Kamasutra - лучший курс видео. Большой
- http://code.mu - курс. Должен быть неплох
- learn.javascript.ru - вводный курс видео. Короткий
- Habr- Учебный курс по React, (28 частей)
- monsterlessons.com - вводные уроки. Видео + текст. Примерно 2017
- какой-то курc
- ещё курc
- developer.mozilla.org - раздел про JS-фрэймворки (частично на русском)
- Максим Пацианский
- npm trends - сравнение популярности React, Angular & Vue
- google trends - сравнение популярности React, Angular & Vue
- Medium - Прощай, Redux (2018)
Как работает React
-
Схема работы React
- Компоненты написанные на
JSX
(HTML и JS) преобразуются в чистый JS с помощью CLI-инструментаBabel
. - Затем React функция
React.createElement
преобразует эти JS вReactElement
. Получаем деревоVirtualDOM
. - После обновления
VDOM
, React сравнивает его текущую версию с предыдущей. - Обнаружив объекты, изменившиеся в
VDOM
, React обновляет соответствующие объекты в реальномDOM
(нашем приложении). - То же самое подробнее
- JSX помогает написать “представление” DOM. Но, в итоге, нам нужен реальный DOM.
- Функция
React.createElement
преобразует наше “представление” (JSX) в JSON (VDOM, который также дерево). Чтобы использовать этот JSON в качестве входных данных для создания DOM. - Чтобы преобразовать JSX к виду
React.createElement
функции нуженBabel
. - Babel проходит через каждый узел JSX и преобразует его к функции
React.createElement()
. - На входе у нас JSX, на выходе из Babel - вызов функции
React.createElement()
в которую передан чистый JS. - Те JSX -> React.createElement(JS)
- React.createElement возвращает ReactElement - он является простым JS объектом узла DOM с его свойствами и наследниками.
- Функция React.createElement не создает целое дерево! Она создаёт простой JS объект для одного узла.
- Другими словами
- В React каждая часть UI является компонентом и почти каждый компонент имеет состояние (state).
- При изменении state/props компонента, React обновляет VDOM.
- После обновления VDOM, React сравнивает его текущую версию с предыдущей. Этот процесс называется «поиском различий» (diffing).
- После обнаружения объектов, изменившихся в VDOM, React обновляет соответствующие объекты в реальном DOM.
- Это существенно повышает производительность по сравнению с прямыми манипуляциями DOM. Именно это делает React высокопроизводительной библиотекой JS.
- Ссылки
- Habr - Немного о том, как работает виртуальный DOM в React
- Habr - Как работает React: подробное руководство
- Habr - Объясняем современный JavaScript динозавру
- Habr - Немного о том, как работает виртуальный DOM в React
- Medium - Как работает Virtual DOM ?
- csssr - Основы производительности React-приложений
- Компоненты написанные на
-
Примеры (Разобрать) *
- VNode = ReactElement
- h = React.createElement
- Сценарий 1: При первом запуске
- Когда наше приложение загружается в первый раз, React создаёт ReactElement с наследниками и атрибутами для основного компонента.
- Потом мы создаём реальный DOM для родительского узла
- Повторяем эти действия (создание реального DOM) для всех наследников
- Добавление наследников к родителям
- Обработка дочерних компонентов наследников
- Повторяем цикл для каждого дочернего узла.
- И наконец последний пункт. Здесь мы просто вызываем “componentDidMount” для всех компонентов (дочерних и родительских)
- После того как все сделано, ссылка на реальный DOM добавляется к каждому экземпляру компонента!. Данная ссылка используется для всех остальных действий (создание, обновление, удаление), чтобы сравнить и избежать создания тех же DOM узлов.
- Сценарий 2: Удаление узла из DOM, который не имеет наследников
- После первоначального рендеринга, каждое изменение считается “обновлением”. Цикл обновления работает почти так же как и цикл создания
- Отличие в том, что это приводит к вызову “componentWillReceiveProps”, “shouldComponentUpdate”, и “componentWillUpdate” для каждого компонента.
- Кроме того, цикл обновления, не создаёт повторно элементы которые уже присутствуют в DOM. Чтобы избежать повторное создание узлов в реальном DOM, каждый экземпляр компонента имеет ссылку на реальное DOM дерево, которое было создано во время начальной загрузки. И когда ReactElement создан, каждое его свойство сравнивают с узлами в реальном DOM. Если такой узел уже существует, цикл переходит к следующему узлу.
- Мы удаляем элемент из VDOM. Происходит сравнение с реальным DOM (скорее всего, с его копией, чтоб не читать реальный DOM, т.к. это дорогая операция). Видим, что они различаются. Перерисовываем реальный DOM
- Вызываем componentDidMount Сценарий #3: Удаление/Unmounting компонента
- Удаление компонента похоже на удаление одного узла. За исключением того, что мы удаляем узел, который имеет ссылку на компонент, в этом случае библиотека вызовет “componentWillUnmount”, а затем рекурсивно удалит все дочерние элементы DOM.
-
Reconciliation, Согласование
-
Процесс, посредством которого React обновляет DOM.
-
Когда состояние компонента изменяется, React должен вычислить, нужно ли обновлять DOM.
-
Он делает это, создавая виртуальную модель DOM и сравнивая ее с текущей моделью DOM.
-
В этом контексте виртуальная модель DOM будет содержать новое состояние компонента.
-
Глубокое сравнение сложных объектов.
-
Алгоритм сравнения в React. Делают обновления компонента предсказуемыми, и в то же время достаточно быстрыми.
-
Ссылки
-
-
Virtual DOM
- Это дерево React элементов на JavaScript.
- Дерево в формате JSON (?).
- React хранит два Virtual DOM:
- тот который отражает текущее состояние React;
- тот который отражает текущее состояние DOM.
- React сравнивает их между собой, не обращаясь к реальному DOM без крайней необходимости (т.к. чтение реального DOM - ресурсоёмкая операция)
- React следит за изменениями в Virtual DOM и автоматически изменяет DOM в браузере так, чтоб он соответствовал виртуальному.
- Процесс сравнения схож с тем, как Github работает при обнаружении изменений в файле.
- Когда VDOM обновляется, React сравнивает его с предыдущим снимком VDOM, а затем обновляет только то, что изменилось в реальной DOM.
- Если ничего не изменилось, реальная DOM вообще не будет обновляться.
- Этот процесс сравнения старой VDOM с новой называется
diffing
. - React создает кэш-структуру в памяти, что позволяет вычислять разницу между предыдущим и текущим состояниями интерфейса для оптимального обновления DOM браузера.
- React считается быстрым из-за VirtualDOM.
- В компоненте есть метод
render()
— вызывается при каждом обновлении компонента. - Входными данными для
render()
являются свойства (props
) и внутреннее состояние (state
). - Когда для
render()
меняются входные данные (props
/state
), меняется и результат выполненияrender()
. - Результат
render()
обрабатывается React — создаётся новая версия VDOM. - Этот VDOM сравнивается с предыдущим VDOM.
- Когда React видит, что эти VDOM отличаются — он переводит разницу в операции с DOM API, которые будут отрисованы в документе.
- В реальный DOM вносятся только необходимые изменения (DOM не пересоздаётся целиком).
- Ссылки
- Оф. документация - Виртуальный DOM и детали его реализации в React
- Habr - Немного о том, как работает виртуальный DOM в React
- Как работает Virtual DOM ?
- csssr - Основы производительности React-приложений
- React и SEO: преимущества изоморфности React для одностраничных приложений
- learnjavascript - про обычный DOM
- Medium - Как работает Virtual DOM?
- Habr - Немного о том, как работает виртуальный DOM в React
- IT-Kamasutra #86 - Virtual DOM
- React Fiber Architecture
- Habr - Error Boundaries в React: препарируем лягушку
-
VDOM — Проблемы. Оптимизация
-
Операции с VDOM тоже могут быть медленными.
-
Результат рендера React — это многоуровневый объект.
-
Сравнение результатов рендера — полное, глубокое сравнение двух объектов (не «по ссылке»).
-
Более того, React будет делать полный перерендер компонента при любом вызове SetState (даже если данные не поменялись) и при перерендере родителя. То есть, если у вас большое приложение, и вы вызываете setState у корневого компонента, у вас всё приложение целиком будет перерендерено. React построит VDOM для всего приложения, сравнит его с предыдущим результатом и в DOM поместит те самые незначительные правки (если они даже были). Всё это приведет к значительным потерям в производительности приложения.
-
Это решается через
shouldComponentUpdate
иPureComponent
. -
Тут тоже есть две проблемы:
- Если в state есть ссылочные типы.
- Если мутировать объект, то нет никакой возможности проверить, изменилось ли значение, так как объект в текущем и новом state будет ссылаться на один и тот же объект.
- Т.е. компонент не будет перерендериваться, хотя данные в действительности поменялись.
- Решение:
- либо заменяем все мутабельные операции на аналогичные иммутабельные операции,
- либо создаем новую ссылку и затем уже её мутируем.
- Обратная ситуация — каждый раз создаем ссылочный тип данных, даже если данные в нем не поменялись.
- Например, прогоняем данные через map - на выходе всегда новый массив, даже если данные не изменились.
- Получается, что у нас каждый раз создается новая ссылка => компонент будет перерендериваться, хотя не должен.
- Решение:
- мемоизация (разновидность кэширования). Запоминаем предыдущие результаты вызова функции, и если вызывается снова - используем их из кэша. Есть специальные библиотеки с разной реализацией.
- Важен тот момент, что так как значение берется из кеша, то возвращается та же самая ссылка. И так как у нас все данные иммутабельны, то нужна мемоизация, которая будет проверять значения по ссылке, а не глубоким сравнением
-
Также важно:
- использование стрелочных функций, bind и литералов массивом/объектов в рендере создает новую ссылку при каждом рендере.
- Решение
- использованием bind один раз в конструкторе или использованием свойств класса и выносом литералов за пределы рендера.
- См. подробнее: csssr - Основы производительности React-приложений
-
-
VDOM — Альтернативны
- Для динамичной интерактивной веб-страницы надо чтобы DOM обновлялся максимально быстро, сразу после изменения состояния элемента.
- Для этого некоторые фреймворки используют прием
dirty checking
— регулярный опрос состояния документа и проверка изменений в структуре данных. - Это сложная задача в случае высоко-нагруженных приложений.
- Virtual DOM же хранится в памяти => React может менять его моментально, сразу в момент изменения настоящего DOM.
- React «собирает» такие изменения, сравнивает их с состоянием DOM, а затем перерисовывает изменившиеся компоненты.
- При данном подходе мы не производим регулярное обновление DOM.
- Поэтому React приложения «высоко-производительны».
-
VDOM — Изоморфность
- Virtual DOM позволяет React легко создавать изоморфные приложения.
- В других JS-фрэймворках клиентская часть кода часто полагается на DOM браузера, которого нет на серверной стороне => нельзя использовать один код и на клиенте, и на сервере.
- React дает абстракцию браузерного DOM в виде VDOM — код, который работает с виртуальным DOM в React не зависит от браузера и может выполняться на сервере.
-
VDOM — React Fiber
- Это и название архитектуры, и название сущности из внутренностей React. Появились в React 16.
- Посмотрим результат вызова
React.createElement()
в консоль — там выводится гораздо больше информации, нежели чем ожидается. - Суть вот в чём: помимо дерева React-элементов/компонентов, существует ещё и набор неких Fiber-нод, которые к этим элементам/компонентам привязаны. В этих нодах содержится - внутреннее состояние React-элемента/компонента (полезное только для React): какие были пропсы ранее, какой следующий effect запустить, нужно ли рендерить компонент сейчас - и т. д.
-
Key *
- Специальный атрибут, связывает данные и элементы React.
- Помогают React идентифицировать, какие элементы были изменены, добавлены или удалены.
- Помогают механизму
reconciliation
(согласование) — алгоритм вычисления, надо ли обновлять DOM. Глубокое сравнение сложных объектов. - Ключи оптимизируют работу с элементами массивов, уменьшают количество ненужных удалений и созданий элементов.
- Строковый атрибут, особое уникальное свойство элемента. .
- Точнее — это
special props
. Не включается в объектprops
и недоступно внутри самого компонента. - Ключ необходимо задавать при создании списков элементов.
- Надо использовать только внутри
.map()
и подобных методов перебора массива. Например.filter()
? - Внутри вызова
map()
обязательно указывать ключи для элементов. - Ключи должны быть заданы элементам внутри массива — предоставляют элементам постоянный идентификатор.
- Ключи оптимизируют работу с элементами массивов, уменьшают количество ненужных удалений и созданий элементов.
- Например, если надо удалить одну статью из 10 - React удалит только статью с нужным key, а остальные перестраивать не
- будет.
- Ключи и Reconciliation
- Главная задача ключей в React — помогать механизму
reconciliation
. - Без
key
механизмreconciliation
сверяет компоненты попарно между текущим и новым VDOM. - Из-за этого может происходить много лишних ререндеров => замедляет работу приложения.
- Если есть
key
reconciliation
не сверяет компоненты попарно. - Он ищет компоненты с тем же key (тег / имя компонента при этом учитывается) => уменьшается количество ререндеров.
- Обновлены/добавлены будут только те элементы, которые были изменены/не встречались в предыдущем дереве.
- Главная задача ключей в React — помогать механизму
- Key и индекс массива
- При смене данных ключи должны меняться.
- Ошибка: использование индекса элемента в массиве как key. Массив изменится, а индекс останется тот же...
- Если у вас нет постоянных идентификаторов для отрисовываемых элементов, в крайнем случае вы можете использовать индекс элемента в массиве. Мы не рекомендуем использовать индексы для ключей, если порядок элементов может измениться. Это может негативно сказаться на производительности и вызвать проблемы с состоянием компонента. Если вы решите не назначать явный ключ для списка элементов, тогда React по умолчанию будет использовать индексы в качестве ключей.
- Хороший ключ
- Лучший ключ — строка, которая однозначно идентифицирует элемент списка среди его соседних элементов.
- Идеальные ключи должны браться из самих данных - id, уникальный title или ещё что-то в этом роде.
- Уникальность
- Ключи в массивах должны быть уникальными среди элементов того же уровня.
- Им не обязательно быть глобально уникальными.
- Можно использовать те же самые ключи при создании двух разных массивов.
- Примеры
-
function ListItem(props) { const value = props.value; return ( // Неправильно! Здесь не нужно указывать ключ: <li key={value.toString()}> {value} </li> ); } function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => // Неправильно! Здесь должен быть указан ключ: <ListItem value={number}/> ); return ( <ul> {listItems} </ul> ); }
-
- Ссылки
-
Композиция в React *
- Комбинирование меньших компонентов при формировании большего.
-
Reverse Data Flow - обратный поток данных
-
Изменение state родительского компонента из его компонента-потомка.
-
Реализуется при помощи callback-функций
-
Например:
- есть два компонента: Родитель и Потомок
- в Родителе хранится состояние-state (например цвет фона)
- в Потомке происходит что-то, что должно поменять состояние Родителя (например, нажата кнопка)
- как из дочернего компонента повлиять на состояние родительского?
-
Делаем так:
- в Родителе кроме
state
определяем функцию для изменения этогоstate
- эту функцию передаём в виде
callback
Потомку (черезprops
) - Потомок вызывает
callback
, тот отрабатывает в Родителе, и там меняется состояние
- в Родителе кроме
-
Другими словами
- В React обычным является кода родитель управляет потомком.
- Так же часто надо управлять родителем из дочернего компонента.
- Такой подход и называется "Обратный поток данных".
- В родительском компоненте, там где хранится состояние, хранится и обработчик этого события.
- Нужно только передать этот обработчик в дочерний компонент в качестве props.
- В дочернем компоненте, в нужный момент я просто подставлю эту функцию из props, и отработает изменение состояния в родительском компоненте.
-
Ссылки
-
-
State lifting - подъём состояния
-
Перенос данных / функций из дочернего элемента в родителя, чтоб они были доступны нескольким потомкам.
-
А из потомка вызываю эти обработчики как коллбэки (через props) или получаю эти данные в виде props.
-
Если несколько компонентов должны отражать одни и те же изменяющиеся данные — поднимаем общее состояние до ближайшего общего предка.
-
Ссылки
-
-
Refs
- Аттрибут HTML-элемента или классового компонента.
- Если быть точным - это special props. Не включается в объект props и недоступно внутри самого компонента.
- От reference - ссылка.
- Refs возвращает ссылку на элемент.
- Используются для получения ссылки на узел DOM или компонента в React.
- Почти как старые добрые getElementById.
- Работают только в классовых компонентах.
- В функциональных — использовать хук
useRef
. Ref
нужен чтоб «достучаться» к конкретному элементу и вызвать метод.- Добавляем атрибут
ref
в компонент для обратного вызова. - Полезно в нескольких случаях. Например:
- хочу прочитать значение элемента без React
- навесить jQuery библиотеку на элемент
- вызвать какой-то нативный метод - например focus.
- В обычном потоке данных React родительские компоненты могут взаимодействовать с дочерними только через пропсы.
- Чтобы модифицировать потомка — надо заново отрендерить его с новыми пропсами.
- Могут возникать ситуации, когда требуется императивно изменить дочерний элемент (React-компонентом или DOM-элемент), обойдя обычный поток данных. Для этого есть refs
- Обычно рефы присваиваются свойству экземпляра класса в конструкторе, чтобы на них можно было ссылаться из любой части компонента. Refs становятся доступны после метода render и перед componentDidMount.
- Почему бы не брать ссылку по ID элемента?
- Под каждый элемент должна быть уникальная id, тогда как названия ref могут повторяться в разных компонентах.
- Это противоречит “философии” React.
- Best Practise
- Выносите функции для получения ref в методы
- Не используйте string рефы (старый синтаксис ref)
- Избегаем использования
- Старайтесь не использовать, если в них нет реальной необходимости.
- Избегайте использования рефов в ситуациях, когда задачу можно решить декларативным способом.
- Например, вместо того чтобы определять методы open() и close() в компоненте Dialog, лучше передавать ему prop isOpen
- Ref — отличный способ доступа к DOM элементам, но его нужно применять с осторожностью.
- Это не «React way», а просто возможность доступа к DOM элементам. Если это возможно - лучше использовать state или props вместо refs, так как они поддерживают правильный поток данных в приложении, а refs нет.
- Применение оправдано
- Управление фокусом, выделение текста или воспроизведение медиа.
- Анимации.
- Интеграция с DOM библиотеками.
- Современная альтернатива - хук
useRef
- Работает в функциональных.
- Хук
useRef
позволяет сохранить некоторый объект, который можно изменять, и который хранится в течение всей жизни компонента.
- Ссылки
-
Как правильно получать данные из html-элемента (без использования ref)
- Не уверен, что это правильное решение. Изучать.
-
let onQuoteChanged = (event) => { let text = event.target.value; }; return (<textarea onChange={onQuoteChanged} value="Test" />)
Компоненты
-
Названия компонент начинаются с Заглавной буквы
- Это важно, так как в работе будут сочетаться HTML-элементы и элементы React.
- Названия со строчных букв зарезервированы для HTML. Если вы попробуете назвать элемент просто button, при рендере фреймворк проигнорирует его и отрисует обычную HTML-кнопку.
-
Компоненты = чистые функции
- React-компоненты обязаны вести себя как чистые функции по отношению к своим пропсам.
- Чистые функции не пытаются ничего изменить и всегда отдают тот же результат (при условии, что на вход подаются одни и те же данные).
- Функция не должна работать ни с какими глобальными объектами или генерировать данные. Всё с чем она работает - только с тем, что приходит в неё через props (т.е. через параметры функции).
- Такие функции не меняют свои входные данные и предсказуемо возвращают один и тот же результат для одинаковых аргументов. Возвращаемая разметка должна зависеть только от входящих значений props - если 100 раз вызвать функцию с одними и теми же значениями props, мы 100 раз получим один и тот же результат.
- State даёт компонентам возможность реагировать на действия пользователя, ответы сервера и другие события, не нарушая чистоту компонента.
- Если есть локальный стэйт - компонента не является чистой функцией. Т.к. она может хранить своё состояние, использовать его, и в следующий раз при тех же входящих значениях вернуть новый ответ (т.к. сохранила в стэйте какие-то данные с прошлого вызова, и использовала их для вычисления результата. Т.е вычисляет ответ не только по входящим данным, но ещё и по стэйту.)
- Такие компоненты легко тестировать. Легко предсказать что они вернут.
- Определения «чистой функции»
- Детерминированная функция, которая не производит побочных эффектов.
- не меняют свои входные данные и предсказуемо возвращают один и тот же результат для одинаковых аргументов.
- Чистая функция
идемпотента
- при повторении операции даст тот же эффектдетерминирована
- для одних и тех же данных всегда выдаёт тот же результатиммутабельна
- неизменяема. Функция не меняет входящие данные. Делает копию, и работает уже с ней.- без
сайд-эффектов
- без побочных эффектов. Например: какой-то внешний объект изменился, функция от него зависела, и при тех же входящих данных (которые мы напрямую передали при вызове функции) мы получили новый результат (т.к. она ещё взаимодействует с каким-то внешним объектом, который тоже меняется). Например, нельзя делать AJAX-запросы
- Как поддерживать чистоту компонент:
- Никогда не менять по ссылке внешние (глобальные) переменные, массивы и т.д.
- Не сортировать и вообще не трогать. Особенно то, что приходит в props.
- Если надо изменить - создавай в компоненте отдельную переменную, записывай в неё, и её меняй.
- Пропсы можно только читать!
- Компонент никогда не должен что-то записывать в свои пропсы — вне зависимости от того, функциональный он или классовый.
- У нас ссылочный тип данных - функция изменит props, и они изменятся в объекте где хранятся (например объект в памяти).
- Соответственно эти изменения могут вылезти где-то ещё. Один метод компонента случайно изменил данные, а другой метод потом взял уже изменённые (хотя ему нужны были оригинальные)...
- Никогда не менять по ссылке внешние (глобальные) переменные, массивы и т.д.
- Пример чистой функции
- не меняет свои входные данные и предсказуемо возвращает один и тот же результат для одинаковых аргументов:
-
function sum(a, b) { return a + b; }
- Пример «нечистой функции»
- записывает данные в свои же аргументы:
-
function withdraw(account, amount) { account.total -= amount; }
- Ссылки
-
Компоненты контейнерные и презентационные (умные/глупые)
- Два типа компонент по типу задач, которые выполняют.
- Контейнерная
- Отвечает за данные и операции с ними.
- Например, берёт на себя общение со Redux или AJAX запросы.
- Позволяет поддерживать внутреннюю (глупую) компоненту чистой.
- Всегда работают как обёртка вокруг другой компоненты - контейнерной или презентационной.
- Обычно не содержат разметки и не имеют CSS-стилей. Их задача - делать грязную/сложную работу (запросы и т.д.), а не отрисовывать данные.
- Их часто создают с использованием React-Redux, они могут осуществлять диспетчеризацию действий Redux.
- Презентационная
- Отвечает лишь за отрисовку полученных данных. И передачу в систему данных от пользователя (клик мышкой, ввод текста...).
- Не осведомлены о состоянии Redux и прочем. Обычно содержат DOM-разметку (JSX, HTML...).
- Часто обрачиваются контейнерными компонентами, которые берут на себя общение со Store и прочую логику.
- А презентационная компонента остаётся чистой, мало знает об окружении, не сильно с ним связана => может быть легко переиспользована в другом проекте. И легко - протестированна.
- Могут быть обёрнуты контейнерной компонентой, либо работают без них (сами по себе)
- Получают данные через свойства и могут вызывать коллбэки, которые также передаются им через свойства. Не должны изменять данные.
- Подробнее
- Отвечают за внешний вид
- Могут содержать как другие presentation компоненты, так и контейнеры
- Поддерживают слоты (Often allow containment via this.props.children)
- Не зависят от приложения (Redux и т.д.)
- Не зависят от данных
- Интерфейс основан на props
- Часто stateless, т.е. не имею своего состояния
- Часто функциональные
- UNSORTED
- Контейнерная компонента - обёртка вокруг презентационной компоненты, чтоб сохранить её чистой (не зависимой от props, store, state...).
- Тогда презентационную можно будет пере-использовать в других проектах и т.д.
- Т.е. чтоб презентационная компонента не использовала actionCreator из dispatch и прочее.
- Вместо этого мы всё получаем через props, а вместо dispatch используем callbacks.
- С другой стороны, если бы контейнерной компоненты не было - нам нужно было бы каждый callback прокидывать из store через всё дерево в каждую презентационную компоненту. Это неудобно.
- Поэтому мы до контейнерной компоненты прокидываем обычный dispatch + state, в ней вызываем отрисовку чистой презентационной компоненты, и передаём ей (через props) из этого dispatch колбэки и state.
- Контейнерная компонента
- берёт на себя общение со Store (ООП-объект, хранящий state).
- общается со Store через context API (https://ru.reactjs.org/docs/context.html)
- И позволяет поддерживать внутреннюю компоненту чистой
- Прокидывает в неё данные из Store (props, dispatch колбэки)
- отрисовывает презентационную компоненту
- Если кратко, компоненты-контейнеры отвечают за данные и операции с ними. Их состояние передается в виде свойств в компоненты-представления и отображается.
- Организация контейнерных компонент и AJAX
- снаружи - контейнерная, которая через connect работает со Store
- в ней (в том же файле) - классовая, которая делает AJAX-запросы и прочие сайд-эффекты
- классовая вызывает отрисовку функциональной (которая лежит в отдельном файле). Та получает только props и отдаёт JSX
- Презентационная компонента
- чистая компонента, получает props, отдаёт JSX.
- Всё получает через props (из контейнерной компоненты), а вместо dispatch используем callbacks.
- Другое деление
smart-компоненты
- манипулируют даннымиdumb-компоненты
- что-то отрисовывают
- Пакет «React-Redux» предоставляет привязки React для контейнера состояния Redux, чрезвычайно упрощая подключение React-приложения к хранилищу Redux. Это позволяет разделять компоненты React-приложения, основываясь на их связи с хранилищем. А именно, речь идёт о следующих видах компонентов:
- Презентационные компоненты. Они отвечают лишь за внешний вид приложения и не осведомлены о состоянии Redux. Они получают данные через свойства и могут вызывать коллбэки, которые также передаются им через свойства.
- Компоненты-контейнеры. Они ответственны за работу внутренних механизмов приложения и взаимодействуют с состоянием Redux. Их часто создают с использованием react-redux, они могут осуществлять диспетчеризацию действий Redux. Кроме того, они подписываются на изменения состояния.
- Ссылки:
-
Компоненты с состоянием и без (stateful/stateless)
- Некоторые компоненты используют в React метод `setState()``, а некоторые нет.
Stateful
- компоненты имеющие состояние (state). Всегда являются классовыми компонентами (у функциональных своего state нет).Stateless
- компоненты без состояния. Могут быть и функциональными и классовыми.
- Недостатки stateful-компонент
- Наличие состояния затрудняет тестирование компонентов
- Наличие состояния затрудняет понимание работы компонента
- Наличие состояния слишком легко позволяет вставить в компонент бизнес-логику. А это не хорошо
- Наличие состояния затрудняет обмен информацией с другими частями приложения. Состояние легко передаётся вниз ( потомкам), а вот в стороны - намного труднее
- Ссылки:
- Некоторые компоненты используют в React метод `setState()``, а некоторые нет.
-
Компоненты классовые и функциональные
- Функциональные
- Functional components
- простые компоненты, созданные как функции.
- React - функционально-ориентированная библиотека, так что рекомендуется использовать эти компоненты, там где можно.
- Кстати, они появились позже
-
//Создаются так: const Welcome = (props) => {} //Или так: function Welcome(props) {}
- Классовые
- Class-Components
- объявлены как класс, имеют методы жизненного цикла, локальное состояние, refs и многие другие штуки.
- Без лишней необходимости их лучше не использовать. С появлением хуков вообще становятся менее востребованны.
-
//Создаются так: class Welcome extends React.Component {} //Или так: class Welcome extends React.PureComponent {}
- UNSORTED
- Есть два типа компонентов
- функциональные - states - очень простые (тупые), без состояний (presentational, stateless, dumb). Это функция, которая принимает props и возвращает JSX
- классовые - с использованием классов ES6 - с состояниями. Можно использовать методы жизненного цикла. Необходимы, если компонент имеет состояние или значимые методы.
- React-разработчики стараются минимизировать использование классовых компонент. Если можно решить вопрос функциональной компонентой - так и делай
- Есть два типа синтаксиса
- Функциональные компоненты.
- Для очень простых компонентов, почти без логики (stateless компоненты):
-
import React, {Component} from 'react'; function Article(props) {}
- Классовый компонент (классы компонентов).
- Позволяет использовать дополнительные возможности, такие как локальное состояние и методы жизненного цикла.
- Наследуется от базового компонента Component
- Должны содержать функцию render()
- Компоненты, основанные на классах, могут хранить информацию о текущей ситуации. Эта информация называется состоянием (state), она хранится в JS-объекте.
-
import React, {Component} from 'react'; class Article extends PureComponent { render () {} }
- метод
render
нужен обязательно, он отвечает за то, как будет выглядеть компонент. props
будет жить вthis.props
-
- Функциональные компоненты.
- Классовые компоненты
- Класс, который наследуется от метода React.Component, у которого есть как минимму метод render, и который возвращает JSX
- Для чего нужны классы? Чтоб создавать однотипные объекты на базе этих классов и реализовать в них концепции ООП (инкапсуляция, полиморфизм, наследование)
- Характеристики классов
Полиморфизм
- свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.Наследование
– это свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью. Класс, от которого производится наследование, называется базовым или родительским. Новый класс – потомком, наследником или производным классом.Инкапсуляция
– сокрытие деталей. Свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе и скрыть детали реализации от пользователя.Абстрагирование
– это способ выделить набор значимых характеристик объекта, исключая из рассмотрения незначимые. Соответственно, абстракция – это набор всех таких характеристик.
- Методы и обработчики классовых компонент писать так (кроме render):
onClick = () => {}
- https://www.youtube.com/watch?v=vO63wxg4aKY
- Позволяет решить вопросы c контекстом вызова (bind и т.д.) -
- Не забыть, что все обработчики (практически все) объявляются внутри классовой компоненты, как её метод
- setState
- метод компонента. Обновляет его состояние, и вызывает перерисовку
- Вызов setState позволяет React перестроить ваше приложение и обновить DOM.
- Обычно когда необходимо обновить компонент вы просто вызываете setState с новым значением переданным в виде объекта в функцию setState: `this.setState({someField:someValue})``
- setState - использование стрелочной функции вместо объекта
- https://clck.ru/GDfFh
- раньше:
setState(nextState)
- теперь:
setState((s,p) => s)
- позволяет использовать текущее состояние this.State, не опасаясь, что произойдёт что-то не то.
- То есть: позволит получить вам достоверные значения для state и props компонента.
- Иначе: так как this.props и this.state могут обновляться асинхронно, то не стоит полагаться на их значения для вычисления нового состояния.
- Т.е. я хочу просто инвертировать свойство внутри state (например open/close), и делаю !this.state.isOpen. Но, к моменту выполнения кода этот параметр может уже измениться из другого места, и я получу неожиданный результат
- Обновления state могут быть асинхронными
-
React может сгруппировать несколько вызовов setState() в одно обновление для улучшения производительности.
-
Поскольку this.props и this.state могут обновляться асинхронно, вы не должны полагаться на их текущее значение для вычисления следующего состояния.
-
Например, следующий код может не обновить счётчик:
-
// Неправильно this.setState({ counter: this.state.counter + this.props.increment, });
-
Правильно будет использовать второй вариант вызова setState(), который принимает функцию, а не объект. Эта функция получит предыдущее состояние в качестве первого аргумента и значения пропсов непосредственно во время обновления в качестве второго аргумента:
-
// Правильно this.setState((state, props) => ({ counter: state.counter + props.increment }));
-
- Set State перестраивает весь виртуальный DOM компонента, на котором он вызван.
- И всех его вложенных компонентов! А потом вносятся изменения в реальный DOM.
- Поэтому - если нет необходимости - не вызывай изменения set state у родителей, чтоб лишний раз не перестраивать виртуальный DOM всех их потомков. Т.е. верхние уровни задействуем только тогда, когда это нужно
- Какой второй аргумент может быть передан в setState?
- Это функция обратного вызова.
- Она реализовывается строго после setState, когда элемент отрендерен, и является полностью опциональной.
- Рекомендуется отдать предпочтение другому методу, нежели данной функции, но знать о ее существовании и принципе работы не помешает:
-
this.setState( {username: 'tylermcginnis33'}, () => console.log('setState has finished and the component has re-rendered.') )
-
- Есть два типа компонентов
- Ссылки:
- Функциональные
-
Компоненты контролируемые и не контролируемые (controlled и uncontrolled)
-
Контролируемые
- работают черезstate
, получают и пишут данные обычно вstate
. -
Неконтролируемые
- работают напрямую с виртуальным DOM-деревом, обычно через ссылку ref. -
React делает упор на контролируемых компонентах.
-
В HTML элементы формы, такие как
input
,textarea
иselect
, обычно поддерживают свое собственное состояние и обновляют его на основе пользовательского ввода. Когда - пользователь отправляет форму, значения из элементов, упомянутых выше, отправляются вместе с формой. -
Таким образом сам элемент формы становится
источником истины
— хранит информацию о своём состоянии. -
Это неправильно с точки зрения
Flux-архитектуры
. -
В React это работает по-другому.
-
Компонент, содержащий форму, будет отслеживать значение ввода в своем состоянии (local state или Redux) и повторно визуализировать компонент каждый раз, когда вызывается функция обратного вызова
onChange
, например, при обновлении состояния. -
Пользователь ввёл букву в input - она не отобразилась, но сработал обработчик
onChsnge
этого input. Данные о новой букве ушли в стейт -> стейт обновился -> форма перерисовалась -> в input видна новая буква. -
Элемент ввода формы, значение которого контролируется React, таким образом называется «контролируемым компонентом».
-
Ссылки:
-
-
PureComponent, React.memo
-
Особый способ создания классовых компонент.
-
Изменяет lifecycle-метод
shouldComponentUpdate
, автоматически проверяя, нужно ли заново отрисовывать компонент. -
PureComponent будет вызывать функцию
render()
, только если обнаруживает изменения вprops
или вstate
. -
В некоторых случаях
React.PureComponent
более эффективен и определенно уменьшает количество кода. -
По сути, вариант реализации метода
shouldComponentUpdate
- поверхностно сравниваются все старые/новыеprops
и все старые/новыеstate
. Если хоть что-то поменялось перерисовываем компонент -
Разница между
Component
иPureComponent
заключается в методеupdating lifecycle: shouldComponentUpdate
. -
Component
не реализуетshouldComponentUpdate()
,PureComponent
реализует его поверхностным сравнением пропсов и состояния. -
В Component этот метод выглядит так:
-
shouldComponentUpdate(){ return true; }
-
-
В PureComponent:
-
shouldComponentUpdate(nextProps, nextState){ return !shallowEqual(nextProps, this.props) || !shallowEqual(nextState, this.state); }
-
-
Что такое
shallowEqual
? Это по сути сравнение оператором === каждого элемента из prevProps с каждым элементом из nextProps. -
Метод
shouldComponentUpdate()
базового классаReact.PureComponent
делает только поверхностное сравнение объектов. Если они содержат сложные структуры данных, это может привести к неправильной работе для более глубоких различий (то есть, различий, не выраженных на поверхности структуры). -
Наследуйте класс
PureComponent
только тогда, когда вы ожидаете использовать простые пропсы и состояние, или используйтеforceUpdate()
, когда знаете, что вложенные структуры данных изменились. Также подумайте об использовании иммутабельных объектов, чтобы упростить процесс сравнения вложенных данных. -
Кроме того, метод
shouldComponentUpdate()
базового классаReact.PureComponent
пропускает обновление пропсов для всего поддерева компонентов. Убедитесь, что все дочерние компоненты также являются «чистыми». -
Важное замечание:
PureComponent
нужно использовать только для так называемыхpresentational
components, т.е. для тех компонент, которые НЕ обёрнуты в вызовredux connect()
. -
Для
container components
(т.е. тех компонент, которые обёрнуты вredux connect()
) нет смысла наследоваться отPureComponent
, т.к. методconnect()
оборачивает ваш компонент своей реализациейshouldComponentUpdate
, которая также используетshallowEqual
. Если вы по недосмотру унаследуете container component отPureComponent
- ошибок не будет, но это не имеет никакого смыла, т.к. ваш код по сути будет дважды делатьshallowEqual
, а зачем делать лишнюю работу? -
Важно помнить, что
PureComonent
пропускает отрисовку не только самого компонента, но и всех его “детей”, так что безопаснее всего применять его в presentational-компонентах, без “детей” и без зависимости от глобального состояния приложения. -
В случае, если pure-компонент имеет детей, все дочерние компоненты, зависящие от смены контекста, не будут реагировать на изменения, если в родительском pure-компоненте не будет объявлен contextTypes.
-
React.memo
- Если я хочу использовать функциональный компонент вместо классового — аналогичную оптимизацию даёт использование
React.memo
-
const Item = React.memo(props => { return 'some JSX' })
- Если я хочу использовать функциональный компонент вместо классового — аналогичную оптимизацию даёт использование
-
UNSORTED
- Важно: при вызове render() перерисовывается не только родительский компонент, но и все дочерние (хотя, в них свойства могли и не поменяться). Т.е. если у родительского компонента внутри render есть дочерние компоненты - они будут перерисовываться. Соотвественно, если мы вызываем render на родительском компоненте - перерисуем всё приложение. Чтоб решить этот вопрос - используем создание компонента от PureComponent
- Даже если я создал класс от PureComponent - это не гарантирует отсутсвие лишних ренедров при тех же данных. Одна из причин - анонимные функци (они при каждом рендере новые).
-
//Неверно: <Component onClick= {() => this.hangleClick}> //Верно: <Component onClick= {this.hangleClick}>
- автоматически проверяет, должен ли компонент обновляться. Не нужно писать shouldComponentUpdate самостоятельно.
- PureComponent будет вызывать функцию render(), только если обнаруживает изменения в props или в состоянии.
- В некоторых случаях React.PureComponent более эффективен и определенно уменьшает количество кода.
- Если в props вы передаёте объекты которые иногда мутируются, т.е. по ссылке они равны ===, но внутри какие-то данные поменялись (что само по себе выглядит странно в экосистеме redux + reselect, но вполне возможно технически), тогда использование PureComponent вам всё поломает, т.к. на экране какие-то компоненты перестанут перерисовываться!
- Если же у вас всё по уму, данные которые передаются через props являются скалярными типами (string, int, float, bool) или immutable объектами, тогда смело используйте PureComponent - в некоторых случаях он поможет избавиться от лишних вызовов render.
- Важное замечание: PureComponent нужно использовать только для так называемых presentational components, т.е. для тех компонент, которые НЕ обёрнуты в вызов redux connect().
- Для container components (т.е. тех компонент, которые обёрнуты в redux connect()) нет смысла наследоваться от PureComponent, т.к. метод connect() оборачивает ваш компонент своей реализацией shouldComponentUpdate, которая также использует shallowEqual. Если вы по недосмотру унаследуете container component от PureComponent - ошибок не будет, но это не имеет никакого смыла, т.к. ваш код по сути будет дважды делать shallowEqual, а зачем делать лишнюю работу?
- По сути, вариант реализации метода shouldComponentUpdate - поверхностно сравниваются все старые/новые property и все старые/новые state. Если хоть что-то поменялось - перерисовываем компонент
- Подводя итог, рецепт такой:
- presentational components наследуем от React.PureComponent
- container components (которые обёрнуты в redux connect()) наследуем от старого доброго React.Component
- Важно помнить, что PureComonent пропускает отрисовку не только самого компонента, но и всех его “детей”, так что безопаснее всего применять его в presentational-компонентах, без “детей” и без зависимости от глобального состояния приложения.
- В случае, если pure-компонент имеет детей, все дочерние компоненты, зависящие от смены контекста, не будут реагировать на изменения, если в родительском pure-компоненте не будет объявлен contextTypes.
-
Ссылки
- Оф. документация - React.PureComponent
- Habr - Разбор: как и зачем применять PureComponent в React
- PureComponent и Components
- csssr - Основы производительности React-приложений
- YouTube - Какие props портят производительность
- totser
- Habr - Оптимизация производительности в React
- IT-Kamasutra #87 - shouldComponentUpdate, PureComponent, memo
-
-
HOC (Higher Order Components) Компоненты высшего порядка
- HOC — функция.
- Принимает компонент и возвращает новый контейнерный компонент (функциональный или классовый).
- Если функция возвращает JSX - это компонент. Если возвращает другую компоненту - это HOC.
- HOC — способ повторного использования логики, кода.
- Позволяет создавать однотипные контейнерные компоненты, передавая в них презентационные компоненты (те, которые надо обернуть).
- HOC часто называют с префиксом «with» —
withRedirect
,withAuth
... - В зависимости от того, какую функциональность добавляет данный HOC.
- Примеры:
- Компонент, возвращает JSX
-
let User = (props) => { return <div> <h1>[props.name]</h1> </div> }
- Компонент, возвращает JSX (который отрисовывает другой компонент)
-
let UserContainer = (props) => { return <User example="text" /> }
- HOC — принимает компонент, возвращает другой компонент (контейнерный)
-
let HOComponent = (User) => { return UserContainer }
- Если точнее, HOC будет выглядеть примерно так:
-
let HOComponent = (Component) => { let WrapperContainer = (props) => { return <Component example="text" /> } return WrapperContainer }
Connect
из Redux - это тоже HOC. Ну, если быть точным - он возвращает HOCwithRouter
из React Router - это тоже HOC.
- Ссылки
-
HOC и Декораторы
-
Ещё есть декораторы. Декораторы и HOC делают одно и то же.
-
Основные отличия от HOC:
- после добавления декоратора свойство/класс можно использовать только в его оформленной форме. HOC pattern оставляет доступными для использования как компоненты более высокого, так и более низкого порядка.
- декораторы используются для мутации переменной, a HOC рекомендуется так не использовать.
- HOC должен представлять компонент, в то время как декораторы могут возвращать разные вещи в зависимости от реализации.
-
Ссылки по декораторам
-
-
Render props, Render Callbacks (Функция в render)
- Ещё один способ сделать логику переиспользуемой.
- Это становится возможным при помощи передаваемого children-а в виде функции.
- Ссылки
-
Компоненты — советы по организации кода
- Компоненты обычно располагаются в папке src/components.
- На каждый компонент обычно заводят отдельный файл.
- Название файла обычно = названию компонента, также с большой буквы. Например:
src/components/Article.js
- Каждый раз добавляя новую функциональность - думать чтоб вынести её в отдельный компонент.
- Каждый раз дублируя код - думать чтоб вынести её в отдельный компонент/функцию.
Жизненный цикл
-
Общее
- Методы, которые React вызывает при разных событиях из жизни компонента (появление, удаление...).
- Каждый классовый компонент имеет несколько «методов жизненного цикла».
- Можно переопределить методы для запуска кода в определённое время в процессе работы приложения.
- Единственный обязательный метод в подклассе React.Component — render(). Все остальные методы, описанные ниже, являются необязательными.
- Префикс в названии
- will - вызываются прямо перед тем, как что-то происходит,
- did - вызываются сразу после того, как что-то происходит.
- should - должен
- Ссылки
-
1. Монтирование
-
Когда экземпляр компонента создаётся и монтируется в DOM
-
constructor()
- Конструктор, в котором происходит начальная инициализация компонента
- Если вы не инициализируете состояние и не привязываете методы, вам не нужно реализовывать конструктор для вашего компонента React.
- Конструктор компонента React вызывается до его монтирования. При реализации конструктора подкласса React.Component вы должны вызвать super(props) перед любым другим оператором. В противном случае this.props не будет определен в конструкторе, что может привести к ошибкам.
- Как правило, в React конструкторы используются для двух целей:
- Инициализация локального состояния путем присвоения объекта this.state.
- Привязка методов-обработчиквов событий к экземпляру.
- Вы не должны вызывать setState() в constructor(). Вместо этого, если вашему компоненту нужно использовать локальное состояние, присвойте начальное состояние this.state непосредственно в конструкторе
-
static getDerivedStateFromProps()
- Заменяет componentWillReceiveProps()
- Возвращает объект для обновления state или null, чтобы ничего не обновлять.
- Этот метод запускается при каждом рендере, независимо от причины.
- Вызывается непосредственно перед вызовом метода render, как при начальном монтировании, так и при обновлениях.
- Cуществует для редких случаев, когда state компонента зависит от изменений в props.
- Т.е. когда состояние компонента должно меняться, в зависимости от изменений props, который передаёт родитель.
- Как и метод render, getDerivedStateFromProps должен быть чистой функцией props и state.
- Есть более простые альтернативы, при прочих равных лучше использовать их.
- Убедитесь, что вы знакомы с простыми альтернативами:
- Чтобы выполнить побочный эффект при изменении пропсов (например, сетевой запрос или анимацию) используйте componentDidUpdate.
- Чтобы повторно вычислить данные при изменении пропсов, используйте функцию мемоизации.
- Чтобы «сбросить» некоторое состояние при изменении пропсов, используйте управляемые компоненты или неуправляемые компоненты с ключом.
- В большинстве случаев от применения getDerivedStateFromProps (и его предшественника componentWillReceiveProps) можно избавиться, перемещая управление состоянием в родительский компонент.
- Помните, что большинство компонентов не нуждаются в getDerivedStateFromProps. Он не предназначен для использования точь в точь, как componentWillReceiveProps.
- Ссылки
-
render()
- Рендеринг компонента.
- Чистый метод, не пихать сюда никакой логики кроме той что относится к render.
- Только для того, чтобы строить виртуальный DOM компонента
- Когда вызывается рендер в React?
- когда меняются state или props
- в частности, когда мы вызываем функцию setState
- когда меняется родительский компонент
- когда меняются state или props
- Когда вызывается рендер в React для функциональных и классовых компонентов?
- Классовые:
- this.setState()
- this.forceUpdate()
- Функциональные
- useState
- useReducer
- Все
- Рендер родителя вызовет рендер всех его дочерних элементов (возможна оптимизация)
- повторный рендеринг будет вызван, если повторно запустить ReactDOM.render(), что эквивалентно forceUpdate() на корневом компоненте.
- Классовые:
- Как оптимизировать рендер компонента?
- shouldComponentUpdate — метод жизненного цикла классового компонента, если он вернет false то рендер не будет запущен
- React.PureComponent — класс, реализующий типовой shouldComponentUpdate.
- React.memo — HOC, который предотвращает повторный рендер, если входные props не изменились
- useMemo() — чтобы в функциональном компоненте сохранить ссылки на объекты между рендерами
- useCallback() — чтобы в функциональном компоненте сохранить ссылки на объекты между рендерами
- аттрибуты key
- Context /useContext() - Context API обеспечивает передачу переменных в дерево компонентов, без их непосредственной передачи в props данных компонентов.
- оптимизация структуры компонет - помещать логику ближе к месту использования данных
- Какие хуки использовать для оптимизации рендера?
- useMemo() — чтобы в функциональном компоненте сохранить ссылки на объекты между рендерами
- useCallback() — чтобы в функциональном компоненте сохранить ссылки на объекты между рендерами
- useContext() - Context API обеспечивает передачу переменных в дерево компонентов, без их непосредственной передачи в props данных компонентов.
- Ссылки
-
componentDidMount()
- После рендеринга компонента.
- Что деалем
- Выполнять запросы к удаленным ресурсам
- Реагируем на появление компонента в реальном DOM.
- Например: получить размеры, позиционирование, подписаться на изменение данных, повесить свои listener на DOM-элементы...
- Происходит один единственный раз - при первой отрисовке компонента на странице. При обновлении не вызывается.
- Все сайд-эффекты делать здесь!
- Например: вызываются запросы на сервер, AJAX-запросы, setTimeout и все манипуляции с DOOM
- Реакт вызывает у компонента метод
render()
, получает из негоJSX
, выводит этотJSX->HTML
на странице. - И потом вызывает у компонента метод
componentDidMount()
-
-
2. Обновление
-
Когда компонент перерисовывается. Может быть вызвано изменениями в state или props
-
shouldComponentUpdate()
-
Каждый раз при обновлении объекта props или state
-
позволяет оптимизировать приложение в ручном режиме, управляя тем, нужно ли перестраивать виртуальный DOM для этого компонента или нет?
-
т.е. позволяет оптимизировать перерисовку виртуального DOM - если в это компоненте ничего не поменялось, не перерисовываем
-
Вызывается при изменении родителей, и при смене setState в самом компоненте.
-
Не забывать, про сравнение ссылочных типов. Не должно быть мутации данных
- Каждый раз, когда меняются данные, должна создаваться новая ссылка.
- либо заменяем все мутабельные операции на аналогичные иммутабельные операции,
- либо создаем новую ссылку и затем уже её мутируем.
- Новая ссылка должна создаваться только тогда, когда меняются данные.
- Каждый раз, когда меняются данные, должна создаваться новая ссылка.
-
Предупреждает, что сейчас будем перестраивать виртуальный DOM этого компонента.
- Можно отреагировать на изменения - загрузить текст для статьи, которая открывается и т.д.
- Вызывается и при изменении родителей, и при смене setState в самом компоненте.
-
Ссылки:
-
-
render()
- См. выше
-
getSnapshotBeforeUpdate()
-
Позволяет компоненту брать некоторую информацию из DOM (например, положение прокрутки) перед её возможным изменением.
-
Возвращает значение снимка или null.
-
Любое значение, возвращаемое этим методом жизненного цикла, будет передано как параметр
componentDidUpdate()
. -
Применяется редко.
-
Может быть полезно в таких интерфейсах, как цепочка сообщений в чатах, в которых позиция прокрутки обрабатывается особым образом.
-
-
componentDidUpdate(prevProps, prevState, snapshot)
- Вызывается сразу после обновления компонента (если shouldComponentUpdate возвращает true)
- Не вызывается при первоначальной отрисовке.
- Реакт вызывает у компонента (точнее объекта) метод
render()
, получает из негоJSX
, выводит этотJSX->HTML
на странице (делаетappend
). - И потом вызывает у компонента метод
componentDidMount()
. - В компоненте что-то изменилось (пришли новые props, или обновился локальный state).
- Реакт снова вызывает метод
render()
, получает новыйJSX
, выводит этотJSX->HTML
на странице (но делает уже неappend
, аupdate
). - И потом вызывает у компонента метод
componentDidUpdate()
. - В качестве параметров передаются старые значения объектов props и state.
- Третий параметр - значение, которое возвращает метод getSnapshotBeforeUpdate
- Вызывается после того как отработал
render
, в каждом цикле перерисовки. - Это означает — можно быть уверенным, что компонент и все его дочерние компоненты уже перерисовали себя.
- В связи с этим эта функция является единственной функцией, что гарантировано будет вызвана только раз в каждом цикле перерисовки, поэтому любые сайд-эффекты рекомендуется выполнять именно здесь. Как componentWillUpdate и componentWillRecieveProps в эту функцию передается предыдущие props, состояние (state) и контекст, даже если в этих значениях не было изменений. Поэтому разработчики должны вручную проверять переданные значения на изменения и только потом производить различные апдейт операции.
- Делайте
- Выполняйте сайд-эффекты (Вызовы AJAX и т.д.)
- Не делайте
- Не вызывайте this.setState т.к. это будет вызывать циклическую перерисовку.
- Чаще всего нужен
- хорошее место для выполнения сетевых запросов, если вы сравниваете текущие свойства с предыдущими свойствами (
- если нас интересуют составляющие реального DOM (размер компонента, позиционирование...) например, не нужно делать сетевой запрос, если свойство не изменилось).
- Можно вызывать
setState()
, но этот вызов должен находится в условии. Иначе можно устроить бесконечный цикл. componentDidUpdate()
не будет вызываться, еслиshouldComponentUpdate()
возвращаетfalse
.- Оф. документация - метод жизненного цикла componentDidUpdate()
-
-
3. Демонтирование
-
Когда компонент удаляется из DOM
-
componentWillUnmount()
- Перед удалением компонента из DOM.
- Предупреждает что компонент будет удалён.
- Подчищаем подписки, проводим логику деструктуризации компонента
- Например, остановить и обнулить таймер
-
-
4. Обработка ошибок
-
При возникновении ошибки
-
componentDidCatch()
- Вызывается при ошибках:
- ошибки во время отрисовки
- ошибки в методе жизненного цикла
- ошибки в конструкторе любого дочернего компонента.
- Вызывается при ошибках:
-
-
Устаревшие методы. Избегайте их:
-
1. componentWillMount()
- Непосредственно перед рендерингом компонента
- Часто используется для получения данных (отправка запроса за статьёй на сервер и т.д.)
- Вместо него лучше использовать componentDid Mount
- На самом деле:
- если нужно выставить изначальное состояние компонента - делайте это в конструкторе
- если нужно изменять DOM - делайте это в componentDid Mount
-
2. componentWillReceiveProps()
- При обновлении props, до монтирования новых.
- Вместо него добавляется новый - getDerivedStateFromProps
- Здесь надо обновлять state компонента из приходящих props
- Тут приходят новые props.
- Вызывается до монтирования новых пропсов в компонент.
- Обычно в этой функции устанавливаются свойства компонента (в том числе из this.state), которые зависят от значений из пришедших в компонент props
- React передаёт новые props, которые можно сравнить с текущими. Вызывается только если поменялся кто-то из родителей и буду меняться какие-то props.
- Два основных варианта использования:
- поменялись важные данные и надо отреагировать. Например пришла новая статья и надо загрузить её с сервера
- мы завязали состояние компонента на porps (не лучшая практика, но иногда оптимальная) - теперь надо следить за изменениями в props, и приводить state к нужному виду. Например, состояние статьи (свёрнута/развёрнута) приходят в компонент из другого компонента, через props. Тогда надо отслеживать -изменились ли эти props, и перерисовывать состояния статьи
-
2. componentWillUpdate()
- Перед обновлением компонента (если shouldComponentUpdate вернул true)
- Вместо него рекомендуется использовать componentDidUpdate
-
-
I. Другие API (есть у каждого компонента)
- setState()
- forceUpdate()
-
II. Свойства класса
- defaultProps
- displayName
-
III. Свойства экземпляра
- props
- state
-
Unsorted
- Сначала определяется шаблон React.js для создания элементов из компонента.
- Указывается где он будет использован. К примеру, внутри вызова функции рендера иного компонента или с помощью ReactDOM.render.
- Реакт создает экземпляр элемента и передает ему набор свойств (props), доступ к которым будет доступен через this.props.
- Поскольку описанное является JavaScript-ом, будет вызван метод конструктора класса (если он определен). Это первый из методов, которые называются методами жизненного цикла компонента.
- React обрабатывает результат вызова функции рендера.
- Затем React осуществит монтирование компонента: взаимодействуя с браузером через DOM API, React выполнит рендеринг.
- Следом, Реакт вызывает другой метод жизненного цикла, который называется componentDidMount. Этот метод можно использовать, чтобы что-то сделать в дереве документа. Весь DOM, с которым мы работали ранее был виртуальным.
- Демонтирование. Жизненный цикл некоторых компонентов заканчивается уже на этом этапе. Компоненты могут быть демонтированы из документа по разным причинам. Однако, перед этим Реакт вызывает другой метод – componentWillUnmount.
- Реакт контролирует состояние каждого компонента на случай изменений. Для того, чтобы React действовал эффективно, необходимо изменить поле состояния с помощью API React и функции this.setState.
- Входными данными для render() являются свойства (props) и внутреннее состояние, которое может быть обновлено в любое время.
- Когда для render меняются входные данные, меняется и результат ее выполнения.
- React.js ведет запись жизненного цикла компонента. Когда React.js видит, что один рендер отличается от другого, он переводит разницу между своим виртуальным представлением в операции с DOM API, которые будут отрисованы в документе.
- Теперь, когда мы знаем, что за магия происходит при изменении состояния компонента, рассмотрим оставшиеся концепции.
- Компонент может быть необходимо повторно отрисовать, если его состояние будет обновлено, либо, если родительский элемент изменит свои свойства.
- Если были изменены свойства, React.js вызовет метод жизненного цикла componentWillReceiveProps.
- Если объект или его свойства были изменены, React.js вызывает еще один метод – shouldComponentUpdate, который, по сути, является вопросом. Так что, если есть необходимость самостоятельно настроить процесс рендера, вы можете ответить на этот вопрос вернув true или false.
- Если shouldComponentUpdate не объявлен, Реакт вызовет безусловный componentWillUpdate и рассчитает различия между текущим компонентом и его новым видом, с учетом изменений.
- Если никаких изменений не зафиксировано, React.js ничего не сделает.
- Если разница есть, фреймворк отрисует компонент.
- Так как процесс обновления в любом случае произошел, Реакт вызовет метод componentDidUpdate.
- .
- Сначала мы определяем шаблон для React для создания элементов из компонента.
- Затем мы указываем React где будем его использовать. Например, внутри вызова функции render другого компонента или с помощью ReactDOM.render.
- Затем React создает экземпляр элемента и передает ему набор props, к которым мы можем получить доступ с помощью this.props. Эти props — это то, что мы передали на шаге 2.
- Поскольку это все JavaScript, то будет вызван метод конструктора (если он определен). Это первый метод из тех, что мы называем методами жизненного цикла компонентов.
- Затем React обрабатывает результат вызова функции render (получает виртуальный узел DOM).
- Поскольку это первый раз, когда React выполняет рендеринг элемента, React будет взаимодействовать с браузером (от нашего имени, используя DOM API), чтобы отобразить в нем элемент. Этот процесс широко известен как монтирование.
- Затем React вызывает другой метод жизненного цикла, называемый componentDidMount. Мы можем использовать этот метод, чтобы, например, сделать что-то в DOM, который, как мы знаем, существует в браузере. До этого метода жизненного цикла, DOM, с которым мы работали, был виртуальным.
- Некоторые истории компонентов заканчиваются здесь. Компоненты демонтируются из DOM браузера по разным причинам. Но перед тем как это произойдет, React вызывает другой метод жизненного цикла, componentWillUnmount.
- Состояние любого смонтированного элемента может измениться. Родитель этого элемента может быть повторно отрисован. Также смонтированный элемент может получить другой набор props. И здесь начинается магия React и наступает именно тот момент, когда React нам так необходим! Честно говоря, до этого он нам особо и не был нужен.
- История этого компонента не заканчивается, но прежде чем продолжить, нам нужно понять, что же это за состояние, о котором я говорю.
- Ссылки
Другие API классовых компонентов
-
setState() API
setState ()
ставит в очередь изменения в состояние компонента и указывает React, что этот компонент и его дочерние элементы должны быть повторно отрисованы с обновлённым состоянием. Это основной метод, который вы будете использовать для обновления пользовательского интерфейса в ответ на «обработчики событий» и ответы сервера.- Думайте о
setState()
как о запросе, а не как о команде немедленного действия для обновления компонента. Для лучшей очевидной производительности React может задержать выполнение, а затем обновить несколько компонентов за один проход. React не гарантирует незамедлительного применения изменений в состоянии. setState()
не всегда сразу обновляет компонент. Этот метод может группировать или откладывать обновление до следующего раза. Это делает чтениеthis.state
сразу после вызоваsetState()
потенциальной ловушки.- Вместо этого используйте
componentDidUpdate()
- или обратный вызов
setState() (setState (updater, callback))
, любой из которых может быть запущен после того, как обновление было применено. - Если вам нужно обновить состояние на основе предыдущего состояния, прочитайте ниже аргумент
updater
.
setState((state, props) => stateChange, callback)
-
this.setState((state, props) => { return {counter: state.counter + props.step}; }, aomeCallback);
setState()
имеет два параметра:- функция updater
(state, props) => stateChange
. Принимает предыдущий state, props. Возвращает новый объект - необязательный колбэк, вызываемый после завершения работы setState. Далее компонент будет повторно отрисован. Обычно для подобной логики рекомендуют использовать
componentDidUpdate()
.
- функция updater
- Ссылки
-
forceUpdate() API
- Метод классовых компонентов.
- Способ принудительно вызвать re-render компонента.
- По умолчанию компонент будет перерисован если его
state
илиprops
меняются. - Если метод
render()
зависит от каких-то других данных — можно вызватьforceUpdate()
. - Укажет React, что компонент нуждается в повторной отрисовке.
- Вызов forceUpdate() приведёт к выполнению метода render() в компоненте, пропуская shouldComponentUpdate().
- Это вызовет обычные методы жизненного цикла для дочерних компонентов, включая метод каждого дочернего элемента shouldComponentUpdate().
- React по-прежнему будет обновлять DOM только в случае изменения разметки.
- Старайтесь избегать использования
forceUpdate()
и только читать изthis.props
иthis.state
вrender()
. - Есть несколько случаев когда нужно использовать метод
forceUpdate()
, но лучше использовать хуки, props, state и Context для повторного рендеринга компонента. - Есть несколько нишевых случаев, например изменение блокчейна (которое возвращает только хэш транзакции и никаких данных), когда принудительный повторный рендеринг имеет смысл для получения обновленных данных из блокчейна .
forceUpdate
не предназначен для использования в обычных условиях, только в тестировании или других невыясненных случаях.- Можно реализовать на хуках, например с
useState
- Ссылки
Хуки
-
Хуки в программировании
Hooking
(перехват) — технология изменения поведения программы путем перехвата вызовов функций.- Используется повсеместно: в операционных системах, CMS и т.д.
- Позволяет отследить событие Х в системе и вызвать в этот момент свою функцию.
- Система сообщает: сейчас я буду делать то-то, есть желающие в этот момент выполнить свою логику?
- Если какие-то сторонние функции на это событие/хук «подписаны» — они в этот момент будут вызваны.
-
- Т.е. это заложенная в систему возможность:
-
- уведомлять другие программы/процессы о своих событиях
-
- вызывать их функции (в момент наступления данных событий)
-
- как-то обработать и применить то, что эти функции делают.
Hook
(крючок, перехватчик)— код, который обрабатывает эти перехваченные вызовы функций или события.- Т.е. это часть основной программы, которая обрабатывает пришедшую со стороны функцию.
-
Хуки в React
Хуки
в React — функции для работы со state и методами жизненного цикла в функциональных компонентах.- Не работают в классовых компонентах, только в функциональных.
- React содержит около 15 встроенных хуков (см. ниже).
- Можно создавать собственные.
- Хуки удобно повторно использовать в других компонентах.
- Позволяют повторно использовать логику состояния, а не само состояние (state).
- Хуки это функции => между ними можно передавать информацию.
- Какие проблемы решают?
- Хуки позволяют использовать больше возможностей React без написания классов
- Трудно повторно использовать логику состояний между компонентами
- Хуки позволяют вам повторно использовать логику состояния, не затрагивая дерево компонентов
- Сложные компоненты становятся трудными для понимания
- в одном методе жизн. цикла часто приходится хранить много разной логики.
- хуки позволяют разбить один компонент на маленькие функции по их назначению (например, подписке или загрузке данных)
- Классы и ООП сложны для понимания разработчиками — поведение
this
в JS, привязка контекста в обработчиках событий... - Классовые компоненты создают проблемы при внутренней оптимизации React - команда хотела от них избавиться
- классовые компоненты могут приводить к ненамеренным паттернам, сводящим оптимизации на нет. Классы создают сложности для инструментов и сегодня. Например, классы плохо минифицируются, а горячая перезагрузка (hot reloading) ненадёжна и часто ломает их.
- Правила работы с хуками
- Вызывать хуки только на верхнем уровне. Не вызывать внутри циклов, условий или вложенных функций.
- Не вызывать хуки из обычных JS-функций. Только из функциональных компонент или пользовательских хуков.
- Должны находиться в коде строго до любых условий (if, switch)
- Название хука обычно начинается с
use
: useState, useRef, useId, useИмяСамодельногоХука...
- Как работают хуки?
Хуки
в React — функции.- Для управления хуками React использует
связный список
. - Каждый (текущий) хук содержит указатель на следующий хук или null (в свойстве «next»).
- Вот почему важно соблюдать порядок вызова хуков при каждом рендеринге (React полагается на порядок вызова хуков).
- Последовательность хуков, формируемая React при первом рендеринге —
связный спсок
- При повторном рендеринге с меньшим (или большим) количеством хуков,
updateWorkInProgressHook()
возвращает хук, не соответствующий своей позиции в предыдущем списке, т.е. в новом списке будет недоставать узла (или появится дополнительный узел). - И в дальнейшем для вычисления нового состояния будет использовано неправильное мемоизированное состояние.
- Связный список
- базовая динамическая структура данных в информатике, состоящая из узлов, содержащих данные и ссылки («связки») на следующий и/или предыдущий узел списка.
- Принципиальным преимуществом перед массивом является структурная гибкость: порядок элементов связного списка может не совпадать с порядком расположения элементов данных в памяти компьютера, а порядок обхода списка всегда явно задаётся его внутренними связями.
- Ссылки
- Хранение данных о хуках в React
- React запоминает вызовы функций, которые использовали хуки + запоминает вызовы этих хуков + хранит где-то у себя данные, связанные с работой этих хуков (state и т.д.)
- При этом, функция, как и прежде, «умирает» после того как вернула JSX (или нет?).
- В этом отличие от классовых компонент - там объект, созданный классовой компонентой, продолжал жить в памяти, его методы и state находились в нём.
- Изолированное состояние (state)
- Каждое обращение к хуку обеспечивает совершенно изолированный state.
- Один хук можно использовать несколько раз в компоненте.
- В классовом компоненте (т.е. без хуков) я бы создал локальный state, записал в его свойство данные.
- Если мне нужно хранить что-то еще - надо это свойство менять, или добавить новое.
- Например:
- в классовом компоненте несколько счётчиков, Они пишут свои значения в локальный стэйт
- они либо затирают значения друг-друга,
- либо мы вводим под каждый счётчик отдельное значение в стэйте
- Если же я использую хуки - я могу вызывать несколько раз один хук, и он будет по каждый вызов создавать разные локальные стэйты, которые независимо хранят данные для каждого счётчика. Как-то так.
-
Список хуков React (2022)
- React содержит 15 встроенных хуков.
- Основные
- useState — работа с локальным стэйтом компонента.
- useEffect — замена методам жизненного цикла.
- useLayoutEffect — как useEffect, но выполняется до рендера DOM.
- useContext — работа с контекстом (обмен данными между компонентами без прокидывания props)
- useReducer — аналог reducer в Redux. Можно вынести данные из компонента
- useCallback — мемоизация/кэш коллбэков. Предотвращать лишний ре-рендер.
- Мемоизируем коллбэк, экземпляр функции.
- Полезно когда прокидываем колбэки в дочерний компонент.
- useMemo — мемоизация/кэш значений. Предотвращать лишний ре-рендер.
- Мемоизируем значение.
- Замена PureComponent и shouldComponentUpdate.
- useRef — взаимодействие с DOM-объектами (аналог Ref) + хранение данных вместо
useState
(изменение данных не вызвает ре-рендер)
- Дополнительные
- useImperativeHandle — передавать данные от дочернего компонента родительскому (значение, state или функцию)
- Организовать двунаправленный поток данных и логики (обычно в React используется однонаправленный поток)
- useDebugValue — добавлять хукам метки отладки, выводятся в React DevTools.
- Полезно для отладки пользовательских хуков.
- Введён в React 18 (2022)
- useDeferredValue — аналог debouncing / throttling. Позволяет отложить повторный рендеринг несрочной части дерева.
- Например, показать пользователю старые данные, пока подгружаются новые.
- Похож на
debouncing
, но имеет несколько преимуществ по сравнению с ним.- Фиксированной задержки по времени нет, поэтому React попытается выполнить отложенный рендеринг сразу после того, как первый рендер отобразится на экране.
- Отложенный рендеринг может быть прерван и не будет блокировать ввод данных пользователем.
- Введён в React 18 (2022)
- useTransition — помечать некоторые обновления состояния как несрочные
- Другие обновления состояния по умолчанию считаются срочными. React позволит срочным обновлениям состояния (например, обновлению ввода текста) прерывать несрочные обновления состояния (например, отображение списка результатов поиска).
- Введён в React 18 (2022)
- useId — создание уникальных ID как на клиенте, так и на сервере, избегая hydration несоответствий.
- Наиболее полезно для библиотек компонентов, интегрирующихся с API, для которых требуются уникальные ID.
- Особенно актуально в React 18 т.к. новый рендер сервера доставляет HTML не по порядку.
- Введён в React 18 (2022)
- useImperativeHandle — передавать данные от дочернего компонента родительскому (значение, state или функцию)
- Library hooks
- useSyncExternalStore — позволяет внешним хранилищам поддерживать параллельное чтение. Заставляет обновления в хранилище быть синхронными.
- Устраняет необходимость в useEffect при реализации подписок на внешние источники данных
- Рекомендуется для любой библиотеки, которая интегрируется со сторонним состоянием по отношению к React.
- Введён в React 18 (2022)
- useInsertionEffect — позволяет библиотекам CSS-in-JS решать проблемы с производительностью при внедрении стилей во время рендеринга.
- Если вы не планируете создавать библиотеку CSS-in-JS, мы не ожидаем, что вы когда-либо будете это использовать.
- Запустится после изменения DOM, но до того, как эффекты лейаута узнают об этом.
- Особенно актуален в React 18, поскольку React уступает браузеру во время одновременного рендеринга, давая ему возможность пересчитать лейаут.
- Введён в React 18 (2022)
- useSyncExternalStore — позволяет внешним хранилищам поддерживать параллельное чтение. Заставляет обновления в хранилище быть синхронными.
-
useState
-
Общее
- useState — работа с локальным стейтом компонента.
- Задаем состояние компонента + получаем возможность его менять.
- В одном компоненте можно вызвать несколько useState() — получим несколько отдельных state.
- Изменение каждого будет вызывать ререндер компонента.
-
const [state, setState] = useState(initialState);
-
Хук useState()
- Принимает
начальное значение
для state. Используется при первом рендеринге компонента. - Здесь можно использовать массив или объект (если надо), а не только примитив
Ленивая инициализация состояния
- можно передать сюда функцию, котора будет выполняться при первом рендеринге и возвращать начальные значения state. Полезно при сложных вычислениях начального значения.- Возвращает
значение state
ифункцию
для его обновления (метод сеттер, setState(newState)) - Пример использования
-
import React, {useState} from 'react'; function Example() { // Объявление новой переменной состояния «count» const [count, setCount] = useState(0); return ( <div> <p>Вы кликнули {count} раз(а)</p> <button onClick={() => setCount(count + 1)}> Нажми на меня </button> </div> ); }
- Вызов useState вернёт пару значений: текущее состояние и функцию, обновляющую состояние.
- Поэтому мы пишем const [count, setCount] = useState().
- Это похоже на this.state.count и this.setState в классах, с той лишь разницей, что сейчас мы принимаем их сразу в паре.
- В классовой компоненте это выглядело бы так:
-
class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <p>Вы кликнули {this.state.count} раз(а)</p> <button onClick={() => this.setState({count: this.state.count + 1})}> Нажми на меня </button> </div> ); } }
- Чтоб прочитать state из хука мы используем переменную, которую ранее объявили
-
<p>Вы кликнули {count} раз(а)</p>
- В классовой компоненте это было бы так:
-
<p>Вы кликнули {this.state.count} раз(а)</p>
-
- Чтобы обновить state:
-
<button onClick={() => setCount(count + 1)}> Нажми на меня </button>
- В классовой компоненте это было бы так:
-
<button onClick={() => this.setState({count: this.state.count + 1})}> Нажми на меня </button>
-
-
- В отличие от this.setState в классах, обновление переменной состояния всегда замещает её значение, а не осуществляет слияние.
-
- Принимает
-
Функция setState(newState)
- Принимает
- значение стейта —
setCount(newCount)
- или функцию для вычисления значения на основе предыдущего state —
setCount(prevCount => prevCount - 1)
- значение стейта —
- Возвращает — новое значение
state
- Ререндер компонента
- Если метод setState() обновил значение state — происходит ререндер компонента.
- Если метод setState() вернул тот же результат, что и текущий state — повторного рендеринга не будет.
Досрочное прекращение обновления состояния
— если обновить state хука тем же значением, что и текущее состояние, React досрочно выйдет из хука без повторного рендера дочерних элементов и запуска эффектов.
- Перезапись объекта
state
- Если в state хранится объект —
useState
не объединяет объекты, а заменяет старый объект новым.- Получаем новый объект
state
, который заменит предыдущий. - Если хотим изменить в объекте только часть, надо делать так:
-
const [user, setUser] = useState({ name: "ivan", age: 23, }); const onNameChange = (e) => { setUser({ ...user, // Копируем оригинальный обэект state name: e.target.value }); };
- В отличии от метода
setState
классовых компонент — там будет объединени объектов
- Получаем новый объект
- Если в state хранится объект —
Асинхронность setState()
- setState() — асинхронная функция. Под капотом Реакт объединяет все мутации состояний — код компонента будет вызван 1 раз (это называется «batching») . Если новое состояние опирается на предыдущее состояние, используйте функцию.
- Неправильно:
-
const onClick0 = () => { setCount(count + 1); setCount(count + 1); setCount(count + 1); }; //При onClick0() счётчик увеличится только на 1 // Например count = 0 const onClick0 = () => { // count + 1 = 0 + 1; setCount(count + 1); // Здесь можем ожидать, что count уже 1, // но т.к. вызов setState асинхронный состояние еще не изменено, // поэтому count по-прежнему 0 // count + 1 = 0 + 1; setCount(count + 1); // count + 1 = 0 + 1; setCount(count + 1); };
- Правильно:
-
//Если новое состояние опирается на предыдущее состояние, используйте функцию const onClick1 = () => { setCount(v => v + 1); setCount(v => v + 1); setCount(v => v + 1); };
Пакетное обновление состояния
(batching)- React может сгруппировать несколько setState в один повторный рендеринг
- Повышает производительность.
- До React 18 пакеты обновлялись только внутри обработчиков событий React.
- Начиная с React 18, пакетная обработка включена для всех обновлений по умолчанию.
- Обновления из N разных событий, инициированных пользователем, всегда обрабатываются отдельно и не объединяются (например двойное нажатие кнопки). Это предотвращает логические ошибки.
- Идентичность функции setState стабильна и не изменяется при повторных рендерах => её можно безопасно не включать в списки зависимостей хуков useEffect и useCallback.
- Принимает
-
-
useEffect и useLayoutEffect
-
Общее
- useEffect — замена методам жизненного цикла.
- Позволяет совершать какие-то действия на разных этапах жизни компонента. Side-эффекты делать здесь.
-
//Пример 1 - базовый useEffect(didUpdate); //Пример 2 - с подпиской на функцию из props, удалением подписки при размонтировании компонента и зависимостью от props.source useEffect(() => { const subscription = props.source.subscribe(); return () => { subscription.unsubscribe(); }; }, [props.source]); //подписка будет создана повторно только при изменении props.source.
-
useEffect и useLayoutEffect
- useEffect — запускается после окончания рендера.
- useLayoutEffect — запускается до рендреа. Только когда он закончится - начинается рендер.
-
Хук useEffect()
- Принимает
функцию-callback
(эффект),- Функция будет запущена по завершении рендера (или перед ним — для
useLayoutEffect
). Если не указаны зависимости (см. ниже). - внутри функции происходит работа с обновленными данными.
- запросы на сервер, таймеры, мутации, подписки, логирование, задание обработчиков событий на документ и другие side-эффекты
return
в функции — вернёт «функцию очистки» (cleanup)- Функция запускается до удаления компонента из пользовательского интерфейса (
componentWillUnmount
). - Функция запускается также до обновления любого значения из массива зависимостей (можно было бы назвать
componentWillUpdate
). - Часто эффекты создают ресурсы, которые необходимо очистить/сбросить перед демонтированием компонента. Предотвращает утечки памяти. Например подписку или идентификатор таймера.
- Если компонент рендерится несколько раз (как обычно происходит), предыдущий эффект очищается перед выполнением следующего эффекта. В нашем примере это означает, что новая подписка создаётся при каждом обновлении. Чтобы избежать воздействия на каждое обновление — см. «Порядок срабатывания эффектов. useLayoutEffect».
-
useEffect(() => { const subscription = props.source.subscribe(); return () => { subscription.unsubscribe(); // Очистить подписку }; });
- Функция запускается до удаления компонента из пользовательского интерфейса (
- Функция будет запущена по завершении рендера (или перед ним — для
- массив
зависисмостей
- массив значений, при изменении которых должен срабатывать useEffect()
- обычно useEffect запускается после каждого завершённого рендеринга, но если указать зависимости — будет запускаться только при их изменении (ну и при первом монтированиии компонента).
- Благодаря этому аргументу мы можем имитировать методы жизненного цикла
componentDidMount
иcomponentDidUpdate
- Если указываешь зависимости — указывай все!
- Массив зависимостей должен включать все значения, которые используются в useEffect и могут меняться (например данные из props и state). Иначе код будет использовать устаревшие значения из предыдущих рендеров.
- пустой массив
[]
- useEffect() запустится только один раз при монтировании и сбросится только один раз при размонтировании
=
componentDidMount
иcomponentWillUnmount
- React посчитает, что ваш эффект не зависит от каких-либо значений из пропсов или состояния и поэтому не будет выполнять повторных запусков эффекта.
- Если вы передадите пустой массив ([]), пропсы и состояние внутри эффекта всегда будут иметь значения, присвоенные им изначально.
- Передача [] похожа на
componentDidMount
иcomponentWillUnmount
. Но обычно есть лучшие способы избежать лишних ре-рендеров. React не запускает useEffect(), пока браузер не отрисует все изменения => обычно есть время и рессурсы для выполнения дополнительной работы.
- useEffect() запустится только один раз при монтировании и сбросится только один раз при размонтировании
=
Линтер
. Рекомендуется использовать правилоexhaustive-deps
, входящее в пакет правил линтераeslint-plugin-react-hooks
. Оно предупреждает, когда зависимости указаны неправильно и предлагает исправление.
- Принимает
-
Замена методов жизненного цикла
- по умолчанию
- функция effect запускается после каждого рендера (до рендера для
useLayoutEffect
)
- функция effect запускается после каждого рендера (до рендера для
componentDidMount
- если передать пустой массив зависимостей
- функция effect вызовется только при монтировании компонента
componentDidUpdate
- если передать не пустой массив зависимостей
- функция effect вызовется при изменении любого значения из массива зависимостей.
- Это лучше классического componentDidUpdate — более гибкое управление
- Если нужно сделать аналогию componentDidUpdate, в качестве зависимостей можно указать все параметры и состояния. Но useEffect запустится ещё и на стадии монтирования.
componentWillUnmount
- если в функции effect есть
return
— функция внутри return выполнится перед размонтированием компонента (и перед обновлением любой зависимости) - или если передать пустой массив зависимостей (?!) - функция effect вызовется только при монтировании и размонтировании компонента
- если в функции effect есть
componentWillUpdate
—- добавляет «новый метод».
- если в функции effect есть
return
— функция внутри return выполнится перед обновлением любой зависимости из массива - Этот хук асинхронный, будет вызван после рендеринга (осмыслить)
- по умолчанию
-
Порядок срабатывания эффектов. useLayoutEffect
- Функция, переданная в
useEffect
, запускается после рендера компонента. - Это делает хук подходящим для многих распространённых побочных эффектов, таких как настройка подписок и обработчиков событий, потому что большинство типов работы не должны блокировать обновление экрана браузером.
- Отличает
useEffect
отcomponentDidMount
иcomponentDidUpdate
, - Если надо выполнить до рендера (например, изменение DOM) — для этих типов эффектов используй хук
useLayoutEffect
. useLayoutEffect
предотвращает лишнее обновление компонента.- Используйте
useLayoutEffect
для чтения макета из DOM и синхронного повторного рендеринга. - Обновления, запланированные внутри
useLayoutEffect
, будут полностью применены синхронно перед тем, как браузер получит шанс осуществить отрисовку. - Предпочитайте стандартный
useEffect
, когда это возможно, чтобы избежать блокировки визуальных обновлений.
- Функция, переданная в
-
Когда использовать?
- Для "подписки" на изменения какой-либо переменной, например, состояния или пропса.
- Обработка событий «не react» компонентов.
- Например, установить обработчик mousemove на window, это может быть полезно, когда мы создаем кастомный инпут диапозона или элемент перемещаемый по экрану.
- Для этого нужно при монтировании компонента добавить обработчик, а при размонтировании снять, чтобы если компонент будет смонтирован снова не происходила двойная обработка.
-
Когда использовать useEffect без массива зависимостей?
- Хук useEffect без массива зависимостей будет вызван каждый раз при обновлении компонента.
- Например, можно использовать, при изменении заголовка документа. Эта операция дешевле, чем проверка массива зависимостей, поэтому можно его не указывать.
- Если компонент не принимает пропсы и его состояние не заставит компонент часто обновляться, также можно использовать useEffect с пустым массивом зависимостей.
-
Типизация
-
type DependencyList = ReadonlyArray<any>; type EffectCallback = () => (void | (() => void | undefined)); function useEffect(effect: EffectCallback, deps?: DependencyList): void;
-
-
-
useContext
- useContext — работа с контекстом (обмен данными между компонентами без прокидывания props)
- Ссылки
-
useCallback
-
useCallback — мемоизация/кэширование коллбэка (т.е. функции).
-
Возвращает мемоизированнный коллбэк (экземпляр функции).
-
Создаёт новый экземпляр функции только если изменилась зависимость.
-
Получает два параметра: функцию и массив зависимостей.
-
Полезно когда надо прокидывать колбэки в дочерний компонент — позволяет избежать лишнего ре-рендера.
-
Возвращает один и тот же экземпляр передаваемой функции (коллбэк) вместо создания нового при каждом повторном рендеринге компонента. Новый экземпляр коллбэка будет создан только при изменении массива зависимостей.
-
Особенность работы с функциями в React: когда мы используем их в качестве колбеков в компонентах, ссылка на функцию меняется.
-
Так происходит потому, что каждый раз создается новая функция, и React вызывает перерисовку компонента.
-
Т.е. когда компонент повторно визуализируется, он создает новые экземпляры всех объектов, включая все функции в нем.
-
Чтобы избежать такой проблемы, можно использовать
useCallback()
, который возвращает всегда одну и ту же ссылку на переданную функцию. -
Отличия от useMemo
useCallback(fn, deps)
— это эквивалентuseMemo(() => fn, deps)
.
-
Когда использовать?
- Если вы подумываете о добавлении
useCallback
иuseMemo
в ваш компонент, не торопитесь. - Они добавляют некоторую дополнительную сложность коду и ухудшают читабельность.
- Менеджмент производительности с помощью
useCallback
иuseMemo
обходится дорого и это не всегда оправдано. - Рассмотрите возможность использования useCallback/useMemo в следующих ситуациях:
- обработка больших объемов данных;
- работа с интерактивными графиками и диаграммами;
- реализация анимации;
- включение компонента в ленивую загрузку.
- Если вы подумываете о добавлении
-
Ссылки
- Оф. докуиентация - useCallback
- Hexklet - Хуки useCallback и useMemo
- Habr - React hooks, как не выстрелить себе в ноги. Часть 3.1: мемоизация, memo
- Habr - React hooks, как не выстрелить себе в ноги. Часть 3.2: useMemo, useCallback
- Демистификация хуков React: useCallback, useMemo и все-все-все
- When to useMemo and useCallback
- Dan Abramov - Before You memo()
-
-
useMemo
- useMemo — мемоизация/кэширование значений, результатов работы функции.
- Возвращает мемоизированное значение.
- Запоминает значение, которое возвращает функция.
- Вычисляет значение заново только если изменилась зависимость — позволяет избежать дорогостоящих вычислений при каждом рендере.
- Получает два параметра: функцию и массив зависимостей.
- Если массив зависимостей не был передан, новое значение будет вычисляться при каждом рендере.
- Позволяет предотвращать лишние повторные рендеры.
- Замена
PureComponent
иshouldComponentUpdate
. - Также смотри HOC
React.memo
- Принимает функцию, которая возвращает какой-то результат.
- Хук запоминает этот результат и возвращает его каждый раз, не вызывая повторно переданную функцию.
- Используется вместо того, чтобы возвращать невызванную функцию, как это делает
useCallback
– он работает с передаваемой функцией и возвращает результирующее значение только при изменении массива параметров. - Т.е. useMemo вызывает функцию только при необходимости и возвращает кэшированное значение для других визуализаций.
- Особенность работы с функциями в React: когда мы используем их в качестве колбеков в компонентах, ссылка на функцию меняется.
- Так происходит потому, что каждый раз создается новая функция, и React вызывает перерисовку компонента.
- Т.е. когда компонент повторно визуализируется, он создает новые экземпляры всех объектов, включая все функции в нем.
- Чтобы при каждом ре-рендере не проводить ресурсоёмкие вычисления заново — можно мемоизировать результаты вычислений функции, использую хук
useMemo()
. - Функция, переданная
useMemo
, запускается во время рендеринга. - Не делайте там ничего, что вы обычно не делаете во время рендеринга.
- Например, побочные эффекты принадлежат
useEffect
, а неuseMemo
. - Отличия от useCallback
useCallback(fn, deps)
— это эквивалентuseMemo(() => fn, deps)
.
- Когда использовать?
- Если вы подумываете о добавлении
useCallback
иuseMemo
в ваш компонент, не торопитесь. - Они добавляют некоторую дополнительную сложность коду и ухудшают читабельность.
- Менеджмент производительности с помощью
useCallback
иuseMemo
обходится дорого и это не всегда оправдано. - Рассмотрите возможность использования useCallback/useMemo в следующих ситуациях:
- обработка больших объемов данных;
- работа с интерактивными графиками и диаграммами;
- реализация анимации;
- включение компонента в ленивую загрузку.
- Если вы подумываете о добавлении
- Ссылки
- Оф. докуиентация - useMemo
- Hexklet - Хуки useCallback и useMemo
- Habr - React Hooks простыми словами
- Учим useMemo на примерах — React Hooks
- Habr - React hooks, как не выстрелить себе в ноги. Часть 3.1: мемоизация, memo
- Habr - React hooks, как не выстрелить себе в ноги. Часть 3.2: useMemo, useCallback
- Демистификация хуков React: useCallback, useMemo и все-все-все
- When to useMemo and useCallback
- Dan Abramov - Before You memo()
-
useRef
- useRef — позволяет хранить объект, который можно изменять.
- Объект хранится в течение всей жизни компонента.
- Изменение значения этого объекта не вызовет ререндер
- Хук
useRef
часто используется взаимодействие с DOM-объектами, аналог Ref. - принимает начальное значение хранимого объекта.
- это значение запишется в свойство
.current
при инициализации
- это значение запишется в свойство
- возвращает
- изменяемый ref-объект
- иначе говоря - ссылка-объект, из свойства current которого можно получить хранимое значение
- Зачем используется
- работа с DOM-объектами, аналог React.createRef() в классовых компонентах.
- вместо
useState
- может хранить любой объект
- изменение значения не вызовет ре-рендер (в отличие от setState())
- для сохранения любого мутируемого значения, которое надо сохранить между ренедерами компонента
- Где обновлять значение useRef()?
- Обновление значения ref считается побочным эффектом. Именно по этой причине необходимо обновить значение ref в обработчиках событий и эффектах, а не во время визуализации (если только вы не работаете с ленивой инициализацией). React docs предупреждает нас, что несоблюдение этого правила может привести к неожиданному поведению приложения.
- Использовать ли refs вместо state?
- Нет. Refs – нереактивный, а значит, изменения значений не приведет к обновлению HTML.
- useRef() аналог createRef?
- Нет.
createRef()
полезен для доступа к узлам DOM или элементам React, но он создает новый экземплярref
на каждом рендере вместо того, чтобы сохранять значение между визуализациями при использовании в функциональных компонентах.useRef()
полезен для доступа к узлам DOM или элементам React. Он сохраняет значение даже при повторной визуализации компонента.
- Пример
-
const App = () => { const ref = useRef(); //1. Создали объект ref useEffect(() => { console.log(ref.current); //3. Взаимодействуем в DOM-элементом }, []); return <div ref={ref} />; //2. привязали DOM-элемент };
-
- создаём объект ref
- указываем его в качестве элемента, обозначающего DOM-объект, к которому мы хотим обратиться, а также прописываем этот объект в качестве параметра.
- теперь мы можем взаимодействовать с Dom-объектом напрямую, как если бы мы нашли его с помощью селектора. Для этого используем свойство current у объекта ref.
- Ссылки
- Habr - React hooks, как не выстрелить себе в ноги. Часть 4
- Habr - React hooks, как не выстрелить себе в ноги. Разбираемся с замыканиями. Совместное использование хуков
- Habr - React Hooks простыми словами
- Mentanit - useRef
- Оф. документация - Хук useRef
- Hexlet - Хуки
- Умный способ использования хука useRef() в React
- Демистификация хуков React: useCallback, useMemo и все-все-все
-
useImperativeHandle
- useImperativeHandle — передавать данные от дочернего компонента родительскому (значение, state или функцию)
- Организовать двунаправленный поток данных и логики (обычно в React используется однонаправленный поток)
- Ссылки
-
useDebugValue
- useDebugValue — позволяет добавлять хукам метки отладки, которые выводятся в React DevTools.
- Полезно для отладки пользовательских хуков.
- Введён в React 18 (2022)
- Ссылки
-
useDeferredValue
- useDeferredValue — аналог debouncing / throttling. Позволяет отложить повторный рендеринг несрочной части дерева.
- Например, показать пользователю старые данные, пока подгружаются новые.
- Похож на
debouncing
, но имеет несколько преимуществ по сравнению с ним. - Фиксированной задержки по времени нет, поэтому React попытается выполнить отложенный рендеринг сразу после того, как первый рендер отобразится на экране.
- Отложенный рендеринг может быть прерван и не будет блокировать ввод данных пользователем.
- Введён в React 18 (2022)
- Ссылки
-
useTransition
- useTransition — помечать некоторые обновления состояния как несрочные
- Другие обновления состояния по умолчанию считаются срочными. React позволит срочным обновлениям состояния ( например, обновлению ввода текста) прерывать несрочные обновления состояния (например, отображение списка результатов поиска).
- Введён в React 18 (2022)
- Ссылки
-
useId
-
useId — создание уникальных ID как на клиенте, так и на
-
сервере, избегая hydration несоответствий.
-
Наиболее полезно для библиотек компонентов, интегрирующихся с API, для которых требуются уникальные ID.
-
Особенно актуально в React 18 т.к. новый рендер сервера доставляет HTML не по порядку.
-
Введён в React 18 (2022)
-
Ссылки
-
-
useSyncExternalStore
-
useSyncExternalStore — позволяет внешним
-
хранилищам поддерживать параллельное чтение. Заставляет обновления в хранилище быть синхронными.
-
Устраняет необходимость в useEffect при реализации подписок на внешние источники данных
-
Рекомендуется для любой библиотеки, которая интегрируется со сторонним состоянием по отношению к React.
-
Введён в React 18 (2022)
-
Ссылки
-
-
useInsertionEffect
-
useInsertionEffect — позволяет библиотекам
-
CSS-in-JS решать проблемы с производительностью при внедрении стилей во время рендеринга.
-
Если вы не планируете создавать библиотеку CSS-in-JS, мы не ожидаем, что вы когда-либо будете это использовать.
-
Запустится после изменения DOM, но до того, как эффекты лейаута узнают об этом.
-
Особенно актуален в React 18, поскольку React уступает браузеру во время одновременного рендеринга, давая ему возможность пересчитать лейаут.
-
Введён в React 18 (2022)
-
Ссылки
-
-
Пользовательские хуки
- Функция, имя которой начинается с «use», и которая может вызывать другие хуки.
- Ссылки
-
Ссылки
- Habr - React hooks, как не выстрелить себе в ноги. Разбираемся с замыканиями. Совместное использование хуков
- Habr - React Hooks простыми словами
- Matanit.com - Хуки. Управление функциональными компонентами
- Оф. документация
- Оф. документация - список хуков
- Дэн Абрамов - Полное руководство по useEffect (!)
- IT-Kamasutra #84 - hook, useState, хуки
- WebDev - React видеокаст #2. Полное введение в хуки
- WebDev - React видеокаст #3. Релиз хуков
- Демистификация хуков React: useCallback, useMemo и все-все-все
React.memo HOC
-
Общее
- HOC для пропуска повторных рендеров.
- Использует мемоизацию (кэширование).
- Принимает react компонент, а возвращает react компонент, который будет обновляться только если его props изменились.
- Если обернуть компонента в
React.memo
— React будет использовать последнюю отрисованную версию этого компонента (если props не изменились). HOC
(high order component, компонент высшего порядка) — функция, которая принимает компонент и возвращает его улучшенную версию.Мемоизация
— хранение результатов дорогостоящих вызовов функций и возврата кэшированного результата вычислений при повторном использовании тех же входных данных.- React.memo затрагивает только изменения пропсов.
- Если функциональный компонент обёрнут в React.memo и использует useState, useReducer или useContext, он будет повторно рендериться при изменении состояния или контекста.
- В идеале мемоизированные компоненты должны быть чистыми.
- Рекомендуется добавлять в такой компонент только те свойства, которые редко меняются.
- Этот метод предназначен только для оптимизации производительности.
- Не полагайтесь на него, чтобы «предотвратить» рендер, так как это может привести к ошибкам.
- Memo принимает 2 аргумента: компонент и функцию propsAreEqual (пропсы равны?).
- По умолчанию он поверхностно сравнивает вложенные объекты в объекте props. Если вы хотите контролировать сравнение, вы можете передать свою функцию сравнения в качестве второго аргумента.
-
memo vs shouldComponentUpdate
memo
часто сравнивают сshouldComponentUpdate
.- Оба предотвращают лишнее обновление компонентов.
- Чтобы предотвратить обновление
shouldComponentUpdate
возвращаетtrue
, аmemo
—false
shouldComponentUpdate
- "должен ли компонент обновиться?", если скажем да (вернемtrue
) - обновится.propsAreEqual
- "пропсы равны?", если скажем да (вернем true) - не обновится, пропсы ведь равны.
- Правда propsAreEqual это утверждение, а не вопрос и я бы назвал: arePropsEqual, но суть не меняется.
shouldComponentUpdate
— метод жизненного цикла классовых компонент.- вызывается каждый раз при обновлении объекта props или state
- Вызывается при изменении родителей, и при смене setState в самом компоненте.
- позволяет оптимизировать приложение в ручном режиме, управляя тем, нужно ли перестраивать виртуальный DOM для этого компонента или нет?
- т.е. позволяет оптимизировать перерисовку виртуального DOM - если в это компоненте ничего не поменялось, не перерисовываем
-
Когда «memo» не имеет смыла
- Если компонент принимает
children
, вероятно не имеет смысла его мемоизировать. - «Вероятно» — потому что есть способ сохранить мемоизацию: можно дочерние компоненты мемоизировать с помощью useMemo.
- Это не самое чистое и довольно хрупкое решение, тем не менее это возможно.
- Если вы передаете в компонент children, стоит задуматься, а действительно ли этот компонент выполняет сложную работу и его нужно мемоизировать. Также стоит учесть, как часто будет обновляться родительский компонент, если не часто - можно отказаться от мемоизации.
- Принимает компонент или нет другие компоненты в качестве children - ключевой вопрос, который подскажет нужно мемоизировать компонент или нет. Если children компонента - другие компоненты, вероятно мемоизация не имеет смысла. Также мемоизация не имеет смысла, когда имеем дело с каким нибудь простым компонентом, например стилизованной кнопкой.
- Если компонент принимает
-
Примеры
-
const Component = React.memo(function Component(props) { return ( <p>Text</p> ) });
-
import React, { useState, FC, memo } from "react"; export const MemoChild = memo(() => { return ( <div>Я никогда не буду обновляться</div> ); }); export const Child: FC = () => { return ( <div> Я буду обновляться всегда, когда обновляется родитель </div> ); }; export const Parent: FC = () => { const [state, setState] = useState<boolean>(true); return ( <div> <Child /> <MemoChild /> <button onClick={() => setState(v => !v)}>click</button> </div> ); };
-
-
TypeScript
-
function memo<P extends object>( Component: SFC<P>, propsAreEqual?: (prevProps: Readonly<PropsWithChildren<P>>, nextProps: Readonly<PropsWithChildren<P>>) => boolean ): NamedExoticComponent<P>;
-
Context API
-
Что такое контекст и зачем он нужен?
- Объект, который создаётся у родителя и доступен всем детям.
- Контекст предоставляет способ делиться данными между компонентами без необходимости явно передавать пропсы через каждый уровень дерева.
- Может содержать некоторые очень глобальные данные - активный язык приложения (ru/en), активную тему оформления (ночь/день), store... Т.е. что-то, что редко меняется
- Что попало туда пихать не надо, только очень глобальные вещи.
- Контекст нужен чтобы не пробрасывать некоторые данные по длинной цепочке только для того, чтоб они пришли в компоненту нижнего уровня - эти данные помещаются в контекст а компонента нижнего уровня берёт их сразу оттуда, не из props. Обычно контекст используется, если необходимо обеспечить доступ данных во многих компонентах на разных уровнях вложенности. По возможности не используйте его, так как это усложняет переиспользование компонентов.
- Обычно контекст используется, если необходимо обеспечить доступ данных во многих компонентах на разных уровнях вложенности. По возможности не используйте его, так как это усложняет повторное использование компонентов.
- Раньше был старый синтаксис Context, его использовать не надо
-
Context API: createContext, Provider, Consumer
- Функция React.createContext, которая создает объект Context
- Provider (возвращается createContext) - устанавливает «шину» для прямой передачи данных, проходящую через дерево компонентов
- Consumer (возвращается createContext) - впитывается в «шину» для извлечения данных
-
Provider
- Provider очень похож на Provider в React-Redux. Он принимает значение, которое может быть всем, чем хотите (это может быть даже store Redux… но это было бы глупо). Скорее всего, это объект, содержащий ваши данные и любые actions, которые вы хотите выполнить с данными.
-
Consumer
- Consumer работает немного похоже как функция connect в React-Redux, подключаясь к данным, и сделав их доступными для компонента, который их использует.
Error Boundaries
-
Общее
- Классовые компоненты, которые
- отлавливают ошибки JS в любом месте деревьев их дочерних компонентов (при помощи спец. методов жизн. цикла)
- сохраняют их в журнале ошибок
- выводят запасной UI вместо рухнувшего дерева компонентов.
- Отлавливают ошибки при рендеринге, в методах жизненного цикла и конструкторах деревьев компонентов, расположенных «под ними».
- Аналог
catch
в js-конструкцииtry ... catch
, но только для компонентов - Классовый компонент является «предохранителем», если включает хотя бы один из следующих методов жизненного цикла:
static getDerivedStateFromError()
— для рендеринга запасного UI в случае отлова ошибки.componentDidCatch()
— для журналирования информации об отловленной ошибке.
- Классовые компоненты, которые
-
Предохранители не поймают ошибки в:
- обработчиках событий (подробнее);
- асинхронном коде (например колбэках из setTimeout или requestAnimationFrame);
- серверном рендеринге (Server-side rendering);
- самом предохранителе (а не в его дочерних компонентах).
-
Не «ловят» ошибки внутри себя
- Предохранители отлавливают ошибки исключительно в своих дочерних компонентах.
- Предохранитель не сможет отловить ошибку внутри самого себя. Если предохранителю не удаётся отрендерить сообщение об ошибке, то ошибка всплывает до ближайшего предохранителя, расположенного над ним в дереве компонентов. Этот аспект их поведения тоже напоминает работу блоков catch {} в JavaScript.
-
Где размещать предохранители
- Степень охвата кода предохранителями остаётся на ваше усмотрение.
- Например, вы можете защитить им навигационные (route) компоненты верхнего уровня, чтобы выводить пользователю сообщение «Что-то пошло не так», как это часто делают при обработке ошибок серверные фреймворки.
- Или вы можете охватить индивидуальными предохранителями отдельные виджеты, чтобы помешать им уронить всё приложение.
-
Как насчёт try/catch?
try / catch
— отличная конструкция, но она работает исключительно в императивном коде:- Компоненты React являются декларативными, указывая что должно быть отрендерено:
<Button />
- Предохранители сохраняют декларативную природу React и ведут себя так, как вы уже привыкли ожидать от компонентов React. Например, если ошибка, произошедшая в методе componentDidUpdate, будет вызвана setState где-то в глубине дерева компонентов, она всё равно корректно всплывёт к ближайшему предохранителю.
-
А что насчёт обработчиков событий?
- Предохранители не отлавливают ошибки, произошедшие в обработчиках событий.
- React не нуждается в предохранителях, чтобы корректно обработать ошибки в обработчиках событий. В отличие от метода render и методов жизненного цикла, обработчики событий не выполняются во время рендеринга. Таким образом, даже если они сгенерируют ошибку, React всё равно знает, что нужно выводить на экран.
- Чтобы отловить ошибку в обработчике событий, пользуйтесь обычной JavaScript-конструкцией try / catch:
-
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { error: null }; this.handleClick = this.handleClick.bind(this); } handleClick() { try { // Делаем что-то, что сгенерирует ошибку } catch (error) { this.setState({ error }); } } render() { if (this.state.error) { return <h1>Отловил ошибку.</h1> } return <button onClick={this.handleClick}>Нажми на меня</button> } }
-
Пример
-
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) {// Обновить состояние с тем, чтобы следующий рендер показал запасной UI. return { hasError: true }; } componentDidCatch(error, errorInfo) {// Можно также сохранить информацию об ошибке в соответствующую службу журнала ошибок logErrorToMyService(error, errorInfo); } render() { if (this.state.hasError) { // Можно отрендерить запасной UI произвольного вида return <h1>Что-то пошло не так.</h1>; } return this.props.children; } } //И можно дальше им пользоваться, как обыкновенным компонентом: <ErrorBoundary> <MyWidget /> </ErrorBoundary>
-
-
Заметки
- Не могут быть адекватно заменены функциональными компонентами (на осень 2022).
- Подвижки на эту тему: GitHub discussion
- На практике чаще всего целесообразным будет один раз описать предохранитель и дальше использовать его по всему приложению.
- Начиная с React 16, ошибки, не отловленные ни одним из предохранителей, будут приводить к размонтированию всего дерева компонентов React.
- Не могут быть адекватно заменены функциональными компонентами (на осень 2022).
События (events) *
- При обработке событий важно понимать, что все атрибуты элементов React именуются с помощью camelCase. Т.е.
onClick
а неonclick
. - При работе с функциями, мы передаем фактическую ссылку на функцию, а не строку.
- React.js создает для DOM-события обертку в виде собственного объекта, чтобы оптимизировать производительность работы с событиями. Внутри обработчика все так же возможно получить доступ ко всем методам, доступным для документа.
- При использовании React вам обычно не нужно вызывать addEventListener, чтобы добавить обработчиков событий в элемент DOM после его создания. Вместо этого просто предоставьте обработчик, когда элемент изначально отрисовывается.
- Ссылки
JSX
-
Что такое JSX?
- JavaScript XML (JSX) - расширение синтаксиса JavaScript, "синтаксический сахар" для JS.
- Позволяет использовать похожий на HTML синтаксис для описания структуры интерфейса.
- https://learn.javascript.ru/screencast/react#03-jsx
- При помощи Babel он компилируется в обычный JS. В JSX пишется и html-содержимое компонентов.
- Расширение .jsx использовать только для компонент. Не использовать для редьюсеров и т.д.
- JSX - синтаксическое расширение JavaScript.
- JSX производит React-элементы.
- Можно работать с React на обычном JS, без JSX.
- Babel компилирует JSX в вызовы React.createElement().
-
Зачем нужен JSX?
- React учитывает тот факт, что логика отрисовки связана с другой логикой пользовательского интерфейса: как обрабатываются события, как изменяется состояние со временем и как данные подготавливаются для отображения.
- Вместо того чтобы искусственно отделять технологии, помещая разметку и логику в отдельные файлы, React разделяет задачи , используя слабо связанные единицы, называемыми «компонентами», которые содержат и разметку, и логику.
-
В JSX нельзя вывести два html-элемента рядом
-
Так нельзя
-
function Test() { return ( <h1>Title</h1> <p>Text</p> <div></div> ) }
-
Надо так:
-
function Test() { return ( <div> <h1>Title</h1> <p>Text</p> </div> ) }
-
Или так:
-
function Test() { return [ <h1 key = 'a'>Title</h1>, <p key = 'b'>Text</p> ] }
-
Чтоб создать в JSX пустую корневую компоненту можно сделать так:
-
return <> <ComponentOne /> <ComponentTwo /> </>
-
Иначе - только через массив с уникальными ключами
-
-
В JSX обязательно надо закрывать открытый тэг
- Но, можно использовать такой синтаксис
<Article />
- Но, можно использовать такой синтаксис
-
В JSX есть соглашение - все кастомные (т.е. мной созданные) компоненты называются с большой буквы .
- Пример: Aricle, MyComponent...
- Т.к. компонент = класс.
- И при выводе их внутри других компонентов - тоже (
<Aricle />
,<MyComponent />
, ...)
-
В JXS, если надо написать кусок на обычном JS, я помещаю его в фигурные скобки.
- Например, создаю и вывожу переменную
-
function Test() { const text = <p>Text</p> return ( <div> <h1>Title</h1> {text} </div> ) }
- Лучше не злоупотреблять выводом внутри JSX фигурных скобок с JS - тяжело разбираться.
- Если нужны большие объёмы - выноси в переменные (см выше)
-
В JXS аттрибуты html пишут так:
-
function Test() { return ( <div className="test" style={{color: 'red'}}> <h1>Title</h1> </div> ); }
-
-
Внутри JSX можно использовать только выражения
- Внутри JSX можно использовать только выражения. Так, например, вы не можете использовать оператор if, но можете использовать тернарное выражение.
- Переменные JavaScript также являются выражениями
- Объекты JavaScript также являются выражениями.
- Вы можете использовать элемент React внутри JSX, потому что это тоже выражение
- Вы также можете использовать все функциональные методы JavaScript для коллекций (map, reduce, filter, concat и т. д.) внутри JSX. Опять же, потому что они возвращают выражения
-
JSX предотвращает атаки, основанные на инъекции кода.
- https://ru.reactjs.org/docs/introducing-jsx.html
- Данные, введённые пользователем, можно безопасно использовать в JSX.
- По умолчанию React DOM экранирует все значения, включённые в JSX перед тем как отрендерить их.
- Это гарантирует, что вы никогда не внедрите чего-либо, что не было явно написано в вашем приложении.
- Всё преобразуется в строчки, перед тем как быть отрендеренным.
- Это помогает предотвращать атаки межсайтовым скриптингом (XSS).
-
Все атрибуты элементов React именуются с помощью camelCase.
- CSS-class записываем как className
- tabindex = tabIndex
-
Внутри JSX разметки можно использовать только готовые выражения.
- Нельзя, например, использовать конструкцию if/else (точно?)
- Но можно заменить ее тернарным оператором.
Рендер. Гидратация. Изоморфность.
-
Рендер, отрисовка
Рендер
— отрисовка кода веб-документа в интерактивную веб-страницу, которую пользователь видит в браузере.- Процесс выполнения всех правил, прописанных в HTML-коде, JS-скриптах и CSS-стилях.
- Одна из самых долгих и «дорогих» (по времени) операций в работе сайта. Чем она быстрее — тем лучше.
- Задержка в отрисовке страницы на 1 сек, снижает конверсию на 7%.
- Т.е. чем дольше грузится страница тем больше людей уйдёт не дождавшись.
-
3 основных подхода
Рендеринг на клиенте
(Client Side Render, CSR)- браузер скачивает почти пустой HTML, потом выкачивает большой JS (возможно разбыиты на «бандлы»), только потом страница отображается.
- Пока качается JS - отображается только малая часть страницы или прелоадер. Информации на странице почти нет и взаимодействовать с ней нельзя.
- YouTube — Animated Client Side Rendering (Fruits)
Рендеринг на сервере
(Server Side Render, SSR)- весь HTML готовится на сервере. Браузер скачивает этот HTML и показывает пользователю. Потом докачивает скрипты.
- Пока пользователь разглядывает это HTML - скачиваются скрипты + дополнительный (не критичный HTML). Когда подгурзятся и подключатся JS-скрипты — страница становится интерактивной.
- Первая отрисовка страницы заметно быстрее чем в SSR — пользователь сразу видит какой-то осмысленный контент, хоть и без интерактивности.
- YouTube — Animated Server Side Rendering (Fruits)
Прогрессивный рендеринг
на стороне сервера (Progressive Server Side Render)- мин. необходимый HTML готовится на сервере. Браузер скачивает этот HTML и показывает пользователю. Потом подгружаются скрипты и остальной HTML.
- Также называется
прогрессивная гидратация
- Основан на концепции потоковой передачи HTML.
- Страницы делятся на осмысленные части-компонента, эти части подгружаются отдельно. Рендер компонента не происходит, пока он не появится в поле зрения (scroll) или не понадобится для взаимодействия с пользователем.
- Быстрее
CSR
иSSR
. Особенно медленном соединении - Чтоб ещё ускорить — применяют оптимизацию критических этапов рендеринга.
-
Гидратация. Регидратация
Гидратация
илирегидратация
(hydration / re-hydration) — процесс, когда JS преобразует статический HTML в динамическую веб-страницу (присоединяет обработчики событий к элементам HTML). Происходит в браузере после получения HTMl с сервера.- Когда мы используем CSR/PSSR мы получаем с сервера HTML с контентом, и отображаем его пользователю.
- В это время JS работает над созданием полноценного клиентского приложения (Virtual DOM и binding интерфейса управления им). Здесь не надо заново рендерить весь DOM на клиенте, но необходимо добавить недостающие события, методы, а иногда и элементы, которые не рендерились на сервере.
- Этот процесс называется
гидратацией
илирегидратацией
.
-
React hydrate() / hydrateRoot()
hydrate()
— отдельный метод для рендеринга на клиентской стороне.- Используется когда рендеринг на клиенте основан на результатах серверного рендеринга.
- Ввели в 16 версии React (2017)
hydrateRoot()
— заменил методhydrate()
, начиная с React 18 (2022).
-
Параллельный рендер в React (Concurrent rendering)
-
Общее
-
Concurrent rendering
— скрытый механизм работы React. Фундаментальное обновление базовой модели рендеринга React. -
Позволяет React одновременно подготавливать N версий пользовательского интерфейса.
-
Включается только при использовании параллельных функций.
-
Появился в React 18 в 2022.
-
Для реализации
Concurrent rendering
React использует внутри сложные методы, например приоритетные очереди и множественную буферизацию. -
Поскольку одновременный рендеринг прерываем, компоненты ведут себя немного по-другому, когда он включен.
-
Например, используется при:
- при использовании
startTransition
(переход между экранами без блокировки пользовательского ввода). - при использовании
DeferredValue
(ограничении дорогостоящих повторных рендеров).
- при использовании
-
-
Схема работы
- В обычном поведении:
- если React начал перерисовывать DOM, все остальные обновления в очереди блокируются и дожидаются окончания обновления.
- Как только начался рендер обновления ничто не может прервать его, пока пользователь не увидит результат на экране.
- В режиме
Concurrent rendering
- рендеринг не всегда блокируется, а может прерваться.
- React может начать рендеринг обновления, сделать паузу в середине, а затем продолжить позже.
- Может даже полностью отказаться от незавершенного рендеринга. React гарантирует, что пользовательский интерфейс будет выглядеть согласованным, даже если рендеринг будет прерван. Для этого он ожидает выполнения мутаций DOM до конца, как только все дерево будет оценено.
- Благодаря этой возможности React может подготавливать новые экраны в фоновом режиме, не блокируя основной поток => пользовательский интерфейс может немедленно реагировать на действия пользователя, даже если React находится в середине большой задачи рендеринга => плавный пользовательский интерфейс.
- Это улучшает UX и открывает новые возможности.
- В обычном поведении:
-
Повторно используемое состояние (reusable state)
- Другой пример —
повторно используемое состояние
(reusable state). Concurrent React
может удалять разделы пользовательского интерфейса с экрана, а затем добавлять их обратно, повторно используя предыдущий state.- Например:
- пользователь уходит с экрана, а потом возвращается — React должен иметь возможность восстановить предыдущий экран в том же состоянии, в котором он был.
- возможность подготовить новый пользовательский интерфейс в фоновом режиме. Чтобы он был готов до того, как показать пользователю (эту возможность планируют добавить позже, с компонентом
<Offscreen>
).
- Другой пример —
-
StrictMode и поиск ошибок
- Можно использовать
StrictMode
чтобы обнаружить ошибки, связанные с параллелизмом, во время разработки. StrictMode
не влияет на продакшен-сборку, но во время разработки он будет регистрировать дополнительные предупреждения и функции двойного вызова, которые, как ожидается, будут идемпотентными.- Он не поймает все, но эффективно предотвращает наиболее распространенные типы ошибок.
- Можно использовать
-
-
Рендер на стороне сервера
- Рендеринг на стороне сервера - браузер шлёт серверу запрос, сервер присылает весь HTML-файл. Браузер только выводит его пользователю.
- Рендеринг на стороне клиента - браузер запрашивает у сервера много JS и сырые данные из базы. Генерация HTML идёт в браузере (клиенте).
- Рендеринг на стороне клиента имеет недостатки, например плох для SEO. Поэтому можно настроить React для рендеринга на сервере.
- React может выполняться на стороне клиента и, при этом, рендерится на стороне сервера, и эти части могут взаимодействовать друг с другом. Поэтому он широко используется для создания высокопроизводительных веб-приложений и пользовательских интерфейсов.
- React может рендерить компоненты сайта как на серверной, так и на клиентской стороне.
- Хорош для создания изоморфных приложений - позволяет переиспользовать почти весь клиентский код для рендеринга на сервере, в зависимости от масштаба приложения.
- Это возможно т.к.:
- и на сервере и на клиенте используется JS;
- и там и там не критичен реальный DOM браузера - сам React работает с виртуальным.
-
Изоморфность
- При первом обращении к сайту все операции выполняются на сервере и в браузер передается HTML (как обычный статический сайт). После загрузки JS сайт превращается в «одностраничное приложение», и работает соответственно.
- Это полезно для SEO + пользователи сразу видят страницу, а не ждут пока загрузятся все данные и отрисуется нужная информация
- Когда пользователь открывает сайт, содержимое страницы должно быть загружено с сервера. В случае с SPA это может занять некоторое время. Во время загрузки пользователи видят либо пустую страницу, либо анимацию загрузки. Учитывая, что по современным стандартам ожидание в течение более чем двух секунд может быть весьма заметным неудобством для пользователя, сокращение времени загрузки может оказаться крайне важным.
- Virtual DOM позволяет React легко создавать изоморфные приложения. В других JS-фрэймворках клиентская часть кода часто полагается на DOM браузера, которого нет на серверной стороне => нельзя использовать один код и на клиенте, и на сервере. React же дает нам абстракцию браузерного DOM'а в виде виртуального DOM'а.
- Это дает два основных преимущества:
- код, который работает с виртуальным DOM в React не зависит от браузера и может выполняться на сервере;
- React может оптимизировать операции над документами и снизить количество обращений к браузерному DOM и за счет этого значительно ускорить работу фронтенда.
- Одной из основных особенностей React является то, что он может выполняться на стороне клиента и, при этом, рендериться на стороне сервера, и эти части могут взаимодействовать друг с другом. Поэтому он широко используется для создания высокопроизводительных веб-приложений и пользовательских интерфейсов.
-
Ссылки
- Habr - Прогрессивный рендеринг для лучшей производительности веб-приложений
- Habr - Что, черт возьми, такое гидратация и регидратация?
- Серверный или клиентский рендеринг на вебе: что лучше использовать у себя в проекте и почему
- Как интегрировать серверный рендеринг в React-приложение, и зачем он вообще там нужен
- Прогрессивный рендеринг для повышения производительности веб-приложений
- Wikipedia - Hydration (web development)
- Оф. документация — метод
hydrate()
- Видео - Server Side Rendering с использованием NextJS (ru, «Andersen»)
- Дока - Виды веб-приложений
- IT-Kamasutra — FullStack Web архитектура - Server Side Rendering - 01
- IT-Kamasutra — FullStack Web архитектура - Server Side Rendering сегодня - 02,
Порталы
- Способ отображения дочерних компонентов в узел DOM вне DOM-иерархии родительского компонента.
- Если надо, чтобы дочерние компоненты визуально вырывались из родительского контейнера.
- Примеры:
- Модальные окна
- Всплывающие подсказки
- Загрузчики
- ...
- Т.е. у меня компонент, который выводит что-то внутри
body.div#root.div#component
. - А я хочу вывести из него модальное окно, которое будет отображаться в
body
-
<html> <body> <div id="root"> <div id="component">Компонент здесь</div> </div> <div id="modal">Модальное окно должно быть здесь</div> </body> </html>
- Синтаксис
-
ReactDOM.createPortal(child, container)
-
return ReactDOM.createPortal( <div className="modal"> <span>{message}</span> <button onClick={onClose}>Close</button> </div>, document.body );
-
const Modal =({ message, isOpen, onClose, children })=> { if (!isOpen) return null; return ReactDOM.createPortal( <div className="modal"> <span>{message}</span> <button onClick={onClose}>Close</button> </div>, document.body ); } function Component() { const [open, setOpen] = useState(false) return ( <div className="component"> <button onClick={() => setOpen(true)}>Open Modal</button> <Modal message="Hello World!" isOpen={open} onClose={() => setOpen(false)} /> </div> ) }
-
- Ссылки
Роутинг
-
Что такое React-Router?
- React-Router - набор компонентов определяющих на основе текущего пути, какой компонент будет выводиться.
- react-router - пакет с базовым набором функций
- router-dom - пакет с набором функций для работы в браузере
- React-Router - набор компонентов определяющих на основе текущего пути, какой компонент будет выводиться.
-
React-router-dom
-
Модуль npm + специальная разметка в корневом компоненте
-
Переключение компонентов на странице, в зависимости от адреса в адресной строке.
-
Отличия React-router, React-router-dom и React-router-native**
- React-router - базовая библиотека, необходимая для работы React-router-dom и React-router-native
- React-router-dom и React-router-native ставятся поверх неё. Все функции использовать тольько из них, никогда не использовать напрямую React-router
- React-router-dom - для роутинга в веб-разработке
- React-router-native - для роутинга в разработке под мобильные
- .
- https://stackoverflow.com/questions/42684809/react-router-vs-react-router-dom-when-to-use-one-or-the-other
- https://reactdev.ru/libs/react-router/?ysclid=l7v9mkltgi451956102#match
- https://habr.com/ru/post/329996/?ysclid=l7v9rl06yb497534719
- https://metanit.com/web/react/4.1.php?ysclid=l7v9ssxl69809229348
- https://www.npmjs.com/package/react-router
-
Ссылки
- IT-Kamasutra #19 - React-router-dom
- IT-Kamasutra #22 - Route-exact — настройка роута для страниц 2 уровня - переключаем адрес в адресной строке,
-
-
BrowserRouter и HashRouter
-
Для браузерных проектов есть BrowserRouter и HashRouter компоненты.
- BrowserRouter — следует использовать когда вы обрабатываете на сервере динамические запросы. Если проект предполагает бекенд - бери BrowserRouter.
- HashRouter - когда у вас статический веб сайт.
-
Обычно предпочтительнее использовать BrowserRouter, но если ваш сайт расположен на статическом сервере(как github pages) , то использовать HashRouter это хорошее решение проблемы.
-
-
Компонент Route
<Route/>
- компонент, строительный блок React Router'а.- Если вам нужно рендерить элемент в зависимости от pathname URL'ов, то следует использовать компонент
<Route/>
-
Как связаны Route и NavLink?
- Route и NavLink = два независимых элемента.
- Фактически, это обычные компоненты, написанные разработчиками, и подключаемые из билиотеки. В них передаются параметры и функции. ПРи наступлении условия X сделай то-=то (например, отрисуй компоненту такую-то)
- Route - меняет содержимое страницы, в зависимости от того, что введено в адресной строке. Следит за ней, и при изменениии - отрабатывает
- NavLink - при клике меняет адрес в адресной строке
StrictMode (Строгий режим)
-
StrictMode
— инструмент для выделения потенциальных проблем в приложении. -
Работает только в режиме разработки (development), никак не влияет на продакшен-сборку.
-
Активирует дополнительные проверки и предупреждения.
-
Не отображает ничего визуального (похожего на Fragment), но обнаруживает потенциальные проблемы в коде и даёт полезные предупреждения.
-
Появился в React 16.3 в марте 2018.
-
С появлением Hooks и Concurrent Mode (параллельный режим), который не за горами, строгий режим становится всё более важным инструментом для обнаружения плохих практик (параллельный режим, вероятно, не будет работать, пока не исправишь предупреждения, выдаваемые в строгом режиме).
-
Зачем
- Обнаружение небезопасных методов жизненного цикла
- Предупреждение об использовании устаревшего API строковых реф
- Предупреждение об использовании устаревшего метода findDOMNode
- Обнаружение неожиданных побочных эффектов
- Обнаружение устаревшего API контекста
- Обеспечение переиспользованного состояния
- Дополнительные проверки будут включены в будущих релизах React
-
Использование
- Обычно в
index.js
оборачивают весь компонент приложения в<React.StrictMode> </React.StrictMode>
: -
import React from "react"; import ReactDOM from "react-dom"; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root") );
- Можно обернуть в код один/несколько компонентов
- Обычно в
-
Ссылки
CSS
-
Структура проекта
- Если нужно добавить для компонента CSS - создаю для этого компонента отдельную папку (название = названию компонента), в ней файл компонента (index.js) и style.css
- Есть ещё различные варианты CSS-in-JS - когда CSS хранится и генерируется прямо в JS-коде. Подходы интересные, но со совимим минусами. См Styled component.
-
Библиотеки для работы с ClassNames: Classnames, CLSX
- Позволяют более простым способом объединять разные classes в зависимости от различных условий.
- Предположим, у вас есть 2 класса, из которых один будет использоваться каждый раз, а второй будет использоваться в зависимости от некоторого условия.
- Есть две самых популярных библиотеки для работы ClassNames:
- classnames — немного популярнее, более функциональна (см документацию)
- clsx — легче и быстрее (т.к. функциональность меньше)
- Classnames
- https://www.npmjs.com/package/classnames
- объединять имена css-классов, чтоб просто писать через запятую, без конструкций типа ’&{styleName1} + ', ' + &{stylenmae2
- динамический css - писать простые условия в именах css-классов. Если props = true, то ставь класс
-
classNames('foo', 'bar'); // => 'foo bar' classNames('foo', { bar: true }); // => 'foo bar' classNames({ 'foo-bar': true }); // => 'foo-bar' classNames({ 'foo-bar': false }); // => '' classNames({ foo: true }, { bar: true }); // => 'foo bar' classNames({ foo: true, bar: true }); // => 'foo bar' // lots of arguments of various types classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux' // other falsy values are just ignored classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1' //Arrays var arr = ['b', { c: true, d: false }]; classNames('a', arr); // => 'a b c' //Dynamic class names let buttonType = 'primary'; classNames({ [`btn-${buttonType}`]: true });
- Ссылки
-
Adaptive/Responsive. Способы различения десктопа / мобильного
Адаптивный (Adaptive)
— комплекс визуальных интерфейсов, созданных под конкретные размеры экрана.Отзывчивый (Responsive)
— единый интерфейс, который подстраивается под любой размер экрана.- Два основных подхода к реализации:
- по ширине экрана. Определяем ширину скриптом (например
window.innerWidth
), храним вstate
, сравниваем с константами-breakpoints, по результатам выводим нужный компонент или меняем класс) - по user-agent
- по ширине экрана. Определяем ширину скриптом (например
- Для обоих есть модули и библиотеки.
- Лучше всего использовать оба.
- Подход с user-agent проще, но использовать только его недостаточно. Любой, кто серьезно разрабатывал адаптивные интерфейсы, знает про «магический поворот» iPad-ов и подобных ему девайсов, которые в вертикальном положении попадают под определение мобильных, а в горизонтальном — десктопных, но при этом имеют user-agent мобильного устройства. Также стоит отметить, что в рамках полностью адаптивно/отзывчивого приложения по одной лишь информации о user-agent невозможно определить мобильность, если пользователь использует, например, десктопный браузер, но сжал окно до «мобильного»размера.
- Не стоит пренебрегать информацией о user-agent. Очень часто в коде можно встретить такие константы, как isSafari, isIE и т.д., которые обрабатывают «особенности» этих устройств и браузеров. Лучше всего комбинировать оба подхода.
- ВАЖНО: при использовании системы типа Google material UI - используй её встроенные возможности. Там есть
родные
методы реализации адаптивности/отзывчивости. - Общие соображения
- надо отслеживать мобильность и на уровне CSS (менять вёрстку) и на уровне компонент (в одних случаях выводим мобильные, в других десктопные, или просто изменять их).
- желательно и там и там использовать какой-то единый метод, иначе может получиться что в CSS (media-query) определилиась одна ширина, а в компоненте (по ширине дисплея) - другая
- при этом, из компоненты я могу добавить класс Desktop, а вот из CSS передать в компонент информацию сложно
- откуда вывод: лучше работать «от комопненты» — определять мобильность там, прописывать в глобальный стэйт, и там где нужен специальный css — выводить доп. класс (на основании этого знаения в state)
- Библиотеки
- react-use (979 199) - отслеживает кучу всего, в том числе MediaQuery и WindowSize
- react-device-detect (530 267) - работает с user agent
- react-responsive (511 397) - работает с шириной экрана
- mobile-detect (164 455) - работает с user agent
- react-media (112 694) - работает с шириной экрана
- react-socks (4 401) - HOC
- Ссылки
-
SCSS, SASS
-
Разработчики React не рекомендуют повторно использовать одни и те же классы CSS в разных компонентах. Например, вместо использования css-класса
.Button
в компонентах<AcceptButton>
а также<RejectButton>
, рекомендуется создать отдельный компонент<Button>
со своим стилем.Button
=> его можно отображать и в<AcceptButton>
, и в<RejectButton>
(но не наследовать). -
Следование этому правилу часто делает препроцессоры CSS менее полезными (SCSS и SASS в том числе), поскольку такие функции, как примеси и вложенность, заменяются композицией компонентов. Однако вы можете интегрировать препроцессор CSS, если считаете его полезным.
-
Например, с его помощью удобно выносить в отдельный файл css-переменные (цвета, кегль шрифтов, размеры отступов и т.д.)
-
Как включить поддержку SCSS
yarn add sass
- В некоторых случаях надо настраивать пути
SASS_PATH
и файл.env
(см. оф. документацию Create React APP)
-
Ссылки
-
-
Сss-modules
-
CSS модуль — это CSS файл, в котором все имена классов и анимаций имеют локальную область видимости по умолчанию.
-
Такой метод подключения CSS, при можно использовать одинаковые имена css-классов в разных компонентах, и конфликта не будет. Что-то типа автоматизированного BEM. При это css по-прежнему хранятся в отдельном файле и пишутся практически как обычно
-
Вместе с CSS-modules обычно используют библиотеки
CLSX
илиClassnames
(см. выше)
-
Как использовать (Create React APP)
- Переименовать файл стилей из
file.css
вfile.module.css
- В компоненте, где используются стили импортировать так:
import styles from './file.module.css';
- Классы прописывать так:
- Было:
<div className = 'App'>
- Стало:
<div className = {styles.App}>
- Если в className нужно несколько стилей или какие-то условия (при значении Х ставь класс Y) используем библиотеки
CLSX
илиClassnames
(см. выше).
- Переименовать файл стилей из
-
Ссылки
- Habr - Практическое руководство по использованию CSS Modules в React приложениях
- Habr - Эволюция CSS: от CSS, SASS, BEM и CSS–модулей до styled-components
- You-Tube - IT-Kamasutra 14. Уроки React JS (css-модули, css-modules)
-
-
Styled components
- Библиотека для работы со стилями методом
CSS in JS
(описание стилей в JavaScript файлах). - Аналоги:
- Среди причин выбора CSS-in-JS можно назвать то, что эта технология позволяет ограничивать область видимости стилей и отказаться от глобальной стилизации. Её удобно применять для работы с темами приложений.
- В старой версии приложения я использовал библиотеку styled-components. Чем это плохо? Дело в том, что обычный CSS быстрее и занимает меньше места. Современные браузеры умеют загружать CSS-код параллельно с JavaScript-бандлом. Кроме того, для использования обычного CSS не нужно дополнительной библиотеки. Минифицированный вариант styled-components занимает порядка 54 Кб. Использование обычного CSS вместо styled-components привело к тому, что код приложения быстрее загружается, и к тому, что при изменении стилей системе приходится выполнять меньше вычислений
- Особенности
- Css in JS - использование компонент. Стиль становится частью компонента - динамический css
- Вся мощь JS - у нас обычная функция, которая вернёт строку со стилями. Модем делать все что можно в функции
- Lazy loading - стили подгружаются вместе с компонентом. Через это решается вопрос с critical css - компонент первый загрузился, и стили уже загрузились с ним. Именно стили для него.
- Стили формируются на этапе runtime.
- Коллизии имён - имена стилей уникальны, не надо об этом беспокоится. Имена классов формируются автоматически, они уникальны
- Dead css - т.к. стили хранятся в компоненте, не бывает что компонент уже удален, а стили от него остались
- Объединение не по технологиям (отдельные файлы html, JS, css), а по бищнес-залачам (кнопка, модальное окно). Архитектурно более правильно - абстракция с точки зрения бизнес-функции
- Работает в runtime - браузер парсит josh строку компонента и засовывает в css в header. Это быстро
- Можно прокидывать стили в сторонние библиотеки ui-компонентов (оборачивать их)
- атрибут attrs - пробросить в DOM-элемент какие-то property, которые хочется не через стили меняться, а прокидываться сразу в DOM. Например быстрая-частая смена цвета фона - через inline-стили, чтоб не генерировать кучу classname
- Есть отдельный компонент Icon, есть компонент Avatar внутри которого выводится Icon. Могу из компонента Avatar управлять параметрами Icon. Если в Avatar пришёл props X - можно, например предать в Icons width = (размер*Х).rem
- Ссылки
- Официальный сайт проекта (en)
- YouTube - Артём Арутюнян с докладом о библиотеке styled-components
- Habr - Styled Components — идеальная стилизация React-приложения (2021)
- Habr - Знакомство с Styled components
- Habr - Эволюция CSS: от CSS, SASS, BEM и CSS–модулей до styled-components
- Habr - CSS-in-JS — мифы и реальность (на примере styled-components)
- Habr - Анонс новой версии Styled Components v5
- Habr - История четырёхкратного ускорения React-приложения
- Medium - Styled-components getting started (en)
- Medium - How to use styled components with Material UI in a React app (2019)
- Как использовать стилизованные компоненты в React
- Знакомство с библиотекой Styled Components в React
- Библиотека для работы со стилями методом
-
React + Bootstrap
- C Bootstrap в React можно работать как в чистом виде, так и при помощи специальных react-библиотек, которые интегрируют Bootstrap в React.
-
React + Google Material UI
-
Material Design — фрэймворк/дизайн-система, создана Google дл быстрой разработки интрефейсов.
-
Есть различные js-библиотеки, которые интегрируют Material Design в React.
-
Я использую официальную библиотеку Google - Material UI
-
Material UI использует «под капотом» библиотеку для работы со стилями — Emotion либо Styled-components.
-
Какую из этих двух библиотек использовать — выбирается при установке npm-пакета.
-
Краткий алгоритм установки/настройки
- ставим Material Ui и зависимости
- сам Material Ui
yarn add @mui/material @mui/styled-engine-sc styled-components
- шрифт Roboto
yarn add @fontsource/roboto
или подключить из CDN (https://mui.com/material-ui/react-typography/#general) - SVG-иконки
yarn add @mui/icons-material
- сам Material Ui
- ставим пакеты типизации (кажется не нужно, всё работает из коробки)
- опционально: настраиваем IDE (подсветка Styled-components/Emotion и т.д.)
- Настройки: File | Settings | Plugins
- Styled Components & Styled JSX
- https://www.jetbrains.com/help/phpstorm/react.html
- настраиваю провайдер
theme
— объект с базовыми настройками дефолтных стилей, прокидывается по дереву проекта (или его части) на подобииcontext
- https://mui.com/material-ui/customization/theming/
- Создаю файл с темой, например muiTheme.ts
import { red } from '@mui/material/colors'; import { createTheme } from '@mui/material/styles'; export const theme = createTheme({ palette: { primary: { main: red[500], }, }, });
- оборачиваю компонент верхнего уровня в
ThemeProvider
и прокидываю в негоtheme
import React from 'react'; import App from './components/App'; import { ThemeProvider } from '@mui/material/styles'; import { theme } from './config/muiTheme'; root.render( <ThemeProvider theme = {theme}> <App /> </ThemeProvider> );
- задаю базовую разметку страницы
<Box> <Container> < > </Container> </Box>
- подключаю готовые стилизованные компоненты (кнопки, меню и т.д.)
- если надо - настраиваю и стилизую их
- ставим Material Ui и зависимости
-
Ссылки
-
Формы — популярные библиотеки
-
Redux-Form
- Redux-Form - популярный, старый. Есть ряд проблем - см Final Form. Автор рекомендует сейчас использовать Final Form или Formik
- Дэн Абрамов: состояние формы по своей сути является эфемерным и локальным, поэтому отслеживание его в Redux (или любой другой библиотеке Flux) не требуется.
- Redux-Form вызывает весь редуктор Redux верхнего уровня несколько раз НА КАЖДОМ ОДНО НАЖАТИЕ. Это нормально для небольших приложений, но по мере роста вашего приложения Redux задержка ввода будет продолжать увеличиваться, если вы используете Redux-Form.
- Redux-Form имеет размер 22,5 КБ в сжатом виде (формат Formik составляет 12,7 КБ)
-
Final Form
- Final Form - улучшенная версия от создателей Redux-Form.
- Меньший размер
- Не только для React
- Не имеет зависимостей,
- Можно использовать встроенные функции рендеринга.
- Поддерживает хуки
- Использует свой стэйт (не Redux).
- Не лагает на больших формах (т.к. использует Redux).
- В форме очень много данных постоянно меняется, там же не только текущие значения инпутов, а еще много разной доп. информации, и на каждое изменение у тебя перерисовывается вся форма.
- форма принимает функционального чилдрена, и он будет перерисован на любое изменение стейта формы
- филды тоже перерисовывают переданную им компоненту постоянно. в final-form ввели механизм подписок, ты можешь гибко управлять перерисовками.
- В чем разница между redux-form и final-form - redux-form дает возможность управлять формой извне, если быть точным из любого уголка твоего приложения, т.к. ты можешь диспатчить экшены редакс-формы. И ситуация, когда тебе, в каком-то рядовом проекте, нужно залезть в потроха формы не из формы, мне кажется уже выглядит криво. Нжно из двух зол выбрать наименьшее:
- с одной стороны final-form, который не позволяет лезть в форму извне (читать стейт формы мы можем без проблем), но при этом решает проблемы производительности redux-form
- с другой стороны redux-form, который дает больше гибкости по управлению формами извне (что мне кажется приводит к увеличению кол-ва говнокода на проекте), но при этом есть проблемы с производительностью, о которых нужно постоянно думать и закрывать какими-то костылями оптимизациями рендера.
- Что значит потроха? Нужен доступ к стейту формы за пределами формы. Регулярно такие требования возникают. Ничего кривого не вижу: у тебя могут возникать моменты в которых тебе необходимо заполнить форму из других частей приложения. Требования нахреновертить идут от бизнес-требований. Если есть требование ткнуть в одно место и заполнить форму или засабмитить форму совершенно в другом месте то redux-form тут ни в чём не виноват. Наоборот, позволяет легче сделать
-
Formik
- Formik - популярный. Рекомендуется разработчиками React
- есть хуки
- хранит стэйт в компонентах формы
- поддерживает тесную интеграцию с Yup — библиотекой для валидации.
- есть 3 метода создания форм: через компоненты и другие, через хук useFormik (лдя простых случаев), через HOC withFormik
-
React Hook Form
- React Hook Form - простой и на хуках.
- Без зависимостей.
- Заметно меньше ререндера чем у Formik (3 против 30 в 2021)
- Есть визуальный конструктор простых форм - https://react-hook-form.com/form-builder
-
Работа с формами при помощи кастомных хуков - useState, useCallback
-
Ссылки
- Erik Rasmussen - Final Form: Дорога к клетчатому флагу
- Обсуждение - Выпилить react-final-form. Вернуть redux-form
- Habr - React.js — формошлепство или работа с формами при помощи пользовательских хуков
- Как с легкостью создавать формы на React с помощью react-hook-form
- Оф. документация React - Формы. Управляемые компоненты. Тэги форм в React
Графики, визуализация данных — популярные библиотеки
-
D3.js
- D3.js - библиотека графики для JavaScript.
- Позволяет привязывать произвольные данные к объектной модели документа (DOM), а затем изменять этот документ на основе данных.
- Включает в себя много более мелких технических модулей, таких как оси, цвета, иерархии, контуры, многоугольники и проч.
- Если вы хотите углубиться в детали и иметь полный контроль над каждым элементом, эта библиотека – лучший выбор для вас. Но если вы вам нужно сделать визуализацию данных в вашем проекте, а сроки поджимают, это вряд ли лучший вариант.
- Попытка создать простую диаграмму может быть довольно трудоемкой задачей. Все элементы, включая оси и прочие детали, должны задаваться явно.
-
Victory
- Victory - высокопрофессиональную экосистему компонентов с полностью изменяемыми стилями и поведением
- хороший баланс между простотой использования и высокой настраиваемостью.
- У этой библиотеки есть практически идентичные API для ReactJS и React Native — эту важную особенность стоит иметь в виду.
-
Nivo
- Nivo - красиво, просто в использовании(на основе D3js)
- красивый UI,
- просто невероятную «интерактивную документацию». Nivo предлагает вам не читать длинные тексты с описанием функционала, а воспользоваться ее потрясающим пользовательским интерфейсом для испытания и настройки компонентов ваших графиков. Когда сочтете, что все готово, просто скопируйте код вашего графика. (Со временем, конечно, вы сможете работать с кодом напрямую — этот подход предпочтительнее).
-
Chart.js
- [Chart.js](https://www.chartjs.org/) - библиотека с открытым исходным кодом, поддерживает 8 типов диаграмм. - - Отличается малым весом – всего 60kb. - Использует элементы canvas для рендеринга, диаграммы отзывчивы к изменению размеров окна. - Анимация при первой отрисовке.
-
Google Charts
- Google Charts
- просты в использовании и при этом являются достаточно мощным инструментом
-
Vis
- Vis — напоминает Recharts в том, что касается попыток сделать экосистемы компонентов как можно проще и модульнее.
- Лейтмотив этой библиотеки — если вы знаете React, то вы знаете и Vis.
- созданная Uber
- графики выглядят про
-
VX
- VX - для тех, у кого есть четкое понимание того, как должны выглядеть и работать их графики, но еще нет достаточно знаний, чтобы построить собственную экосистему компонентов на базе D3
- отличается гибкостью и спроектирован таким образом, чтобы служить в качестве надстройки
-
WebDataRocks
- WebDataRocks - компонент сводной таблицы JavaScript, который совместим с React и другими фреймворками.
- Поддерживает подключение к удаленным/ локальным источникам данных JSON и CSV.
- бесплатный и настраиваемый.
-
Flexmonster
- Flexmonster — это более продвинутый компонент сводной таблицы и расширенная версия WebDataRocks.
-
JSCharting
- JSCharting - поддерживает большое количество видов диаграмм. Можно создавать богатый интерактивные диаграммы
- включая картодиаграммы, диаграммы Гантта, биржевые диаграммы и другие, для использования которых часто требуются отдельные библиотеки.
- Содержит встроенные карты всех стран мира и библиотеку SVG-значков.
- Набор отдельных микро-диаграмм может отображаться в любом элементе div на странице.
- Элементы управления (UiItems) позволяют создавать более богатые интерактивные диаграммы.
- Библиотеку отличает простота управления данными или переменными визуализации в режиме реального времени.
- Готовые диаграммы можно экспортировать в форматы SVG, PNG, PDF и JPG.
-
Highcharts
- Highcharts - популярная библиотека JavaScript для создания диаграмм, которую используют многие из самых крупных компаний мира.
- Диаграммы генерируются с использованием SVG. Откат к VML обеспечивает обратную совместимость вплоть до IE6/IE8.
- Довольно богатый функционал,
- Примеры на сайте визуально особого восхищения не вызывают.
-
AmCharts
- AmCharts
- включен сильный движок SVG-анимации, позволяющий создавать сцены, напоминающие кино.
- Примеры диаграмм выглядят очень красиво. Для большинства демок есть несколько палитр и слайдер UI для регулировки переменных диаграммы в режиме реального времени.
- Для настройки применяется декларативный API (не подход на основе конфигурации, как у других). Для настройки диаграмм требуется немного больше кода.
-
FusionCharts
- FusionCharts - старая надежная библиотека для визуализации.
- Поддерживает много форматов данных, включая XML, JSON и JavaScript, работает в современных браузерах и имеет обратную совместимость с IE6.
- Галерея диаграмм включает большое количество примеров, которые выглядят безупречно.
-
KoolChart
- KoolChart - для создания диаграмм на основе HTML5 Canvas.
- Использование canvas предлагает лучшую производительность за счет растровой основы.
Lazy и Suspense. Разделение кода (code splitting)
- «Ленивые» (lazy) компоненты - их смысл заключается в разделении приложения на небольшие фрагменты кода. Загрузка этих фрагментов выполняется только тогда, когда они нужны.
- Лучший способ внедрить разделение кода в приложение — использовать синтаксис динамического импорта:
import()
. - Когда Webpack сталкивается с таким синтаксисом, он автоматически начинает разделять код вашего приложения.
- Функция
React.lazy
позволяет рендерить динамический импорт как обычный компонент. React.lazy принимает функцию, которая должна вызвать динамический import(). Результатом возвращённого Promise является модуль, который экспортирует по умолчанию React-компонент (export default). - Компонент с ленивой загрузкой должен рендериться внутри компонента Suspense, который позволяет нам показать запасное содержимое (например, индикатор загрузки) пока происходит загрузка ленивого компонента.
- Ссылки
Статическая типизация React
-
Про статическую типизацию
- При статической типизации мне необязательно напоминать компилятору, что данная переменная, например, целое число, и всегда должно им оставаться. Эта информация хранится в программе, и даже если я, забывшись, попытаюсь изменить ее значение на недопустимое, ничего страшного не произойдет.
- Статическая типизация: Проверка типов на стадии компиляции, перед запуском программы. C, C++, C#, Java, Pascal...
- Динамическая типизация: Проверка типов когда программа уже запущена. Perl, Ruby, JavaScript, Lisp, PHP, Python...
- Статические языки
- производительнее.
- с помощью тестирования типов легко проверить работоспособность кода еще до его выполнения.
- требует от программиста большей ответственности.
- Динамические языки, в свою очередь, могут поощрять некоторую раскованность, вырабатывая у разработчика привычку следовать дурным паттернам.
- В каждом случае если вы хотите использовать типы, то явно говорите инструменту, в каких файлах осуществлять проверку типов.
- В случае TypeScript вы делаете, создавая файлы с расширением .ts вместо .js.
- В случае Flow вы указываете в начале кода комментарий @flow.
- Динамический пример на JS
-
var name = "Susan", age = 25, hasCode = true;
-
- Статический пример на TypeScript
-
let name: string = "Susan", age: number = 25, hasCode: boolean = true;
-
- Ссылки
-
Type Script - язык (Microsoft)
- Язык разработанный Microsoft. Совместим с JS (расширяет его). Добавляет возможности статической типизации и ООП.
- Представляет собой надмножество, которое компилируется в JavaScript — хотя по ощущениям TypeScript похож на новый язык со статической типизацией сам по себе. То есть очень похож на JavaScript и не сложен в освоении.
- Есть аналогичное решение от Facebook - Flow.
- В Angular разработка ведётся на TypScript
- Динамический пример на JS
-
var name = "Susan", age = 25, hasCode = true;
-
- Статический пример на TypeScript
-
let name: string = "Susan", age: number = 25, hasCode: boolean = true;
-
- Надо явно сообщить React, в каких файлах осуществлять проверку типов - создаём файлы с расширением .ts вместо .js. **
- Уточнить:** кажется, это вообще отдельные от кода файлы с описанием типов.
- Ссылки
-
Flow - библиотека (Facebook)
- Open source библиотека для статической проверки типов.
- Библиотеку разработала и выпустила Facebook.
- Позволяет постепенно добавлять типы в ваш код JavaScript.
- Flow представляет собой инструмент статического анализа. Использует надмножество языка, позволяя добавлять аннотации типов ко всему вашему коду и улавливать весь класс ошибок во время компиляции.
- Надо явно сообщить React, в каких файлах осуществлять проверку типов - указывать в начале кода комментарий @flow.
-
PropTypes - библиотека React
-
propTypes - специальное свойство класса компонента (так было раньше?). Задают типы входных параметров для отрисовываемого компонента.
-
Очень простой - проверяет только props.
- изначально был в составе React, потом вынесли в отдельную библиотеку
- функциональность намного меньше и проще, чем у TS/Flow
- проверяет только props
- выдаёт предупреждения об ошибках типов во время запуска
-
PropTypes может давать вам предупреждения во время выполнения, что может быть полезно для быстрого поиска неверных ответов, поступающих с сервера, и т.
-
В существующих приложениях с большими объектами, это быстро приведет к большому количеству кода. Это проблема, так как в Реакте часто нужно передавать один и тот же объект множеству компонентов. Повторение этого процесса во множестве компонентов нарушает принцип DRY (Don’t Repeat Yourself). Самоповторы приводят к проблемам с поддержкой.
-
Проблема с использованием PropTypes вместе с Flow заключается в том, что вы пишете много дубликатов кода.
-
Оба определения в основном содержат ту же информацию, и когда тип данных изменяется, необходимо обновить оба определения.
-
В случае несовпадения (например вместо числа пришла строка) позволяют получить ошибку в react-dev-tools и отловить этот момент во время выполнения.
-
Это была встроенная возможность контроля типов для больших приложений, но с недавнего времени вынесена в отдельный пакет.
-
Для некоторых приложений, вы можете использовать расширения JavaScript такие как Flow или TypeScript осуществляя проверку типов всего вашего приложения. Но если вы не используете таковые — React предоставляет некоторые встроенные возможности проверки типов.
-
Пример:
-
class Greeting extends React.Component { render() { return ( <h1>Hello, {this.props.name}</h1> ); } } Greeting.propTypes = { name: React.PropTypes.string };
-
-
В целях производительности, propTypes проверяются только в режиме разработки (development). Т.е. сам код остаётся, но не проверяется. Для удаления кода есть спец. модуль
-
PropTypes и defaultProps это статичные свойства. Объявлять их надо как можно выше в коде (позже вам скажут спасибо многие разработчики).
-
-
Различия PropTypes и Flow
- Кроме того, что и PropTypes и Flow относятся к очень широкому полю проверки типов, между ними нет особого сходства.
- Flow
- почти язык
- инструмент статического анализа
- использует надмножество языка, позволяя добавлять аннотации типов к ко всему вашему коду и улавливать весь класс ошибок во время компиляции.
- если вам нужен более гибкий метод проверки типов для всего проекта, то Flow/TypeScript являются подходящими.
- PropTypes
- едва ли библиотека
- инструмент проверки во время запуска. Теоретически может отловить ошибки, которые могут быть пропущены
- базовы проверяльщик типов, который был частью React. Он не может проверять ничего, кроме типов props, передаваемого данному компоненту.
- пока вы передаете только аннотированные типы в компоненты, вам не понадобятся PropTypes.
- Если вы просто хотите проверить типы props, не делайте излишнюю усложнение остальной части своей кодовой базы и идите с более простой опцией.
Тестирование React-component
-
Общее
- Есть
unit-тестирование
(проверка работы отдельных модулей самих по себе) иe2e тестирование
(проверка всей системы целиком). - В первую очередь, все говорят про unit-тесты
- Есть
-
Что тестировать
- значения приходящие в компонент через
props
должно быть доступно ей изнутри, например в её стэйте - при рендере компонента в ней есть нужные html-тэги. Например, у компонента есть два режима - «Edit mode» и «Normal», переключатся по пропсам или стэйту. В одном случае выводится , в другом . Проверяем, есть ли в разметке при таком-то выводе
- в пропсах закинули статус, он должен отобразиться в . Проверяем что в придёт именно этот текст.
- кнопки нажимаются
- проверить работу Callback - что он вызывается и делает что надо....
- имитировать клики, хэндеры, разные пропсы... - что произойдёт
- значения приходящие в компонент через
-
Популярные технологии
- Jest - delightful JavaScript testing used by Facebook to test all JavaScript code including React applications. Разрабатывается Facebook
- Enzyme - a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components’ output.
- Mocha
-
На оф. сайте также упоминаются
- react-testing-library - Simple and complete React DOM testing utilities that encourage good testing practices.
- React-unit - a lightweight unit test library for ReactJS with very few ( js-only) dependencies.
- Skin-deep - Testing helpers for use with React’s shallowRender test utils.
- Unexpected-react - Plugin for the unexpected assertion library that makes it easy to assert over your React Components and trigger events.
-
Ссылки
- Википедия - модульное тестирование
- Оф. документация - инструменты тестирования (en)
- Habr - Тестирование React-Redux приложения (Jest)
- Тестирование компонентов в React с использованием Jest: основы
- Habr - React: тестируем компоненты с помощью Jest и Testing Library (2022)
- Hexlet - Тестирование JSReact (платный доступ)
- Пацианский М - Тестирование React компонентов с помощью jest и enzyme
- Medium - Модульное тестирование React-приложения с помощью Jest и Enzyme
- Medium - Что и как тестировать с помощью Jest и Enzyme. Полная инструкция по тестированию React-компонентов
- learn.javascript.ru - Автоматические тесты при помощи chai и mocha
- Знакомство с разработкой через тестирование в JavaScript (Mocha)
- IT-Kamasutra #92 - тестируем компоненты, тесты, react-test-renderer - React JS
Методы отладки React
- React devtools
- поставляется в двух видах
- отдельным пакетами
- расширением для популярных браузеров. В расширении можно увидеть изменения состояний приложения и узлы виртуального DOM-дерева.
- поставляется в двух видах
- console.log()
- Иногда хочется отлаживать по старинке, с помощью console.log().
- Можно получить значение переменной внутри JSX прямо в точке её применения.
<img src={console.log('logo', logo) || logo} />
- Как это работает:
- console.log() вернет undefined и код выполнится дальше по условию "||",
- а в консоли браузера мы увидим искомое значение, например: "/static/media/logo.5d5d9eef.svg".
- debugger
(() => { debugger })() || // anything
- Отладка внутри IDE
- На примере WebStorm
- Установите расширение Chrome — JetBrains IDE Support.
- Добавьте Run/Debug-конфигурацию.
- Запустите create-react-app через терминал командой: $ yarn start
- Выберите конфигурацию Debug и нажмите кнопку с иконкой жука (в правом верхнем углу IDE)
- Откроется браузер с предупреждением "JetBrains IDE Support отлаживает этот браузер". Замечено, что если теперь открыть Chrome DevTools по [F12], то отладка в WebStorm завершится — не надо этого делать)
- можно отметить нужную строчку кода, как точку останова, затем перегрузить страницу браузера по [F5], и получить желаемое — инструмент отладки внутри WebStorm.
- Ссылки
Быстродействие и оптимизация React
-
Как оптимизировать рендер компонента
shouldComponentUpdate
— метод жизненного цикла классового компонента, если он вернет false то рендер не будет запущенReact.PureComponent
— класс, реализующий типовой shouldComponentUpdate.React.memo
— HOC, который предотвращает повторный рендер, если входные props не изменилисьuseMemo()
— чтобы в функциональном компоненте сохранить ссылки на объекты между рендерамиuseCallback()
— чтобы в функциональном компоненте сохранить ссылки на объекты между рендерами - аттрибуты keyContext
/useContext()
- Context API обеспечивает передачу переменных в дерево компонентов, без их непосредственной передачи в props данных компонентов.- оптимизация структуры компонет - помещать логику ближе к месту использования данных
-
Какие хуки использовать для оптимизации рендера
useMemo()
— чтобы в функциональном компоненте сохранить ссылки на объекты между рендерамиuseCallback()
— чтобы в функциональном компоненте сохранить ссылки на объекты между рендерамиuseContext()
- Context API обеспечивает передачу переменных в дерево компонентов, без их непосредственной передачи в props данных компонентов.
-
Изменение параметров функции, side-эффекты и cloneDeep
- Никогда, никогда, никогда в жизни не делайте так!
- Изменения параметров функции это зашквар если честно - это сразу +50% к появлению сайд эффектов.
- Изменять параметры функции можно только в одном случае, если у вас рекурсия и вы изменяете объект.
- Хотите такой же объект, делайте копию.
- Есть крутая штука, cloneDeep у lodash
- Пользуйтесь ей - это сразу избавил вас от неожиданных последствий
- Оптимизацию react приложений, я заставляю изучать всех, с кем в проектах участвую. Это сейчас «must have»
-
Side-эффекты в методах жизненного цикла
- В
ComponentDidMount
мы имеем право сделать сайд-эффект - Сайд-эффектами являются запросы на сервер, асинхронные операции, setTimeout, обращения к DOM-элементам напрямую
- В результате компонента будет перерисована, и будет вызван уже не ComponentDidMount, а ComponentDidUpdate
- В
ComponentDidUpdate
мы имеем право сделать сайд-эффект, но уже с неким условием, которое позволить его в какой-то момент прекратить. - Потому что в результате будет вызвана перерисовка компоненты, снова вызван ComponentDidUpdate... И если нет условия - получим цикл, и приложение зависнет
- В
-
Props и производительность
- Основная опасность - увеличение количества render, которые мешают производительности
- Важно: при вызове render() перерисовывается не только родительский компонент, но и все дочерние (хотя, в них свойства могли и не поменяться). Т.е. если у родительского компонента внутри render есть дочерние компоненты - они будут перерисовываться. Соответственно, если мы вызываем render на родительском компоненте - перерисуем всё приложение. Чтоб решить этот вопрос - используем создание компонента от PureComponent
- Даже если я создал класс от PureComponent - это не гарантирует отсутствие лишних ренедров при тех же данных. Одна из причин - анонимные функции (они при каждом рендере новые).
- Неверно:
-
render() { return <Component onClick= {() => this.hangleClick}> }
-
- Верно:
-
handleClick = () => {...} render() { return <Component onClick= {this.hangleClick}> }
-
- То же самое с объектами - не создаём их прямо в функции, а подключаем как константу
- Неверно:
-
render(){ return <Component test= {{a: 1}}> };
-
- Верно:
-
const obj = {a:1} render() { return <Component test= {obj}> }
-
- Неверно:
- Ссылки
-
Ссылки
- Medium - 7 методов оптимизации производительности React (2023)
- Гайд по оптимизации веб- приложений в 2017
- Habr -
- Habr -
- Habr -
- Habr -
- Habr -
- CSSSR - Основы производительности React-приложений
- Habr - Оптимизация производительности в React
- Habr - Несколько способов оптимизировать React-Redux приложение
- Habr - Как организовать большое React-приложение и сделать его масштабируемым (2017)
- IT-Kamasutra #87 - shouldComponentUpdate, PureComponent, memo
- см. раздел «Redux» - Быстродействие и оптимизация Redux
- .
- Оф. документаця - Rendering
- Руководство по рендеренгу в React
- Когда React выполняет повторный рендеринг компонентов?
- Визуальное руководство по состоянию в React
Архитектура React-приложения. Ducks
-
Ducks
-
Метод организации Redux-кода в приложении.
-
Позволяет собрать код, относящийся к управлению состоянием, в одном месте.
-
Общий принцип:
- стараемся «не размазывать» Redux-логику по проекту.
- ActionTypes, actions, reduce...
- Всё что можно храним в папке компонента/фичи.
- Обычно сохраняем в одном общем файле.
- чтобы вся функциональность имеющая отношение к фиче хранился в папке фичи. Не надо прыгать по куче файлов и папок — ускоряет работу, облегчает поиск.
- чтобы можно было взять папку с фичей из одного проекта и перенести в другой. Останется только чуть-чуть подправить структуру store, всё остальное перенесётся в папке
- стараемся «не размазывать» Redux-логику по проекту.
-
ActionTypes, actions, reduce объединяются в изолированный модуль, который является автономным.
-
И даже может быть легко упакован в библиотеку.
-
-
Модуль
-
Должен
export default
функцию с названиемreducer()
-
Должен
export
своиaction creators
как функции -
Должен содержать
action types
в формеnpm-module-or-app/reducer/ACTION_TYPE
(это про именованиеaction types
) -
Может экспортировать свои
action types
как UPPER_SNAKE_CASE, если внешний редьюсер должен их прослушивать или если это опубликованная повторно используемая библиотека. -
То же самое рекомендуется для {actionType, action, reducer} пакеты, которые применяются как повторно используемые библиотеки Redux.
-
-
Ссылки
- Ducks: Redux Reducer Bundles
- Habr - Масштабирование Redux-приложения с помощью ducks (2020)
- Habr - Как организовать большое React-приложение и сделать его масштабируемым (2017)
- IT-Kamasutra - 90. Redux-ducks рефакторинг (YouTube)
- [Организация кода в масштабных React проектах.(2018)](https://blogru.4xxi.com/%D0%BE%D1%80%D0%B3%D0%B0%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-%D0%BA%D0%BE%D0%B4%D0%B0-%D0%
- Medium - Проволочки при проектировании структуры React приложения
- Medium - The Ducks File Structure for Redux (2016)
React JS и React Native
- React JS - js-библиотека для создания UI (2013)
- React Native - платформа для разработки мобильных приложений, создает мобильные приложения с помощью React.js (2015)
- Мобильные приложения имеют некоторые преимущества по сравнению с сайтами. Их можно использовать без соединения с Интернетом. Они имеют доступ к таким возможностям устройства, как всплывающие уведомления. Также они позволяют быть в контакте с вашими пользователями в режиме 24/7. React Native — это фреймворк, который позволяет вам создавать мобильные приложения, используя React. Логика приложения пишется на JavaScript, таким образом, программисту не нужно отказываться от привычных приемов веб-разработчика. Все что нужно — научиться писать специфичный для устройства код, который адаптирует компоненты, ранее созданные для веб-сайта к новой среде обитания.
- Если мы сравним затраты на разработку разных видов мобильных приложений, мы получим примерно следующие результаты:
- В случае с нативными приложениями вы можете надеяться на довольно высокую производительность, но стоимость разработки будет довольно высокой;
- Если вы предпочтете фреймворки, которые позволяют использовать HTML5, CSS3 и JavaScript, например PhoneGap, вы можете снизить стоимость. Но в этом случае уровень производительности будет гораздо ниже;
- В случае React вы можете достигнуть уровня производительности, сравнимого с нативными приложениями. При этом стоимость разработки сравнима с предыдущим примером.
- Если вы планируете создать корпоративное веб-приложение и не вполне уверены, будет ли разработка мобильной версии этого же приложения хорошей идеей, вот что вы должны помнить. React Native позволяет использовать уже имеющуюся логику веб-приложения при создании мобильного приложения. Это значит, что команда разработчиков может использовать тот же код, который был использован в процессе создания сайта вместо того, чтобы начинать с чистого листа.
- React.js использует Virtual DOM, в то время как React Native использует собственные API
- Ссылки
Ещё разное про React
- При работе с функциями, мы передаём фактическую ссылку на функцию, а не строку.
- Разобраться какая часть системы отвечает за преобразование JS в HTML? Скорее всего - браузер
-
Связывание данных
- Связывание данных - функция, которая синхронизирует данные между состоянием (моделью) приложения и представлением.
- Односторонняя привязка данных - любое изменение модели автоматически обновляет представление. Но не наоборот.
- Двусторонняя привязка данных - любое изменение модели автоматически обновляет представление. И наоборот.
- В React однонаправленная привязка - свойства передаются от родительских компонент к дочерним ("сверху вниз").
- Состояние компонента инкапсулируется и недоступно для других компонентов. Если только оно не передается дочернему компоненту в качестве props (т.е. состояние компонента становится props дочернего компонента).
- Компоненты получают свойства как неизменяемые (immutable) значения.
- Поэтому компонент не может напрямую изменять свойства, но может вызывать изменения через callback функции. Такой механизм называют «свойства вниз, события наверх».
- Связывание данных - функция, которая синхронизирует данные между состоянием (моделью) приложения и представлением.
-
Устанавливая пакет - дописывать в конце `--save`
- Означает, что нужно внести запись в package.json
- Пример:
npm install react-router-dom --save
-
В React мы никогда не лезем в DOM напрямую.
- Никаких getElementById и т.д. Мы работаем с VirtualDOm, а уже сам React занимается связкой Virtual DOM & DOM
-
Избегать циркулярных (циклических) зависимостей
-
Например:
- файл a.js импортирует в себя файл b.js,
- при этом внутри b,js есть импорт файла a.js.
- То есть фалы импортируются друг в друга.
-
Это плохой код, идущий в разрез с принципами функционального программирования.
-
Т.е не должно быть именно взаимных импортов.
-
Но, можно вызвать функцию из другого файла, и в качестве данных отдать в неё свою функцию. Т.е. использовать callback.
-
Ссылки
-
-
Избегать изменения элементов/данных по ссылке
-
Никогда не менять внешние переменные, и тем более - ничего, что приходит в props.
-
Мы не знаем где ещё используется эти данные.
-
Работать надо с локальными переменными.
-
Работать с иммутабельными данными (теми, которые не меняются по ссылке).
-
Если нужно поменять что-то - мы не меняем по ссылке локальную переменную (например массив), а создаём новый массив с нужными параметрами
-
Про JS-Объекты (и массивы). При копировании объект в памяти остаётся то же, на него просто создаётся новая ссылка.
-
Поэтому, если изменить что-то в объекте-копии, оригинальный объект тоже изменится (т.к. у нас есть только один объект, с двумя разными ссылками на него).
-
Если объект одноуровневый - можно сделать его полноценную копию так (через спред-оператор):
newObject = {...oldObject}
-
Но, если в объекте oldObject были вложенные объекты/массивы - они передадутся опять по ссылке, а не создадут полноценной копии.
-
Передача аргументов в обработчики событий
-
Внутри цикла часто нужно передать дополнительный аргумент в обработчик события.
-
Например, если id — это идентификатор строки, можно использовать следующие варианты:
<button onClick={(e) => this.deleteRow(id, e)}>Удалить строку</button>
<button onClick={this.deleteRow.bind(this, id)}>Удалить строку</button>
- Две строки выше — эквивалентны, и используют стрелочные функции и Function.prototype.bind соответственно.
-
В обоих случаях аргумент e, представляющий событие React, будет передан как второй аргумент после идентификатора.
-
Используя стрелочную функцию, необходимо передавать аргумент явно, но с bind любые последующие аргументы передаются автоматически.
-
Ссылки
-
-
Деструктуризация
- learn.javascript.ru - Деструктуризация
- Деструктуризация в ES6. Полное руководство
- Habr - Вы не знаете деструктуризацию, пока
- Medium - Learn the basics of destructuring props in React
- IT-Kamasutra #90 - Про деструктуризацию props в функциональных компонентах
- IT-Kamasutra #90 - Про деструктуризацию props в классовых компонентах
- Дэн Абрамов - Чем функциональные компоненты React отличаются от компонентов, основанных на классах? (см. про деструктуризацию props)
-
Ссылки
Ссылки
- База
- Курсы
- Reactjs.org (офф. сайт) - учебный курс
- IT-Kamasutra - курс «React JS. Путь самурая (часть 1)» (YouTube). Лучший учебный курс, 100 уроков
- IT-Kamasutra - курс «React JS. Путь самурая (часть 2)» (YouTube)
- LearnJS - Курс
- Code.mu - Курс
- Bob Ziroll (habr) - учебный курс (27 статей, ru)
- Hexlet - курсы React
- Monsters - курс
- FDS - курс (6 месяцев)
- IT-Kamasutra (YouTube)
- IT-Kamasutra - ReactJS - Путь Самурая 2.0 (YouTube, 23 видео)
- IT-Kamasutra - JS+React для Juniors (YouTube, 4 видео)
- IT-Kamasutra - React JS - путь самурая 1.0 (YouTube)
- IT-Kamasutra - GIT (YouTube, 2 видео)
- IT-Kamasutra - JS в деталях (YouTube, 48 видео)
- IT-Kamasutra - Уроки JavaScript с нуля (YouTube, 29 видео)
- IT-Kamasutra - React - Кабзда как подробно (YouTube, 3 видео)
- IT-Kamasutra - Todolist - React, TypeScript (YouTube, 1 видео)
- IT-Kamasutra - Ajax в деталях (YouTube, 9 видео)
- IT-Kamasutra - addEventListener в деталях (YouTube, 4 видео)
- IT-Kamasutra - react-четверг (YouTube, 1 видео)
- IT-Kamasutra - JavaScript для собеседований (YouTube, 1 видео)
- IT-Kamasutra - TypeScript Камасутра (YouTube, 1 видео)
- IT-Kamasutra - React English (YouTube, 1 видео)
- Best practices
- Паттерны React
- Гайд как писать на React в 2017
- 11 советов для тех, кто использует Redux при разработке React-приложений
- 9 принципов, которые должен знать новичок в React.js
- Почему не надо сохранять props в state
- Как не надо писать React: неправильные шаблоны и проблемы в React
- Яндекс - Разработка фичи: как эффективно пройти путь от идеи до реализации
- Medium - Проволочки при проектировании структуры React приложения