Для чего нужен react fragment

Анатомия React для начинающих. Урок 10. Контекст, фрагменты и порталы

Сейчас мы разберем контекст, фрагменты и порталы в Реакте и поймем зачем все это нужно и вообще может ли быть полезно.

КОНТЕКСТ

Итак, контекст. Это очень полезная вещь, когда нам нужно прокинуть пропсы через несколько промежуточных компонентов вниз. Рассмотрим такой пример:

Здесь сверстан пример некой админки, где есть хедер, часть с основным контентом и сайдбар. Все это оформлено в три отдельных презентационных компонента.

Все они помещены в компонент App, где превращаются в макет с помощью CSS Grid свойств для дива с классом appWrapper. Всю стилизацию вы сможете посмотреть потом сами, я на этом акцентироваться не буду.

Также, у нас есть компонент Button — кнопка, которая умеет применять цвет, пришедший ей сверху.

Можно видеть что все в App обернуто в ThemeContext.Provider. Это и есть контекст, который будет пробрасываться во все компоненты, лежащие внутри App на любом уровне вложенности.

Компонент-контекст создается выше, путем вызова метода React.createContext(). В скобках же задается некоторое значение по умолчанию. Это может быть строка, число, объект.. В зависимости от того, сколько данных вам нужно передать через контекст вниз. Можно инициализировать пустым значением, то есть в скобочки не передавать ничего.

В нашем случае, контекст будет передавать значение основного цвета темы оформления приложения и будет влиять на кнопки на всех уровнях вложенности. Как видно, они берут цвет фона как раз из контекста.

Получается что как бы глубоко в дереве компонентов кнопка не находилась, цвет она все равно получит через контекст. В промежуточных же компонентах в пропсах нигде этот цвет не фигурирует. То есть Реакт пробрасывает его через компоненты автоматически. И это очень удобно.

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

Итак, после создания самого контекста, мы оборачиваем в него внутренности нашего приложения и для атрибута value задаем то, что контекст будет передавать. У нас это объект с полем themeColor и значением цвета.

Так как мы хотим использовать контекст у кнопки, присваиваем ей статическое свойство contextType и указываем на ThemeContext, созданный выше. И после этого, обращаясь к this.context, мы можем вытащить цвет и присвоить его кнопке.

Давайте для доказательства того, что это работает, изменю цвет в контексте.. Работает.

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

Меняем контекст из вложенных компонентов

А теперь давайте сделаем так, чтобы можно было менять цвет темы из сайдбара. То есть нам нужно будет менять контекст. Переделанный код выглядит следующим образом:

Мы завели в App стейт со значением цвета, а также метод с setState, который умеет значение цвета менять.
В контекст мы теперь передаем объект, где цвет берется из стейта, а также есть еще и ссылка на тот метод из класса App, что умеет менять цвет. Так мы сможем его вызывать из компонентов ниже, передавая новый цвет в качестве аргумента.

Header и Main остаются теми же самыми. А вот класс Sidebar мы превращаем в компонент-контейнер и всю разметку помещаем в метод render. Там мы добавили виджет для смены цвета с подписью, полем ввода и кнопкой. В компонент кнопки передаем пропс clickHandler, который обрабатывается соответствующим методом выше. Там мы берем значение из input’а. Однако чтобы это работало, нужно из обработчика кнопки взять значение инпута, но event-объект указывает совсем не на нужный нам инпут. Эту проблему легко решить, используя ref’ы, которые мы еще не разбирали. Однако с ними все просто. В конструкторе создаем поле метода с подходящим названием — в нашем случае пусть будет просто input и туда поместится результат вызова функции React.createRef(), а в render просто для пропса ref задаем этот this.input. Готово, мы теперь можем из любого метода компонента обращатся к инпуту и брать его значение.

Так вот, в обработчике кнопки сохраняем значение, затем обнуляем поле ввода и сохраненное значение подвергаем проверке на регулярное выражение вот такого формата. Оно говорит, что если ввод не соответствует формату из шести символов, где могут фигурировать только цифры от 0 до 9 и буквы от А до F, и который предваряется решеткой, то мы просто выходим из функции. Иначе же вызываем из того самого конекста метод для смены цвета, определенный в App. Он меняет стейт там на введенный цвет и это вызывает перерисовку. Поэтому наша тема и меняет цвет. В частности, кнопки.

