React Junior
208 subscribers
37 photos
462 links
Изучение React с нуля
加入频道
Необходимые импорты

Важно: JSX - это синтаксический сахар для метода React.createElement(). Поэтому если вы используете JSX-синтаксис, React всегда должен быть в зоне видимости.

#важно #jsx #подкапотом #документация
Синтаксис расширения для передачи пропсов

Так как JSX - это обычный JS, мы можем использовать в нем синтаксис расширения для передачи пропсов как объекта.

https://codepen.io/furrycat/pen/RwpYZaz?editors=0010

Например, компонент ButtonWrapper рендерит внутри себя компонент Button. И вы хотите передать ряд свойств для компонента Button через ButtonWrapper.

Компоненту ButtonWrapper совсем необязательно знать полный список пропсов, чтобы все их передать в Button. Он просто заберет из объекта props все нужные ему свойства, а все остальное передаст в Button.

#примерыкода #jsx #документация
Фрагменты

По правилам React компонент может рендерить только один корневой элемент. Поэтому чтобы вернуть несколько элементов, их приходится оборачивать в div или другой тег. Но лишние теги - это, во-первых, некрасиво, а во-вторых, не всегда возможно. Например, если ваш компонент - это группа элементов td, которые будут вставлены в tr.

https://codepen.io/furrycat/pen/gOmdGLR?editors=0010

Для решения этой проблемы предназначены фрагменты. Это нечто вроде невидимой обертки - элемент, который не влияет на рендер.

Фрагменты можно задать двумя способами:

- Обычный синтаксис - это тег React.Fragment.
- Краткий синтаксис - это просто угловые скобки без содержания

Второй вариант красивее, но ему нельзя задать атрибуты. А React.Fragment - можно. Хотя из атрибутов фрагментам доступен только key (если вы выводите массив фрагментов).

#началоработы #фрагменты #примерыкода #документация
Рефы

Реф - это прямая ссылка на компонент или на DOM-элемент. Обычно рефы используются для прямого управления DOM-элементами. React обычно не одобряет такой подход, но в некоторых ситуациях он оправдан:

- Управление фокусом, выделение текста или воспроизведение медиа.
- Императивный вызов анимаций.
- Интеграция со сторонними DOM-библиотеками.

Реф нужно сначала создать с помощью метода React.createRef(), а потом передать в атрибут ref нужного элемента или компонента.

После рендеринга узел будет доступен в свойстве current рефа.

Соответственно, если вы установили ref для DOM элемента, то в свойстве ref.current будет ссылка на DOM-элемент со всеми его методами и свойствами. Если вы установили ref на компонент, то в ref.current будет ссылка на экземпляр компонента.

Исключение: нельзя использовать ref с функциональными компонентами, потому что у них нет экземпляров, в отличие от классовых.

Использование рефа с DOM-элементом: https://codepen.io/furrycat/pen/yLMEpVE?editors=0010

Использование рефа с классовым компонентом: https://codepen.io/furrycat/pen/MWpXrEP?editors=0010

#примерыкода #рефы #началоработы #документация
Коллбэк-рефы

Кроме объектных рефов, которые создаются функцией React.createRef(), если еще один способ задать реф - коллбэк-рефы.

https://codepen.io/furrycat/pen/ZEeRvjw?editors=0010

При этом в атрибут ref передается функция (коллбэк). Эта функция будет вызвана при монтировании компонента и получит в качестве аргумента ссылку на DOM-элемент или экземпляр React-компонента, к которому реф привязан. Внутри функции вы уже можете записать ссылку, куда необходимо.

#примерыкода #рефы #началоработы #документация
Неуправляемые компоненты

Неуправляемые компоненты, как следует из названия, не управляются React-приложением. Обычно у них есть другой источник данных, не синхронизированный с React.

Например, обычные инпуты (без привязки атрибута value и отслеживания onChange). Когда пользователь что-то вводит в инпут, React об этом не знает и, следовательно, не контролирует эти данные.

В рамках приложения React, неуправляемые компоненты - это не самая лучшая идея. Но если вы, например, вводите React в уже работающее приложение, может быть довольно сложно переписать все формы на управляемые. Поэтому иногда имеет смысл оставить все, как есть и получать данные из них по необходимости.

Для доступа к неуправляемым компонентам используются рефы.

React даже дает возможность установить значение по умолчанию, с которым он потом никак не будет взаимодействовать. Это значение должно быть указано в атрибуте defaultValue или defaultChecked (в зависимости от типа поля ввода).

Небольшая статья-сравнение управляемых и неуправляемых компонентов: Контролируемые и неконтролируемые компоненты в React не должны быть сложными

#документация #паттерны #ссылки
Загрузка файлов

