Мышление в React
React может изменить то, как вы смотрите на дизайны и создаваемые приложения. Когда вы создаёте пользовательский интерфейс с помощью React, вы сначала разбиваете его на части, называемыекомпонентами. Затем вы описываете различные визуальные состояния для каждого из ваших компонентов. Наконец, вы соединяете компоненты вместе, чтобы данные протекали через них. В этом руководстве мы проведём вас через мыслительный процесс создания поисковой таблицы данных о продуктах с помощью React.
Начните с макета
Представьте, что у вас уже есть JSON API и макет от дизайнера.
JSON API возвращает данные, которые выглядят так:
Макет выглядит так:

Для реализации интерфейса в React вы обычно следуете одним и тем же пяти шагам.
Шаг 1: Разбейте интерфейс на иерархию компонентов
Начните с того, чтобы обвести рамками каждый компонент и подкомпонент в макете и дать им имена. Если вы работаете с дизайнером, возможно, он уже назвал эти компоненты в своём инструменте проектирования. Спросите его!
В зависимости от вашего опыта, вы можете по-разному подходить к разделению дизайна на компоненты:
- Программирование—используйте те же приёмы, что и при решении, стоит ли создавать новую функцию или объект. Один из таких приёмов — эторазделение ответственности, то есть компонент в идеале должен отвечать только за одну вещь. Если он разрастается, его следует разложить на более мелкие подкомпоненты.
- CSS—подумайте, для чего бы вы создавали селекторы классов. (Однако компоненты немного менее детализированы.)
- Дизайн—подумайте, как бы вы организовали слои дизайна.
Если ваш JSON хорошо структурирован, вы часто обнаружите, что он естественным образом соответствует структуре компонентов вашего интерфейса. Это происходит потому, что UI и модели данных часто имеют одну и ту же информационную архитектуру — то есть одну и ту же форму. Разделите ваш интерфейс на компоненты, где каждый компонент соответствует одной части вашей модели данных.
На этом экране пять компонентов:

