React Junior
207 subscribers
37 photos
462 links
Изучение React с нуля
加入频道
React Developer Tools

Расширение для Chrome и Firefox, которое позволяет отлаживать React-приложения.

Еще есть пакет react-devtools, чтобы отлаживать там, где нельзя установить расширение (в мобильных браузерах, Safari, внутри фреймов).

Этот инструмент отображает дерево компонентов, пропсы и состояние, так что найти причину ошибки становится намного проще.

#инструменты #отладка #документация
Работа с элементами форм

Элементы форм в HTML находятся на особом положении, потому что у них есть свое собственное состояние. Но внутри React приложения удобно это состояние контролировать, то есть указывать элементу, что отображать и перехватывать все изменения. Таким образом, все состояние приложения останется в одном месте.

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

Другими словами, мы устанавливаем значение value (для инпута), а также перехватываем события change и обновляем value собственноручно.

С элементами textarea и select работаем по тому же сценарию - value и onChange. Это довольно удобно, так как унифицирует поведение разных контролов.

Важно: как только вы установите контроль над value, контролы перестанут реагировать на ваши действия (пока вы не начнете отслеживать изменения и обновлять его вручную).

#началоработы #примерыкода #формы #документация
Обработка нескольких полей ввода

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

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

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

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

Не будем с ней спорить. Композиция - это более гибкая модель, чем наследование.

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

👉 Вставка дочерних элементов.

Используйте props.children, или другие пропсы, содержащие JSX.

👉 Специализация.

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

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

Последняя глава в разделе Основные понятия в документации называется Философия React.

👉 На примере показано, как следует делить приложение на компоненты с учетом принципа единственной ответственности (это довольно очевидно). Главное тут не бояться разделять - все может быть компонентом, даже отдельная кнопочка - для 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 #документация