Инпут с типом file - это всегда неуправляемый компонент, ведь его значение нельзя установить программно. Иначе говоря, его источником правды является не приложение React, а пользователь.

Пример из документации: https://ru.reactjs.org/redirect-to-codepen/uncontrolled-components/input-type-file

Для создания загрузчика файлов потребуется использовать реф (ссылку на DOM-элемент инпута) и File API.

#документация #паттерны #примерыкода
Взаимодействие React со сторонними библиотеками

Статья из документации (рус.): https://ru.reactjs.org/docs/integrating-with-other-libraries.html

Статья объясняет, как добавить в React-приложение плагин, написанный, например, на jQuery.

Ключевые моменты:

👉 предотвратить обновление React-компонента, оборачивающего плагин
👉 наладить связь между приложением и плагином с помощью рефов и событий
👉 удалить все обработчики плагина при размонтировании компонента

Рассматривается также противоположная задача - встраивание React-компонентов в приложение, написанное на другой технологии (Backbone).

Ключевые моменты:

👉 на странице может быть сколько угодно React-контейнеров, обрабатываемых методом ReactDOM.render()
👉 наладить связь между приложением и React-компонентом с помощью событий
👉 явно размонтировать React-компонент при удалении DOM-узла с помощью метода ReactDOM.unmountComponentAtNode()

#подключение #документация
Зачем нужны ключи. Пример с неконтролируемыми элементами