Еще замечу, что конекст в Sidebar работает, потому что я добавил static-поле contextType по аналогии с Button.
Ну и последнее здесь это обработчик клика, который пробрасывается в компонент Button. Я переписал сам Button так, чтобы этот обработчик навешивался напосредственно на элемент button, взяв его из переданного пропса clickHandler. Если он там определен, то навешиваем его, в противном же случае это будет пустая стрелочная функция в качестве болванки.

Разбиваем код на отдельные файлы

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

Дело в том, что как только мы это сделаем, контекст больше не будет доступен для всех компонентов и придется переделать некоторые вещи. А именно — экспортировать Consumer, то есть потребитель контекста из App, чтобы иметь возможность его импортировать в любом нужном компоненте. Provider, как вы наверное уже догадались, это тот, кто контекст предоставляет.

App.js:

Создание контекста находится в App. При этом, вот этой строкой мы извлекаем из контекста отдельно провайдер и отдельно консьюмер, деструктурируя их. Ранее мы сохраняли все в переменную ThemeContext и брали оттуда компонент провайдера через точку. Теперь же оборачиваем наш App в Provider, по-прежнему отправляя туда значение из стейта и метод для его смены из нижележащих компонентов.

Внизу App кроме экспорта по умолчанию, мы делаем еще и недефолтный экспорт где указываем Consumer. Теперь пойдем в компонент Button, который через контекст считывает цвет.

Button.js:

Статическое поле теперь удалено, а вся прошлая разметка обернута в компонент Consumer, который мы вот таким образом импортировали из App.

Внутри Consumer у нас функция, которая получает аргументом весь контекст. Тут я для удобства сразу извлек оттуда themeColor с цветом. Хотя можно было записать и так..

Далее я возвращаю ту разметку, что была ранее для кнопки, поменяв this.context.themeColor на просто themeColor, чтобы взять его из этого замыкания. И это работает.

Sidebar.js:

С Sidebar чуть сложнее. Static-поле также исчезло, а вся разметка обернута в Consumer, импортированный из App. Деструктурируем метод changeThemeColor из контекста, а в обработчике клика у нас стрелочная функция, которая получает event-объект. При клике эта функция выполняется и дергается уже метод handleChangeColor, куда передается и event-объект и метод handleChangeColor, которым мы и пользуемся.

Как видно, все это вполне работает. Consumer создается здесь один раз, а при импорте в других компонентах на него делается ссылка. Поэтому сколько бы раз мы в разных компонентах не импортировали Consumer, это будет ссылка на один и тот же объект. Так это работает. Этот механизм распространяется и на все остальные подобные случаи.

Уменьшаем и упрощаем пример

Однако, проанализировав этот код спустя какое-то время после подготовки материала данного урока, я понял, что можно переписать его намного оптимальнее, если не избавляться от static-полей contextType. И дело вот в чем. В React версии 16.6 к новому механизму конекстов, который был представлен уже в версии 16.3, добавилось это самое static-поле contextType. Оно позволяет на заворачивать наш компонент в компонент с контекстом и не добавлять лишней вложенности за счет вот этой функции внутри.

Я переделал предыдущий пример и теперь все работает вот так. Создание нашего контекста вынесено в отдельный файл — ThemeContext.js, который мы из него по дефолту экспортируем.

ThemeContext.js:

Затем импортируем его а App.js и называем ThemeContext.Provider. Создание контекста из App.js, разумеется, теперь убрано. А в Button.js и Sidebar.js мы эти контексты импортируем, но лишь для того, чтобы в классе компонента задать статическое свойство contextType, которому будет присвоено значение нашего ThemeContext. Это простое действие позволяет нам как внутри JSX-разметки, так и в методах класса, обращаться к контексту через this.context.

App.js:

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

Button.js:

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

Sidebar.js:

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

Кроме того, если есть необходимость, то можно в одном компоненте потреблять несколько контекстов, просто заворачивая один контекст в другой подобным образом…

И это будет работать за счет механизма замыканий в JavaScript.

Старый механизм контекстов

В завершение темы о контекстах, давайте рассмотрим как работал старый механизм контекстов. Ссылка на документацию к нему находится внизу раздела с описанием нового варианта.

