2.13 Мышление в React

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

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


2.13.1 Начинаем с макета

Представьте, что у нас уже есть JSON API и макет нашего дизайнера. Макет выглядит так:

Наш JSON API возвращает некоторые данные, которые выглядят так:

Код
    
  [
    {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
    {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
    {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
    {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
    {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
    {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
  ];
  

2.13.2 Шаг 1: Разбиение UI на иерархию компонентов

Первое, что вам нужно сделать, это нарисовать прямоугольники вокруг каждого компонента (и подкомпонента) в макете и дать им всем имена. Если вы работаете с дизайнерами, они, возможно, уже сделали это, поэтому поговорите с ними! Имена их слоев в Photoshop могут оказаться именами ваших компонентов React!

Но как вы поймете, каков должен быть компонент? Просто используйте те же самые методы для принятия решения, как если бы вам было необходимо создать новую функцию или объект. Одним из таких методов является принцип единой ответственности, то есть в идеале компонент должен делать только что-то одно. Если компонент слишком разрастается, он должен быть разбит на меньшие подкомпоненты.

Поскольку вы часто показываете модель данных JSON для пользователя, вы обнаружите, что если ваша модель была построена правильно, ваш UI (и, следовательно, структура вашего компонента) будет отображаться корректно. Это связано с тем, что UI и модели данных имеют тенденцию придерживаться одной и той же информационной архитектуры, а это означает, что работа по разделению вашего UI на компоненты часто тривиальна. Просто разделите его на компоненты, которые представляют собой ровно одну часть вашей модели данных.

Здесь видно, что в нашем простом приложении у нас есть пять компонентов. Мы выделим курсивом данные, представляемые каждым компонентом.

  • FilterableProductTable (оранжевый): содержит контент примера.
  • SearchBar (голубой): принимает весь пользовательский ввод.
  • ProductTable (зеленый): отображает и фильтрует коллекцию данных, основанную на пользовательском вводе.
  • ProductCategoryRow (бирюзовый): отображает заголовок каждой категории.
  • ProductRow (красный): отображает строку каждого продукта.

Если вы посмотрите на ProductTable, вы увидите, что заголовок таблицы (содержащий надписи «Name» и «Price») сам по себе не является компонентом. Это вопрос предпочтения, так что можно реализовать любым способом. В этом примере мы оставили его как часть ProductTable, потому что он является частью процесса отрисовки коллекции данных, что является ответственностью ProductTable. Однако, если этот заголовок становится сложным (т. е. если мы будем добавлять возможности для сортировки), было бы разумно сделать его отдельным компонентом ProductTableHeader.

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

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow


2.13.3 Шаг 2: Построение статической версии в React

Посмотрите Мышление в React: Шаг 2 в CodePen.

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

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

Вы можете начать создавать компоненты сверху вниз или снизу вверх. То есть вы можете начать с построения компонентов, находящихся выше в иерархии (то есть с FilterableProductTable) или с более нижних (ProductRow). В более простых примерах, как правило, легче идти сверху вниз, а в более крупных проектах легче идти снизу вверх и писать тесты по мере создания компонентов.

В конце этого шага вы будете иметь библиотеку повторно используемых компонентов, которые отображают вашу модель данных. Компоненты будут иметь только методы render(), поскольку это статическая версия вашего приложения. Компонент на вершине иерархии (FilterableProductTable) будет принимать вашу модель данных через props. Если вы внесете изменения в свою базовую модель данных и снова вызовете ReactDOM.render(), UI будет обновлен. Легко понять, как обновляется ваш UI и где вносить изменения, так как тут нет ничего сложного. Односторонний поток данных React (также называемый односторонней привязкой) сохраняет все модульным и быстрым.


2.13.3.1 Краткая интерлюдия: Props vs State

Существует два типа "модельных" данных в React: свойства (props) и состояние (state). Важно, чтобы вы понимали различие между ними, в противном случае просмотрите документацию React.


2.13.4 Шаг 3: Определите минимальное (но полное) представление состояния UI

Чтобы сделать UI интерактивным, вы должны иметь возможность инициировать изменения в своей базовой модели данных. React облегчает это, используя состояние state.

Чтобы правильно создать приложение, сначала нужно подумать о минимальном наборе данных изменяемого состояния, которое требуется вашему приложению. Совет здесь прост: не повторяйтесь! Выясните абсолютно минимальное представление состояния, которое требуется вашему приложению, а все остальное, что вам нужно, вычисляйте. Например, если вы создаете список TODO, просто сохраняйте массив элементов TODO. Не сохраняйте в состоянии отдельную переменную для размера списка. Вместо этого, когда вы хотите отобразить размер списка, просто возьмите длину массива его элементов.

Необходимо подумать обо всех частях данных в нашем примере приложения. У нас есть:

  • Оригинальный список продуктов.
  • Текст поиска, введенный пользователем.
  • Значение чекбокса.
  • Отфильтрованный список продуктов.

Давайте рассмотрим каждую из них и выясним, какая является состоянием. Просто задайте три вопроса о каждой части данных:

  1. Передается ли она от родителя через props? Если это так, вероятно, это не состояние.
  2. Со временем она остается неизменной? Если это так, вероятно, это не состояние.
  3. Вы можете вычислить ее на основании любой другой части состояния или свойств в своем компоненте? Если это так, это не состояние.

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

Итак, наше состояние:

  • Текст поиска, введенный пользователем
  • Значение чекбокса


2.13.5 Шаг 4: Определите, где должно находиться ваше состояние

Посмотрите Мышление в React: Шаг 4 в CodePen.

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

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

Для каждой части состояния в вашем приложении:

  • Определите каждый компонент, который отображает что-то на основе состояния.
  • Найдите общий компонент-владелец (единственный компонент, расположенный выше всех компонентов в иерархии, которым требуется состояние).
  • Состоянием должен обладать либо общий компонент-владелец, либо компонент, находящийся выше в иерархии.
  • Если вы не можете найти компонент, который имеет смысл наделить состоянием, просто создайте новый компонент для хранения состояния и добавьте его в иерархию где-нибудь выше общего компонента-владельца.

Давайте применим эту стратегию для нашего приложения:

  • ProductTable должен фильтровать список продуктов на основе состояния, а SearchBar должен отображать текст поиска и состояние чекбокса.
  • Общим компонентом-владельцем является FilterableProductTable.
  • Концептуально тексту фильтра и значению чекбокса имеет смысл находиться в FilterableProductTable

Итак мы решили, что наше состояние будет жить в FilterableProductTable компоненте. Во-первых, добавьте свойство экземпляра this.state = {filterText: '', inStockOnly: false} в конструктор FilterableProductTable, чтобы отразить начальное состояние вашего приложения. Затем передайте filterText и inStockOnly в ProductTable и SearchBar в качестве props. Наконец, используйте props для фильтрации строк в ProductTable и установите значения полей формы в SearchBar.

Вы можете посмотреть, как будет вести себя ваше приложение: установите filterText в "ball" и обновите приложение. Вы увидите, что таблица данных обновлена правильно.


2.13.6 Шаг 5: Добавьте обратный поток данных

Посмотрите Мышление в React: Шаг 5 в CodePen.

До сих пор мы создавали приложение, как функцию свойств props и состояния state, стекающих вниз по иерархии. Теперь пришло время организовать поток данных в обратную сторону: компонентам формы в глубине иерархии необходимо обновить состояние в FilterableProductTable.

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

Если вы попытаетесь ввести текст или установить флажок в текущей версии примера, вы увидите, что React проигнорирует ваш ввод. Это преднамеренно, так как мы установили, чтобы свойство value элемента input всегда было равно значению из состояния state, переданному из FilterableProductTable.

Давайте подумаем о том, что мы хотим получить. Мы хотим убедиться, что всякий раз, когда пользователь изменяет форму, мы обновляем состояние, чтобы отображать ввод пользователя. Поскольку компоненты должны обновлять только собственное состояние, FilterableProductTable будет передавать коллбэки в SearchBar, которые будут срабатывать при каждом обновлении состояния. Мы можем использовать событие onChange на элементах input, чтобы получать уведомление об этом. Коллбэки, переданные компонентом FilterableProductTable, вызовут setState(), и приложение будет обновлено.

Звучит сложно, но на самом деле это всего лишь несколько строк кода, и вам всегда действительно ясно, как ваши данные текут во всем приложении.


2.13.7 Вот и всё

Разработчики надеются, что эта информация даст вам представление о том, каким образом думать во время создания компонентов и приложений с помощью React. Хотя может потребоваться немного больше кодирования, чем вы привыкли, помните, что код читается гораздо больше, чем пишется. Очень легко прочитать этот модульный, ясный код. Когда вы начнете создавать большие библиотеки компонентов, вы оцените эту ясность и модульность, а с повторным использованием кода ваш собственный код начнёт сокращаться. :)