Если вы используете неконтролируемые элементы (https://yangx.top/react_junior/43), React не может полностью отвечать за их состояние. Чтобы избежать проблем, очень важно соблюдать правила, на которые полагается библиотека. Например, не использовать индексы массива в качестве ключей для элементов.

Пример из документации: https://ru.reactjs.org/redirect-to-codepen/reconciliation/index-used-as-key

В примере как раз используются индексы массива и неконтролируемые инпуты для вывода значений. Если вы добавите новый элемент в конец списка, ничего не сломается, так как индексы предыдущих элементов не поменялись.

индекс 0 - элемент с id: 1, значение 1
индекс 1 - элемент с id: 2, значение 2

Но если добавить новый элемент в начало списка, то все индексы пересчитаются.

индекс 0 - элемент с id: 3
индекс 1 - элемент с id: 1, значение 1
индекс 2 - элемент с id: 2, значение 2

Но интерфейс сломался, инпут первого элемента (индекс 0) по-прежнему отображает 1.

Причины такого поведения лежат в механизме согласования изменений, который использует React. О нем поговорим в следующем посте.

#ошибки #jsx #подкапотом #документация #ключи
Согласование

Статья в документации (рус.): https://ru.reactjs.org/docs/reconciliation.html

Статья описывает, как работает сравнение деревьев элементов для выявления изменений. React не обновляет все дерево целиком, он ищет максимально эффективный путь обновления. И для этого опирается на два предположения:

👉 Два элемента с разными типами произведут разные деревья.
👉 Разработчик может указать, какие дочерние элементы могут оставаться стабильными между разными рендерами с помощью пропа key.

Поэтому так важно правильно указывать ключи и не чередовать элементы разных типов без необходимости.

#подкапотом #документация
Компоненты высшего порядка

Компонента высшего порядка (High order component, HOC) - это паттерн для выделения повторяющейся логики и ее переиспользования. По сути это не компонент, а функция, которая возвращает компонент.

Компоненты высшего порядка не являются частью React, поэтому стандартов для их оформления нет. Но обычно эти функции принимают компонент А, добавляют к нему некоторую логику и возвращают уже новый компонент B, но который выводит внутри себя компонент A. При этом компонент B обычно не влияет на UI, то есть он только добавляет некоторую логику, но выводит тот же компонент А. В общем, это по сути декоратор.

Статья из документации (рус.): https://ru.reactjs.org/docs/higher-order-components.html

С налета понять может быть довольно сложно, поэтому лучше рассмотреть на примере простого логгера.

https://codepen.io/furrycat/pen/GRWGMOG?editors=0010

Функция Logger принимает любой другой компонент, который нужно обернуть, а затем создает компонент-обертку. Обертка не рендерит ничего лишнего, только добавляет некоторую логику - в данном случае логирует пропсы.

Если нужно, в оберточном компоненте можно объявить методы жизненного цикла, а также передать в функцию какой-нибудь конфигурационный объект.

Что тут нужно учитывать:

- HOC не должен ничего менять в том компоненте, который он оборачивает. Это должна быть чистая функция.
- HOC должен передать оборачиваемому компоненту все пропсы, так как он является промежуточным звеном и не должен ничего ломать. Для этого обычно используется синтаксис расширения (ссылка)
- HOC должен создаваться ВНЕ render-метода. Иначе при каждом рендере будет создаваться НОВЫЙ компонент, а значит, React не сможет ничего оптимизировать. Лучше всего получать HOC заранее, вне методов жизненного цикла.
- Если вы назначили компоненту высшего порядка атрибу ref, он не передастся оборачиваемому компоненту. (Решить эту проблему можно с помощью перенаправления рефов, которое мы разберем чуть позже)

#началоработы #примерыкода #hoc #паттерны #документация
Перенаправление рефов

Рефы в React позволяют получить прямую ссылку на DOM-элемент или экземпляр классового компонента. Иногда этой функциональности недостаточно.

- Например, у вас есть компонент TextInput, который рендерит поле ввода. И вы хотите из родительского компонента управлять фокусом этого поля. Но если вы установите реф на TextInput, он будет указывать именно на экземпляр компонента, а не на инпут внутри него. Перенаправление рефов позволяет пробросить реф от компонента к элементу/компоненту, который он рендерит.

https://codepen.io/furrycat/pen/NWpLwNm?editors=0010

Ref ведет себя не так, как обычные пропсы. Он привязывается к элементу, для которого указан, поэтому нам нужен метод React.forwardRef. Этот метод принимает рендер-функцию, в которую передает props и ref. Внутри рендер-функции можно вывести любую разметку и разместить ref там, где необходимо.

В некотором смысле React.forwardRef - это обертка/декоратор для функционального компонента, который добавляет в рендер-функцию второй аргумент (реф). То есть у нас есть способ привязывать рефы к функциональным компонентам.

Пошаговый алгоритм:

1. Компонент TextInput создан с помощью метода React.forwardRef. При рендеринге он получит ref и поместит его на DOM-элемент input.

2. В компоненте MyComponent создаем реф с помощью метода React.createRef и помещаем ее на TextInput.

3. В итоге в рефе оказывается ссылка на инпут.

Примечание: Реф внутри React.forwardRef, разумеется, можно привязать не только к DOM-элементу, но и к экземпляру классового компонента (как и всегда).

Важно: Не злоупотребляйте рефами. Это слишком сильно привязывает логику к DOM.

#рефы #примерыкода #документация
Рефы

Важно: До тех пор пока реф не передан в атрибут ref, он представляет собой обычный объект, который можно передавать как простой проп.

#рефы #важно #документация
Перенаправление рефов. Продолжение

Метод React.forwardRef передает в рендер-функцию ref вторым аргументом. И мы не обязаны сразу же привязывать этот реф к чему-нибудь внутри этой рендер-функции. На самом деле этот реф можно передать как обычный пропс какому-нибудь классовому компоненту, то есть записать не в атрибут ref компонента, а например в атрибут forwardedRef (название может быть любым). Таким образом реф можно пробросить на уровень вниз и использовать по назначению уже внутри классового компонента.

Такой подход применяется в компонентах высшего порядка.

Ведь если ваш компонент создан с помощью HOC и вы привязываете к нему ref, то этот реф будет указывать именно на компонент высшего порядка. Это неправильное поведение, так как HOC не должен влиять на интерфейс и ломать ссылки. Значит, здесь нам необходимо перенапраправление рефов.

Вместо того, чтобы из функции-обертки вернуть новый классовый компонент-обертку, нужно вернуть компонент, созданный с помощью метода React.forwardRef.

#документация #рефы
Альтернатива перенаправлению рефов

Ссылка на пример с описанием: https://gist.github.com/gaearon/1a018a023347fe1c2476073330cc5509

Этот подход использует возможность передавать реф как пропс по дереву компонентов.

Минус этого подхода: требуется отдельный кастомный проп, о котором знают все заинтересованные компоненты. В то время как перенаправление рефов работает с встроенным свойством библиотеки React.

#документация #рефы #примерыкода
Рендер-пропсы

Значениями рендер-пропсов являются функции. Но эти функции используются не обычным образом (например, для обработки событий), а для рендера.

В рендер-пропе в компонент передается функция. Компонент вызывает ее в нужном месте своей разметки, передавая ей при необходимости аргументы. Функция выполняется и возвращает фрагмент JSX.

По сути это почти то же самое, что и props.children - дочерние элементы компонента. Кстати, проп children можно указывать явно, и он тоже может быть функцией, то есть рендер-пропом.

Статья из документации (рус.): https://ru.reactjs.org/docs/render-props.html

#рендерпропсы #jsx #документация
Предохранители

Предохранители в React - это компоненты, которые могут отловить ошибки, произошедшие в жизненном цикле ниже лежащих (дочерних) компонентов, и вывести запасной UI (например, сообщение об ошибке). В результате этого целое приложение продолжает работать, даже если одна его часть упала с ошибкой.

Предохранители способны поймать ошибку только в СИНХРОННОМ JavaScript-коде НИЖЕЛЕЖАЩИХ компонентов (по отношению к самому предохранителю). Это значит, что они не смогут поймать ошибки в:

- асинхронном коде (setTimeout, обработчики событий, ajax-запросы)
- самом компоненте-предохранителе
- серверном рендеринге (SSR)

Чтобы компонент стал предохранителем, он должен реализовать один из следующих методов:

- статический метод getDerivatedStateFromError()
- метод экземпляра componentDidCatch()

Отсюда следует, что предохранителем может быть только классовый компонент. Оба метода будут подробнее разобраны в следующих постах.

По сути, предохранители - это декларативный аналог try-catch для компонентов.

Пример использования предохранителей из документации: https://codepen.io/gaearon/pen/wqvxGa?editors=0010

Важно: Начиная с React 16, ошибки, не отловленные ни одним из предохранителей, будут приводить к размонтированию всего дерева компонентов React. Команда проекта считает, что бОльшим злом будет вывести некорректный UI, чем удалить его целиком.

#документация #предохранители #обработкаошибок
static getDerivedStateFromError

Статический метод компонента-предохранителя. Вызывается после возникновения ошибки в каком-либо дочернем компоненте.

Входные параметры

👉 error - объект перехваченной ошибки

Выходные параметры

👉 Метод должен возвращать значение для обновления состояния компонента.

Особенности работы

👉 Метод вызывается во время этапа "рендера", поэтому в нем нельзя вызывать побочные эффекты.

Типичные случаи использования

👍 Изменение состояния для рендера запасного интерфейса (сообщение об ошибке).
👎 Для выполнения побочных эффектов следует использовать метод экземпляра componentDidCatch().

#компоненты #жизненныйциклкомпонента #документация
componentDidCatch

Метод компонента-предохранителя. Вызывается после возникновения ошибки в каком-либо дочернем компоненте.

Входные параметры

👉 error - объект перехваченной ошибки
👉 info - объект с информацией о компоненте, в котором произошла ошибка

Особенности работы

👉 Метод вызывается во время этапа фиксации, поэтому внутри него можно вызывать побочные эффекты.
👉 В дев-режиме ошибки (даже обработанные методом componentDidCatch) всплывают наверх до объекта window. Поэтому можно их перехватывать с помощью обработчика window.onerror. На продакшене этого не происходит, обработанные ошибки дальше не всплывают.

Типичные случаи использования

👍 Логирование ошибок.
👎 Для изменения состояния компонента (например, для рендера резервного интерфейса - вывод сообщения об ошибке) предпочтительнее использовать статический метод getDerivedStateFromError()

#компоненты #жизненныйциклкомпонента #документация
Функция как дочерний компонент

Мы уже говорили про рендер-пропсы. Они позволяют передать в компонент рендер-функцию, которая просто вставляется в нужном месте и генерирует разметку.

Тут можно немного затормозить, поэтому захотелось вернуться к этому моменту. На самом деле ничего странного в этом нет. Помним, что JSX - это обычный JavaScript, поэтому в ней можно использовать любые валидные выражения, включая также и вызовы функций.

https://codepen.io/furrycat/pen/oNZQmgm?editors=0010

Вы помещаете вызов функции внутри фигурных скобок. Функция выполняется, возвращает разметку, которая и выводится.

Это позволяет передавать в разметку динамические аргументы.

#примерыкода #jsx #началоработы
Контекст

Контекст - это "глобальные переменные" React.

По умолчанию, чтобы глубоко дочерний компонент получил какую-то важную информацию, определенную на верхнем уровне, ее нужно пробросить до него по цепочке пропсов. Но иногда это излишне, особенно если информация нужна буквально каждому компоненту в приложении - надоест пробрасывать.

Яркий пример таких данных - цветовая тема приложения или выбранный язык (локализация).

Важно: применение контекста усложняет повторное использование компонентов, поэтому не стоит им злоупотреблять. Иногда его лучше заменить старой-доброй композицией компонентов или рендер-пропсами.

https://codepen.io/furrycat/pen/NWpEeox?editors=0010

Использование контекста

1. Создать объект контекста с помощью метода React.createContext(defaultValue)
2. Создать провайдер для этого контекста - компонент MyContext.Provider. Все его потомки смогут подписаться на этот контекст (стать потребителями).
3. Передать значение через проп value провайдера. Когда проп изменится, все потребители контекста будут перерендерены.
4. Подписать компонент-потребитель на контекст, указав объект контекста в статическом свойстве класса .contextType.
4. Получить актуальное значение контекста в компоненте-потребителе из свойства this.context.

#контекст #примерыкода #документация