2.13 Мышление в React


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


Одна из многих замечательных частей 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: Построим статическую версию приложения


See the Pen Thinking In React: Step 2 by Sergey (@stzidane) on 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: Определим, где должно находиться наше состояние


See the Pen Thinking In React: Step 4 by Sergey (@stzidane) on 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: Добавим обратный поток данных


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

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

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

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

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

Вот окончательная версия приложения:


See the Pen Thinking In React: Step 5 by Sergey (@stzidane) on CodePen.



2.13.7 Вот и всё


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