Это стоит рассмотреть, потому что много библиотек используют именно его и, по моему мнению, он местами даже был удобнее и проще. Поддержка старого варианта будет осуществляться на протяжении релизов 16-й мажорной версии Реакта. На момент выпуска этого ролика, актуальной была версия 16.6.0.

Разберем старые контексты прямо по докам.

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

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

Здесь мы для компонента MessageList cоздаем метод getChildContext, где возвращаем объект с тем, что станет нашим контекстом. А затем добавляем статическое поле childContextTypes, где говорим, что color должен быть строкой.

Теперь в Message нет никаких промежуточных пропсов.

А вот в Button мы контекст считываем, просто добавив статическое поле contextTypes и написав там то же самое, что и в MessageList. И в итоге используем значение из контекста, обращаясь к нему через this.context.

Контекст также пробрасывается в методы жизненного цикла дополнительным аргументом. Вот тут его можно видеть:

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

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

ФРАГМЕНТЫ

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

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

В доках простой пример на эту тему:

Рендерится таблица, в строке которой находится компонент, который возвращает ее ячейки:

Поэтому return компонента Columns оборачиваем в компонент React.Fragment:

и это дает в итоге в DOM‘е то что нужно:

Есть и короткий синтаксис фрагмента в виде пустых угловых скобок, однако нужно удостовериться что ваша версия Babel равна 7-й и выше. Иначе не заработает.

Fragment поддерживает только специальный атрибут key, и пока только его. Позже обещают добавить и поддержку произвольных пропсов.

В принципе с фрагментами все.

ПОРТАЛЫ

И последняя для этого урока функция React’а — порталы. Звучит очень круто, хотя по сути все намного менее фантастично, хотя и удобно.

Идея порталов в том, чтобы рисовать компонент где-то внутри ReactDOM-дерева, но в реальном DOM-дереве разметка этого компонента размещается внутри ноды, указанной как точка назначения портала. Причем они могут быть в совершенно разных ветвях этого дерева.

Это нужно для того, чтобы например, прорвать контейнер с overflow: hidden для показа подсказки или чего-то еще. Или, чтобы отрисовать компонент Реакта вне root-ноды. Бывает так, что Реакт-приложение является частью статической страницы. В общем, применения этому можно придумать и самостоятельно. Главное поймите суть и когда надо, оно у вас всплывет в виде решения той или иной проблемы.

