React Junior
207 subscribers
37 photos
462 links
Изучение React с нуля
加入频道
Атрибут key в списках

Три правила для правильной установки ключа:

👉 ключ должен быть уникальным В ПРЕДЕЛАХ МАССИВА ЭЛЕМЕНТОВ
👉 не следует использовать в качестве ключа порядковый индекс элемента в массиве
👉 ключ должен находиться именно на том элементе, который возвращается из массива, а не на его потомках

#важно #jsx #ключи #документация
Зачем в списках нужны ключи?

При выводе массива элементов в JSX React просит указать key для каждого элемента. Другими словами, для каждого элемента нужно указать УНИКАЛЬНЫЙ (в пределах массива) идентификатор. React хочет иметь возможность различать эти элементы, зачем это ему?

Для оптимизации. Если вы добавляете в массив новые элементы, не изменяя при этом другие, React хочет просто добавить один элемент в DOM, а не перерисовывать весь список. Но для этого он должен понимать, что можно оставить как есть.

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

Статья (рус): Использование индекса в качестве ключа — это анти-паттерн

#подкапотом #jsx #ключи #ссылки
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, он представляет собой обычный объект, который можно передавать как простой проп.

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