FilterableProductTable(серый) содержит всё приложение.SearchBar(синий) получает ввод пользователя.ProductTable(лавандовый) отображает и фильтрует список в соответствии с вводом пользователя.ProductCategoryRow(зелёный) отображает заголовок для каждой категории.ProductRow(жёлтый) отображает строку для каждого продукта.
Если посмотреть наProductTable(лавандовый), можно заметить, что заголовок таблицы (содержащий метки «Name» и «Price») не является отдельным компонентом. Это вопрос предпочтений, и можно поступить так или иначе. В этом примере он является частьюProductTable, потому что находится внутри спискаProductTable. Однако, если этот заголовок станет сложным (например, если добавить сортировку), вы можете вынести его в собственный компонентProductTableHeader.
Теперь, когда вы определили компоненты в макете, расположите их в иерархию. Компоненты, которые появляются внутри другого компонента в макете, должны быть дочерними в иерархии:
FilterableProductTableSearchBarProductTableProductCategoryRowProductRow
Шаг 2: Создайте статическую версию в React
Теперь, когда у вас есть иерархия компонентов, пришло время реализовать ваше приложение. Самый простой подход — создать версию, которая отрисовывает интерфейс из вашей модели данных, пока без какой-либо интерактивности… пока! Часто проще сначала создать статическую версию, а затем добавить интерактивность. Создание статической версии требует много печатать и не думать, а добавление интерактивности требует много думать и мало печатать.
Чтобы создать статическую версию вашего приложения, которая отрисовывает вашу модель данных, вам нужно будет создатькомпоненты, которые повторно используют другие компоненты и передают данные с помощьюпропсов.Пропсы — это способ передачи данных от родителя к потомку. (Если вы знакомы с концепциейсостояния, не используйте состояние вообще для создания этой статической версии. Состояние зарезервировано только для интерактивности, то есть для данных, которые меняются со временем. Поскольку это статическая версия приложения, оно вам не нужно.)
Вы можете строить «сверху вниз», начиная с создания компонентов выше в иерархии (например,FilterableProductTable), или «снизу вверх», работая с компонентами ниже (например,ProductRow). В более простых примерах обычно легче идти сверху вниз, а в крупных проектах — снизу вверх.
(Если этот код выглядит пугающим, сначала пройдитеБыстрый старт!)
После создания компонентов у вас будет библиотека переиспользуемых компонентов, которые отображают вашу модель данных. Поскольку это статическое приложение, компоненты будут возвращать только JSX. Компонент на верхнем уровне иерархии (FilterableProductTable) будет принимать вашу модель данных как пропс. Это называетсяоднонаправленным потоком данных, потому что данные передаются сверху вниз — от компонента верхнего уровня к компонентам внизу дерева.
Подводный камень
На этом этапе вы не должны использовать никакие значения состояния. Это для следующего шага!
Шаг 3: Найдите минимальное, но полное представление состояния UI
Чтобы сделать UI интерактивным, нужно позволить пользователям изменять вашу базовую модель данных. Для этого вы будете использоватьсостояние.
Думайте о состоянии как о минимальном наборе изменяющихся данных, которые вашему приложению нужно запомнить. Самый важный принцип структурирования состояния — сохранять егоDRY (Не повторяйся).Определите абсолютно минимальное представление состояния, необходимое вашему приложению, и вычисляйте всё остальное по требованию. Например, если вы создаёте список покупок, вы можете хранить элементы в виде массива в состоянии. Если вы также хотите отображать количество элементов в списке, не храните количество элементов как другое значение состояния — вместо этого читайте длину вашего массива.
Теперь подумайте обо всех частях данных в этом примере приложения:
- Исходный список товаров
- Текст поиска, введённый пользователем
- Значение флажка
- Отфильтрованный список товаров
Какие из них являются состоянием? Определите те, которые не являются:
- Оноостаётся неизменнымсо временем? Если да, то это не состояние.
- Онопередаётся от родителячерез пропсы? Если да, то это не состояние.
- Можете ли вы вычислить егона основе существующего состояния или пропсов в вашем компоненте? Если да, то оноопределённоне состояние!
То, что осталось, вероятно, является состоянием.
Давайте пройдёмся по ним ещё раз по одному:
- Исходный список товаровпередаётся через пропсы, поэтому это не состояние.
- Текст поиска, похоже, является состоянием, поскольку он меняется со временем и не может быть вычислен из чего-либо.
- Значение флажка, похоже, является состоянием, поскольку оно меняется со временем и не может быть вычислено из чего-либо.
- Отфильтрованный список товаровне является состоянием, потому что его можно вычислить, взяв исходный список товаров и отфильтровав его в соответствии с текстом поиска и значением флажка.
Это означает, что только текст поиска и значение флажка являются состоянием! Отлично сделано!
Шаг 4: Определите, где должно находиться ваше состояние
Определив минимальные данные состояния вашего приложения, вам нужно выяснить, какой компонент отвечает за изменение этого состояния иливладеетим. Помните: React использует однонаправленный поток данных, передавая данные вниз по иерархии компонентов от родителя к дочернему компоненту. Может быть не сразу понятно, какой компонент должен владеть тем или иным состоянием. Это может быть сложно, если вы новичок в этой концепции, но вы сможете разобраться, следуя этим шагам!
Для каждого фрагмента состояния в вашем приложении:
- Определитевсекомпоненты, которые что-то отображают на основе этого состояния.
- Найдите их ближайшего общего родительского компонента — компонент, находящийся выше всех них в иерархии.
- Решите, где должно находиться состояние:
- Часто вы можете поместить состояние непосредственно в их общего родителя.
- Вы также можете поместить состояние в какой-либо компонент выше их общего родителя.
- Если вы не можете найти компонент, которому логично владеть состоянием, создайте новый компонент исключительно для хранения состояния и добавьте его куда-нибудь в иерархии выше общего родительского компонента.
На предыдущем шаге вы нашли два фрагмента состояния в этом приложении: текст поискового запроса и значение флажка. В этом примере они всегда появляются вместе, поэтому логично поместить их в одно место.
Теперь давайте пройдёмся по нашей стратегии для них:
- Определите компоненты, использующие состояние:
ProductTableдолжен фильтровать список товаров на основе этого состояния (текст поиска и значение флажка).SearchBarдолжен отображать это состояние (текст поиска и значение флажка).
- Найдите их общего родителя:Первый родительский компонент, общий для обоих компонентов, — это
FilterableProductTable. - Решите, где будет находиться состояние: Мы будем хранить текст фильтра и состояние флажка в
FilterableProductTable.
Таким образом, значения состояния будут находиться вFilterableProductTable.
Добавьте состояние в компонент с помощьюхука useState().Хуки — это специальные функции, которые позволяют вам «подключаться» к React. Добавьте две переменные состояния в начало компонентаFilterableProductTableи укажите их начальное состояние:
Затем передайтеfilterText и inStockOnly в ProductTable и SearchBarв качестве пропсов:
Вы можете начать видеть, как будет вести себя ваше приложение. Измените начальное значениеfilterText с useState('')наuseState('fruit')в коде песочницы ниже. Вы увидите, как обновятся и текст в поле поиска, и таблица:
Обратите внимание, что редактирование формы пока не работает. В песочнице выше есть ошибка в консоли, объясняющая почему:
Консоль
Вы передали проп `value` полю формы без обработчика `onChange`. Это приведёт к отображению поля только для чтения.
В песочнице вышеProductTable и SearchBarчитают пропсыfilterText и inStockOnly, чтобы отрисовать таблицу, поле ввода и флажок. Например, вот какSearchBarзаполняет значение поля ввода:
Однако вы ещё не добавили код для реагирования на действия пользователя, такие как ввод текста. Это будет вашим последним шагом.
Шаг 5: Добавьте обратный поток данных
В настоящее время ваше приложение корректно отрисовывается с пропсами и состоянием, передаваемыми вниз по иерархии. Но чтобы изменять состояние в соответствии с пользовательским вводом, вам потребуется поддержать поток данных в обратном направлении: компоненты формы, находящиеся глубоко в иерархии, должны обновлять состояние вFilterableProductTable.
React делает этот поток данных явным, но он требует немного больше кода, чем двусторонняя привязка данных. Если вы попробуете ввести текст или установить флажок в примере выше, вы увидите, что React игнорирует ваш ввод. Это сделано намеренно. Написав<input value={filterText} />, вы установили пропvalue для inputвсегда равным состояниюfilterText, переданному изFilterableProductTable. Поскольку состояниеfilterTextникогда не устанавливается, поле ввода никогда не меняется.
Вы хотите сделать так, чтобы при любом изменении пользователем данных в форме состояние обновлялось, отражая эти изменения. Состояние принадлежитFilterableProductTable, поэтому только оно может вызыватьsetFilterText и setInStockOnly. Чтобы позволитьSearchBarобновлять состояниеFilterableProductTable, вам нужно передать эти функции вниз вSearchBar:
ВнутриSearchBarвы добавите обработчики событийonChangeи будете устанавливать родительское состояние из них:
Теперь приложение полностью работает!
Всё об обработке событий и обновлении состояния вы можете узнать в разделеДобавление интерактивности.
Что делать дальше
Это было очень краткое введение в то, как думать о создании компонентов и приложений с помощью React. Вы можетеначать проект на Reactпрямо сейчас илипогрузиться глубже во весь синтаксис, использованный в этом руководстве.