Я подготовил пример (https://github.com/makewebme/course_react_beginner/tree/master/lesson-10/05-portals) — это некий интернет-магазин с товарами, которые имеют картинки разного размера внутри контейнера с overflow: hidden для того, чтобы можно было показывать изображения разных размеров и чтобы при этом все товары имели всегда предсказуемые размеры.

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

App.js:

Item.js:

В Item.js имется div c классом item у которого высота ограничена 250 пикселами и имеется CSS-свойство overflow: hidden, чтобы ничего за его границы не вылезало. Картинка же внутри него спозиционирована абсолютно, выровнена по центру и растягивается на 100 процентов ширины. Пропорции сохраняются, так как высоту img мы не трогаем.

Description.js:

Компонент Description мы показываем только тогда, когда в стейте descriptionVisible равен true. Внутрь Description пропсами передаются деструктурированные price и desc.

По структуре компонентов React выходит, что Description находится внутри Item. Но в реальном DOM будет иначе, потому что внутри Description вместо простого возврата JSX-разметки из render, вызываем функцию createPortal из ReactDOM. Первый аргумент это разметка, а второй — заселекченная из DOM-дерева нода. Причем прописана она должна быть в HTML-файле в папке public. У меня это div#itemDescription. Именно туда поместится разметка. Вне React-приложения.

В моем случае, необходимо этот элемент спозиционировать так, чтобы описание оказалось прямо под товаром, на который наведен курсор мыши. Для этого я использую синтетические события React — onMouseEnter и onMouseLeave.

По их возникновению я вызываю один и тот же метод компонента — toggleDescription — которому с помощью джаваскриптового bind привязываю первым аргументом true для onMouseEnter и false для onMouseLeave. Первым аргументом в bind передается значение this. Я оставляю его тем что и был раньше — указателем на сам компонент.

За счет передачи true/false в аргументе visibility у меня будет true в одном случае и false в другом. И я всегда знаю что сейчас нужно сделать — показать подсказку или скрыть ее. Это значение мы «сет-cтейтим» в descriptionVisible, что вызовет перерисовку Item и подстановку разметки в портал.

Далее мы селектим ноду на которую указывает портал и сохраняем в desc. После, с помощью findDOMNode из ReactDOM берем DOM-ноду, корневую для нашего компонента Item и вызываем JS-метод getBoundingClientRect. Это дает нам объект, из которого мы извлекаем абсолютные координаты этой ноды от верхнего левого угла документа и ее размеры.

И проверка — если visibility равно true, значит мы показываем Description и нужно подставить координаты. Делаем это обращаясь к стилям ноды напрямую. В обратных скобках интерполируем значения координат и размеров.

Для left это смещение по оси x, для top — смещение по оси y плюс высота Item‘а. Ширина будет равна ширине Item‘а.
Ну и добавляем класс visible, потому что изначально у нас Description скрыт через display: none.

В противном же случае, мы удаляем класс visible и подсказка исчезает.

Если бы порталы мы не использовали, то было бы невозможно вытащить компонент Description за пределы Item‘а для которого установлено свойство overflow: hidden. А порталы решают эту проблему, как вы уже поняли.

Вот и все. Надеюсь идея порталов вам ясна. Кстати, такие вот красивые бэкграунды на чистом CSS, можно найти на сайте http://lea.verou.me/. Рекомендую.

Заключение

На этом данный урок подошел к концу. Надеюсь было полезно.

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

Удачи и в следующем уроке мы продолжим изучать фишки Реакта.

Персональные уроки по скайпу от MakeWeb

Avocode — замена Photoshop для верстальщика

При регистрации используй инвайт «nikita» и получи скидку 10% на первую покупку!

Источник

Фрагменты в React

Для чего нужен react fragment. Смотреть фото Для чего нужен react fragment. Смотреть картинку Для чего нужен react fragment. Картинка про Для чего нужен react fragment. Фото Для чего нужен react fragment

Feb 19, 2019 · 4 min read

Для чего нужен react fragment. Смотреть фото Для чего нужен react fragment. Смотреть картинку Для чего нужен react fragment. Картинка про Для чего нужен react fragment. Фото Для чего нужен react fragment

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

Постановка задачи

Попробуйте визуализировать несколько компонентов React подобным образом:

или создать подобный список узлов:

При использовании VS Code с расширениями поддержки JSX, появится предупреждение: “Родительские выражения JSX должны иметь один родительский элемент”.

Чтобы предупреждение исчезло, нужно добавить дополнительный тег div в качестве родительского элемента в разметку JSX.

Проблема заключается в том, что использовать теги div немного неудобно. В HTML встречаются случаи, когда дополнительный div может деструктурировать DOM. Например, при использовании таблицы в компонентах.

Нам нужно визуализировать данные пользователей в табличной форме с помощью HTML-элемента table. Нужно визуализировать следующий код в React:

Создадим компоненты для визуализации каждого аспекта элемента table. HeaderComponent отобразит заголовок таблицы, BodyComponent — тело таблицы. TableComponent визуализирует каркас таблицы с HeaderComponent и BodyComponent таким образом:

HeaderComponent должен выглядеть так:

А BodyComponent выглядит вот так:

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

Затем выполняем подобные действия, чтобы устранить предупреждение:

Разметка вложена в тег div. Теперь на выходе компонент Table будет выглядеть так:

Выше приведен неправильный вывод элемента table. Элемент div не должен быть отображен. Компоненты React предназначены для возврата элементов, но они должны быть заключены в родительский тег, несколько элементов не могут быть возвращены. Однако добавление дополнительного узла иногда приводит к неправильному форматированию html-элемента output, как показано выше.

Как решить эту проблему? Каким образом можно возвратить заключенные элементы JSX, не влияя на визуализированный вывод в DOM?

Фрагменты React способны решить эту проблему!

Решение — Фрагменты

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

Приступим к решению первой проблемы:

Теперь выполним вложение:

Перейдем ко второму примеру:

Удаляем теги div и добавляем React.Fragment вместо них:

Переходим к третьему примеру и заменяем дополнительные теги div в BodyComponent и HeaderComponent на React.Fragment:

Таблица будет отображена следующим образом:

Источник

React v16.2.0: Improved Support for Fragments

React 16.2 is now available! The biggest addition is improved support for returning multiple children from a component’s render method. We call this feature fragments:

Fragments look like empty JSX tags. They let you group a list of children without adding extra nodes to the DOM:

This exciting new feature is made possible by additions to both React and JSX.

What Are Fragments?

A common pattern is for a component to return a list of children. Take this example HTML:

Prior to version 16, the only way to achieve this in React was by wrapping the children in an extra element, usually a div or span :

To address this limitation, React 16.0 added support for returning an array of elements from a component’s render method. Instead of wrapping the children in a DOM element, you can put them into an array:

However, this has some confusing differences from normal JSX:

To provide a more consistent authoring experience for fragments, React now provides a first-class Fragment component that can be used in place of arrays.

You can use the same way you’d use any other element, without changing the way you write JSX. No commas, no keys, no quotes.

The Fragment component is available on the main React object:

JSX Fragment Syntax

Fragments are a common pattern in our codebases at Facebook. We anticipate they’ll be widely adopted by other teams, too. To make the authoring experience as convenient as possible, we’re adding syntactical support for fragments to JSX:

In React, this desugars to a element, as in the example from the previous section. (Non-React frameworks that use JSX may compile to something different.)

Fragment syntax in JSX was inspired by prior art such as the XMLList() <> constructor in E4X. Using a pair of empty tags is meant to represent the idea it won’t add an actual element to the DOM.

Note that the <> syntax does not accept attributes, including keys.

If you need a keyed fragment, you can use directly. A use case for this is mapping a collection to an array of fragments — for example, to create a description list:

You can experiment with JSX fragment syntax with this CodePen.

Support for Fragment Syntax

Support for fragment syntax in JSX will vary depending on the tools you use to build your app. Please be patient as the JSX community works to adopt the new syntax. We’ve been working closely with maintainers of the most popular projects:

Experimental support for fragment syntax will be added to Create React App within the next few days. A stable release may take a bit longer as we await adoption by upstream projects.

Support for JSX fragments is available in Babel v7.0.0-beta.31 and above! If you are already on Babel 7, simply update to the latest Babel and plugin transform:

Or if you are using the react preset:

Note that Babel 7 is technically still in beta, but a stable release is coming soon.

Unfortunately, support for Babel 6.x is not available, and there are currently no plans to backport.

Babel with Webpack (babel-loader)

If you are using Babel with Webpack, no additional steps are needed because babel-loader will use your peer-installed version of Babel.

Babel with Other Frameworks

If you use JSX with a non-React framework like Inferno or Preact, there is a pragma option available in babel-plugin-transform-react-jsx that configures the Babel compiler to de-sugar the <> syntax to a custom identifier.

TypeScript has full support for fragment syntax! Please upgrade to version 2.6.2. (Note that this is important even if you are already on version 2.6.1, since support was added as patch release in 2.6.2.)

Upgrade to the latest TypeScript with the command:

Flow support for JSX fragments is available starting in version 0.59! Simply run

to update Flow to the latest version.

Prettier added support for fragments in their 1.9 release.

JSX Fragments are supported by ESLint 3.x when it is used together with babel-eslint:

or if you already have it, then upgrade:

Note that babel-eslint is not officially supported by ESLint. We’ll be looking into adding support for fragments to ESLint 4.x itself in the coming weeks (see issue #9662).

It may take a while for fragment syntax to be supported in your text editor. Please be patient as the community works to adopt the latest changes. In the meantime, you may see errors or inconsistent highlighting if your editor does not yet support fragment syntax. Generally, these errors can be safely ignored.

TypeScript Editor Support

If you’re a TypeScript user — great news! Editor support for JSX fragments is already available in Visual Studio 2015, Visual Studio 2017, Visual Studio Code and Sublime Text via Package Control.

For other tools, please check with the corresponding documentation to check if there is support available. However, if you’re blocked by your tooling, you can always start with using the component and perform a codemod later to replace it with the shorthand syntax when the appropriate support is available.

React v16.2.0 is available on the npm registry.

To install React 16 with Yarn, run:

To install React 16 with npm, run:

We also provide UMD builds of React via a CDN:

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *