что такое пропсы react
2.5 Компоненты и свойства
Компоненты позволяют разделить UI на независимые, повторно используемые части и работать с каждой из них отдельно.
Концептуально, компоненты похожи на JavaScript-функции. Они принимают произвольные данные (называемые props) и возвращают React-элементы, которые описывают то, что должно появиться на экране.
2.5.1 Компоненты-функции и компоненты-классы
Самый простой способ объявить компонент – это написать JavaScript-функцию:
Эта функция является корректным React-компонентом, потому что она принимает единственный объект props с данными в качестве аргумента и возвращает React-элемент. Такие компоненты называются «функциональными», так как они и есть JavaScript-функции.
Компонент можно объявить другим способом. Для этого нужно использовать ES6-класс:
Два приведенных выше компонента являются эквивалентными с точки зрения React. Но пока мы будем использовать функциональные компоненты, так как они короче.
2.5.2 Отрисовка компонентов
Ранее, мы наталкивались лишь на React-элементы, которые представляли собой DOM-теги:
Тем не менее, элементы могут быть представлены пользовательскими(кастомными) компонентами:
Например, этот код отрисовывает на странице «Hello, Sara»:
Давайте прорезюмируем то, что произошло в этом примере:
Hello, Sara
Hello, Sara
Всегда именуйте свои компоненты с большой буквы.
Например,
2.5.3 Композиция компонентов
Компоненты могут ссылаться на другие компоненты в своём выводе (результате отрисовки). Это позволяет нам использовать ту же самую абстракцию компонента для любого уровня детализации. Кнопка, форма, диалог, экран: в React-приложении все эти сущности выражены компонентами.
Как правило, новые React-приложения имеют единственный компонент App на самом верху иерархии. Тем не менее, если вы интегрируете React в уже существующее приложение, вы можете начать снизу вверх с маленького компонента, такого как Button и постепенно двигаться вверх по иерархии отображения.
2.5.4 Извлечение компонентов
Не бойтесь разделять компоненты на более мелкие компоненты.
Рассмотрим пример с компонентом Comment :
Он принимает author (объект), text (строка) и date (дата) как свойства, и описывает комментарий на социальном веб-сайте.
Данный компонент довольно сложно изменить из-за его вложенности. Также тяжело повторно использовать и его составные части. Давайте извлечем из него несколько небольших компонентов, упростив исходный компонент.
Для начала давайте извлечем из него компонент Avatar :
Мы рекомендуем именовать props с точки зрения компонента, а не контекста, в котором он будет использован.
Сейчас мы можем немного упростить компонент Comment :
Это позволяет нам еще больше упростить компонент Comment :
Извлечение компонентов по началу может показаться рутинной работой. Однако набор универсальных, переиспользуемых, отлаженных компонентов с лихвой окупит все усилия в больших приложениях, экономя массу времени.
2.5.4 Свойства props – только для чтения
Такие функции называются «чистыми». Потому что они не изменяют свои аргументы и всегда возвращают одинаковый результат для одних и тех же аргументов.
В противоположность им, следующая функция не является чистой, потому что она изменяет свои аргументы:
React является очень гибким, но он имеет одно строгое правило:
Конечно, UI приложения – динамический и изменяется со временем. В следующем разделе мы познакомимся с новой концепцией «состояния». Состояние позволяет React-компонентам изменять их вывод со временем в ответ на действия пользователя, ответы сети или что-то другое, не нарушая данное правило.
React Props: основы
Вы когда-нибудь видели слово «реквизит» в React, возможно, переданное в функцию? Вы когда-нибудь задумывались, что это значит? Из-за неотъемлемой важности этого объекта в приложениях React важно понимать, почему и как используются свойства и состояние.
Если вас смущает реквизит, вы не одиноки! Многие из нас просто привыкли видеть, что реквизит передаётся в определённые моменты, и почти подсознательно соглашаются с этим. Надеюсь, это базовое руководство поможет прояснить, почему и как используются реквизиты React.
Что такое React Props?
Свойства React («свойства») используются как способ передачи данных от одного компонента (родительский компонент) к другому (дочернему компоненту). Свойства неизменяемы, что означает, что вы не можете изменить свойства, передаваемые компоненту изнутри компонента. Компоненты получают данные от других компонентов с помощью свойств. Реквизиты, полученные от другого компонента, не могут быть изменены.
Синтаксис реквизита
Вот базовый пример файла index.js, показывающий реквизиты, передаваемые в качестве аргумента:
// Основные операторы импорта React
import React from ‘react’;
import ReactDOM from ‘react-dom’;
// Компонент фильма с функцией рендеринга
class Movie extends React.Component <
render() <
return
Tonight we’ll watch !
Обратите внимание, что мы ещё не присвоили свойству title значение. Мы сделаем это в следующей строке:
// имя постоянной переменной «myelement»
Здесь мы просим наш компонент «Movie» отобразить (с помощью ) со значением «Casablanca», присвоенным свойству с именем «title».
// Оператор React для рендеринга «myelement» в HTML-элемент с идентификатором «root».
«Сегодня вечером мы посмотрим Касабланку!» будет отображаться.
Отправка Props в конструктор и в super ()
Возможно, вы видели реквизиты, передаваемые в функции конструктора, а также в super (), и были очень (каламбур) не понимали, почему. Вот упрощённый пример:
class Computer extends React.Component <
constructor(props) <
super(props); //we passed in props to super()
console.log(props);
console.log(this.props);
>
Почему мы вызываем super и зачем передавать ему или конструктору реквизиты? Super относится к конструктору родительского класса. Если у вас был значительный опыт программирования, вы уже имели дело с конструкторами. Наш компонент класса «Компьютер» расширяется (другими словами, он построен с использованием) базового компонента React (как вы можете видеть в первой строке).
Итак, выше мы инициализируем this.props компонента React. Если бы мы не передали props в super (), последний оператор console.log вернулся бы как undefined. По сути, внутри конструктора вы не можете использовать this, если не используете super (props). Если бы этот последний оператор console.log был написан до super (props), он не работал бы.
Даже если вы не вызовете super (), вы все равно сможете получить доступ к this.props в других частях вашего компонента. Это связано с тем, что за кулисами React назначает this.props после запуска конструктора. Проблема в том, что вы хотите вызвать метод из самого конструктора. Кодеры включают super (props) для удобства сопровождения кода. Ваш код может измениться в будущем, и вы должны быть готовы к этому. Давайте посмотрим на следующий код, в котором у нас есть компонент класса «Человек» со свойством («имя»):
Добавим класс GoodFriend, расширяющий Friend:
class GoodFriend extends Friend <
constructor(name) <
this.complimentYou(); //you shouldn’t do this before super()!
super(name);
>
// определение функции «комплиментYou», которую мы вызвали выше:
complimentYou() <
alert(‘You look nice today’); // an alert that shows up on your screen
>
>
Вышеуказанное работает нормально. Однако, если позже вы захотите использовать имя и добавить
Способы передачи данных между компонентами в React
Авторизуйтесь
Способы передачи данных между компонентами в React
В React есть несколько способов передачи данных между компонентами: Render props / props, Context, React-Redux / Redux.
В этой статье вы узнаете об этих способах подробно. В каждом из них вы научитесь добиваться двух вариантов взаимодействия:
Render props / props
Один из самых простых способов передачи данных в компоненты — это props (свойства).
Что такое prop?
Как известно, разметка в компонентах рендерится так:
Это отобразится как C component
C фактически отображается в P, так как P является его родительским компонентом. Кроме того, P может отобразить C следующим образом:
P добавляет дополнительные свойства в тег элемента C. Это очень похоже на
В случае выше будет создан HTMLDivElement :
Имя атрибута будет установлено в объект props вот так:
и передастся классу компонента C с помощью React.
React основан на JSX, поэтому разметка в P будет скомпилирована в таком виде:
Первый параметр — это элемент для рендеринга, а второй содержит атрибуты элемента (в виде объектного литерала). CreateElement возвращает литерал, содержащий первый и второй параметры.
При рендеринге React проверяет, является ли этот литерал на самом деле элементом или же классом. Если это класс, React создаёт его экземпляр, используя ключевое слово new, затем передает этот экземпляр в объект props и вызывает метод render.
Приведённый выше код показывает, что фактически делает React для рендеринга компонента или элемента.
Примечание: фрагмент выше не полный, он нужен, чтобы продемонстрировать, как компоненты получают аргумент props.
Таким образом P удаётся отправить данные в C.
Но как C теперь может отправлять данные обратно в родительский класс P? Это делается с помощью render props.
Что такое render props? Это концепция, посредством которой функция может быть передана дочерним компонентам в качестве свойства (props). Строка, объект, число или массив могут быть переданы через props дочерним компонентам. Это реализовано следующим образом:
Здесь функция () =>
Итак, теперь вы знаете, что такое render props (или свойства для рендеринга). Давайте посмотрим, как дочерние компоненты могут использовать их для связи с родительскими.
Что происходит в этом фрагменте? У компонента P метод вывода связан с его экземпляром. bind(this) говорит JS запустить функцию внутри области видимости класса, объекта или функции. Вывод здесь выполняется за пределами класса P, но он по-прежнему может ссылаться на все свойства и методы, определённые в P.
Таким образом, метод вывода передаётся в компонент класса C, чтобы получить аргумент props через свойство func.
Теперь C успешно связался со своим родителем P. Можно изменить код, чтобы C отправлял данные в P.
Как вы видите, значение P отправилось из его дочернего элемента C.
Props и render props — это одно и то же, но они имеют разные концепции. Render props в основном используется для разделения логики рендеринга между компонентами. Но в данном случае этот метод был использован для передачи данных от дочернего компонента к родительскому.
Context
Для большинства разработчиков передача props глубоко вложенным компонентам в дереве была бы утомительной. React предоставляет способ определять глобальные данные в одном месте и получать к ним доступ из любого места в приложении.
Context (контекст) как раз используется в React для обмена данными между глубоко вложенными компонентами.
Чтобы сообщить React о намерении передать context от родительского компонента остальным его дочерним элементам, нужно определить два атрибута в родительском классе:
Родительский компонент определяет context, который React использует для передачи данных глобально своим дочерним элементам.
Как теперь сделать так, чтобы дочерние элементы могли передавать данные своим родителям?
Дочерние компоненты могут изменять значение context, который будет отражаться во всех вложенных компонентах, включая и родительский компонент.
React-Redux/Redux
Это своего рода “ремейк” способа Context в React. Redux — это простая библиотека управления свойствами приложения, которая позволяет легко хранить эти свойства в одном месте и изменять их из любого места.
React-Redux был создан, чтобы сделать Redux легко совместимым с приложениями React.
Поскольку состояние определяется в одном месте, родительские компоненты могут устанавливать или обновлять его значение, а дочерние могут это фиксировать: связь Родитель-Наследник.
Аналогично дочерние компоненты могут устанавливать или обновлять значение состояния в хранилище, и оно также будет фиксироваться родительскими компонентами: связь Наследник-Родитель.
Заключение
В этом материале вы увидели, как происходит взаимодействие между компонентами React и какими способами можно этого достичь:
В первом разделе вы увидели использование props и render props, они работают с помощью HTML-подобных атрибутов. Context использует центральное хранилище, которое родитель и потомки могут как читать, так и обновлять. React-Redux и Redux делают то же самое, что и Context, компоненты выполняют чтение/запись из центрального хранилища.
Для дальнейшего освоения React вам может быть полезен данный материал.
Компоненты и свойства
Компоненты позволяют разделить пользовательский интерфейс на независимые, повторно используемые части и работать с каждой из частей отдельно. На этой странице представлено введение в идею компонентов. Здесь вы можете найти подробный справочник API по компоненту.
Концептуально компоненты похожи на функции JavaScript. Они принимают произвольные входные данные (называемые «props» или свойствами) и возвращают React-элементы, описывающие, что должно появиться на экране.
Функциональные и классовые компоненты
Самый простой способ определить компонент — написать JavaScript-функцию:
Данная функция — корректный компонент React, потому что он принимает один аргумент-объект «props» (который обозначает свойства) с данными и возвращает элемент React. Такие компоненты мы называем «функциональными», потому что они являются буквально функциями JavaScript.
Вы также можете использовать класс из ES6 для определения компонента:
Два вышеуказанных компонента эквивалентны с точки зрения React.
У классов есть дополнительные возможности, которые мы обсудим в следующих разделах. До этого момента мы будем использовать функциональные компоненты из-за их краткости.
Раньше мы сталкивались только с элементами React, представляющие DOM-теги:
Однако элементы также могут быть пользовательскими компонентами:
Когда React видит элемент, представляющий пользовательский компонент, он передаёт JSX-атрибуты этому компоненту в виде единственного объекта. Мы называем этот объект «props».
Например, этот код отображает «Привет, Сара» на странице:
Давайте посмотрим, что происходит в этом примере:
Hello, Sara
Hello, Sara
Примечание: Всегда именуйте компоненты с заглавной буквы.
React обрабатывает компоненты, начинающиеся со строчных букв, как DOM-теги. Например,
Вы можете больше узнать о причинах, лежащих в основе этого соглашения здесь.
Компоненты могут ссылаться на другие компоненты в своём выводе. Это позволяет использовать одну и ту же абстракцию компонента для любого уровня детализации. Кнопка, форма, диалоговое окно, экран: в приложениях React все они обычно являются компонентами.
Не бойтесь разделять компоненты на более мелкие компоненты.
Например, рассмотрим этот компонент Comment :
Он принимает author (объект), text (строка) и date (дата) в качестве свойств и описывает комментарий на сайте социальных сетей.
Этот компонент может быть сложно изменить из-за вложенности, а также трудно повторно использовать отдельные его части. Давайте извлечём из него несколько компонентов.
Сначала мы извлечём Avatar :
Мы рекомендуем называть свойства объекта props с точки зрения самого компонента, а не контекста, в котором он используется.
Теперь мы можем чуть-чуть упростить Comment :
Это позволяет нам упростить Comment ещё больше:
Свойства объекта props доступны только для чтения
Независимо от того, объявляете ли компонент как функцию или класс, он не должен изменять свои свойства. Рассмотрим эту функцию sum :
Такие функции называются «чистыми», потому что они не пытаются изменить свои аргументы и всегда возвращают один и тот же результат для одних и тех же входных данных.
Напротив, функция ниже — не чистая, потому что она изменяет свои входные данные:
React довольно гибкий, но имеет одно строгое правило:
Все React-компоненты должны вести себя как чистые функции в плане своих свойств.
Конечно, пользовательские интерфейсы приложений динамичны и меняются со временем. В следующем разделе мы представим новую концепцию «состояние». Состояние позволяет компонентам React изменять свой вывод с течением времени в ответ на действия пользователя, сетевые ответы и что-либо ещё без нарушения правила выше.
React — Используйте стандартные пропсы для потока данных
Давайте поговорим про поток данных React приложения состоящего из набора форм.
Предполагается, что читатель знаком с react, react-хуками, функциональными компонентами, мемоизацией хорошо знает javascript и не пугается spread операторов (три точки которые).
Update 1: Извините, но примеры написаны без использования Typescript и встречается Redux.
Я постараюсь подвести вас к логичному для меня выводу, что зачастую компонентам, которые отображают форму или часть формы, достаточно иметь всего три пропса.
Чтобы был более понятен ход моих мыслей от самого начала, желательно ознакомится с предыдущей моей статьей про композицию компонентов.
Представьте себе сложную форму которая состоит из нескольких частей, а те в свою очередь состоят из других частей.
Например форма редактирования данных о пользователе:
В компоненте UserInfo редактируются поля firstName, lastName.
В компоненте Experience редактируются поля positionName, positionDescription.
В компоненте Education редактируются поля name, description.
Попробуем реализовать компонент UserInfo
Иногда я встречаю такую реализацию:
И такой вызов из UserForm:
Но не делайте так. Если пойдет в таком духе, то на входе у UserForm будут все пропсы из компонентов UserInfo, Experience и Education. Мне даже лень этот код вам писать.
Обычно всем тоже лень и в итоге просто вместо прописывания всех пропсов используют spread оператор:
И дальше надеются, что каждый компонент сам выберет себе нужные пропсы.
Пожалуйста, так тоже не делайте. Вы подвергаете свой код неявным ошибкам. Мало ли что может залететь в UserForm что не желательно чтобы было в Education. Ну например пропс className или style которые пол года назад использовались чтобы стилизовать UserForm, потом в UserForm это убрали, а в Education такой пропс добавили. И вот кто-то забыл почистить код и где-то остались вызовы UserForm с className. Теперь неожиданно для всех className прокинется в Education.
Всегда явно прокидуйте пропсы чтобы это видно было по коду какие пропсы в какие компоненты попадают.
Что мы можем с этим всем сделать?
Давайте посмотрим на обычные поля ввода которые перекочевали в реакт из HTML. Огромное спасибо разработчикам реакта, что сохранили тот же интерфейс привычный всем, а не, как в ангуляре, напридумывали своих конструкций.
По сути — это все три пропсы достаточные для передачи потока данных.
Вот так например будет выглядеть теперь UserInfo:
Тут я в компоненте UserInfo использую стандартные три пропса. И что немаловажно повторил интерфейс вызова события onChange. Он также возвращает информацию о изменениях как это делает стандартный input используя target, name, value. С одной стороны target добавляет еще уровень вложенности, но так уж исторически сложилось у стандартного события onChange, с этим уже ничего не поделаешь. Зато мы получаем очень важное преимущество — одинаковое поведение всех полей ввода и частей формы.
То есть мы можем теперь переписать UserForm.
Если у нас данные хранятся как такой объект:
Если у нас данные хранятся как такой объект:
Как видим, количество пропсов на входе UserForm уменьшилось с 2*N до всего трех.
Но это только часть выгоды.
Как сделать код компактней и читабельней?
Так как у нас теперь везде одинаковый интерфейс, то теперь можно написать вспомогательные функции, которые будут работать со всеми такими компонентами.
Например, представим функцию getInnerProps, которая мапит вложенные данные на вложенные компоненты. Тогда код компонентов становится намного лаконичней:
Обратите внимание, что одна и та же функция innerProps.forInput() формирует пропсы name, value и onChange и для стандартного поля ввода Input и для компонента UserInfo. Все благодаря одному интерфейсу потока данных.
Усложним пример
Допустим, пользователю нужно ввести несколько образований (education). Один из вариантов решения (на мой взгляд неправильного):
Обработчик onChangeEducation будет менять в нужном месте стора education по его id. Тут есть небольшое противоречие. На вход прилетает коллекция educations, а на событие изменения возвращается один education.
Можно часть кода перенести из Redux в компонент. Тогда станет все логичней. На вход UserForm приходит коллекция educations и на событие изменения уходит тоже коллекция educations:
Немного остановимся на том как мы передаем обработчик в onChangeName и onChangeDescription. Я сознательно не обращал на это внимание для минимизации примеров. Но сейчас это важно.
В реальности компонент Education будет скорее всего мемоизированный (React.memo()). Тогда мемоизация не будет иметь смысла из-за того, что каждый раз мы передаем новую ссылку на функцию. Чтобы не создавать каждый раз новую ссылку используют хук useCallback или useConstant (отдельный npm модуль).
Если в остальных примерах это решало проблему, то тут цикл, а хуки нельзя использовать внутри условий и циклов.
А вот используя name и ожидая от Education стандартного поведения onChange уже можно применить хук useConstant:
А теперь сделаем с помощью функции getInnerProps:
Вроде как достаточно красивый и понятный код получился.
Несколько слов про стейт
Подключим stateless компонент UserInfo к стейту и замкнем поток данных. В качестве примера возьмем Redux.
Вот так иногда реализовывают редюсер:
То есть, изменение каждого поля выносят в отдельный action. В этом подходе я вижу два сомнительных плюса и один большой минус.
Первый плюс — это то, что можно написать тест для этого редсера. Сомнителен — потому что вряд ли этот тест сильно поможет.
Второй плюс — то что можно отдельно подконектить чуть ли не каждый инпут к отдельному полю в сторе и будет обновлятся только это связанное поле ввода. Тут еще не факт что это даст увеличение производительности. Проитерироваться по 10 мемоизированным частям формы, в результате чего будет перерисована только одна часть — это практически не скажется на производительности.
Минус в том что придется писать очень много кода: для каждого поля изменение стейта, потом добавить action, прокинуть значение, вызвать на каждое событие отдельный action.
Да, в документации по редаксу говорят, что надо писать редюсеры не такие у которых только set, а такие в которых побольше экшенов. Типа чем больше экшенов в реюсере, тем больше тестов можно написать. Больше тестов — меньше ошибок.
Но я думаю меньше ошибок там, где меньше кода, а много экшенов нужно писать только там где это надо.
Для себя я решил что для форм в редаксе, везде где это возможно, использую только один action — какой-нибудь SET.
А уже на UI (т.е. в реакте) я определяю какие поля в какой части данных меняются.
Потому как логику специфических полей не опишешь в редаксе. Например поле ввода номера телефона — это может быть сложный реакт компонент компонент, а не просто изменение значение в стейте.
Когда применять описанный подход
Имейте ввиду. Это не подход на все случаи жизни. Все описанное применимо в основном к приложениям в которых много форм, где одна форма собирается из набора других форм и поток данных направлен из стора в форму контейнер, из нее — в составные части формы, из них еще — на уровень ниже.
Если у вас приложение со сложным интерфесом в котором разные компоненты взаимодействуют друг с другом описанное в статье мало что вам даст. Как раз в этом случае логично каждый компонент подключать к стору.
Если у вас смешанное приложение, то важно найти границу — какие части формы подключать к редаксу, а в каких пробрасывать данные от контейнера к дочерним компонентам. Обычно эта граница начинается там, где появляется логика взаимодействия разных частей формы.
Резюме
Давайте использовать одинаковые пропсы для потока данных, пропсы которые уже давно есть в HTML:
Старайтесь на onChange в target.value возвращать ту же сущность что на вход в value.
Тогда, за счет использования стандартного подхода и общих хелперных функций для этого подхода, код станет лаконичней и понятней.