Рассылка
Подпишитесь на рассылку и получайте дайджест новостей и статей.
Никакого спама!
Подписаться

Справочник ReactJS

Думая на React

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

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

Начнем с наброска

Представим, что у нас уже есть 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"}
];

Шаг 1: Разбить интерфейс на структуру компонентов

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

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

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

Пример разделения интерфейса на компоненты

Вы увидите пять компонентов в нашем простом приложении. Курсивом я выделил данные, которые представляет каждый компонент.

  1. FilterableProductTable (оражевый): содержит все приложение
  2. SearchBar (голубой): отвечает за пользовательский ввод
  3. ProductTable (зеленый): отображает и фильтрует коллекцию данных на основе пользовательского ввода
  4. ProductCategoryRow (бирюзовый): отображает заголовок каждой категории
  5. ProductRow (красный): отображает строку для каждого продукта

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

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

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

Шаг 2: Разработать статичную версию на React

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

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

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

В результате этого шага у вас будет библиотека переиспользуемых компонентов, отображащих данные. Компоненты будут иметь только метод render(). Компонент верхнего уровня иерархии (FilterableProductTable) будет принимать данные в качестве свойства. Если вы измените данные и вызовете метод ReactDOM.render() снова, интерфейс обновится. Однонаправленный поток данных (one-way data flow или one-way binding) в React cохраняет приложение модульным и быстрым.

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

Краткое отступление: props vs state

В React есть два типа «модели» данных: props и state. Важно понимать разницу между ними. Обратитесь к документации, если не уверены, в чем эта разница.

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

Чтобы сделать интерфейс интерактивным, нам необходима возможность внести изменения в базовую модель данных. В React это делается с помощью состояния (state).

Чтобы правильно спроектировать приложение, в первую очередь нужно продумать минимальный набор изменяемых данных. Ключевым здесь является принцип DRY: Don’t Repeat Youself. Нужно выяснить, какие данные относятся к минимальному необходимому состоянию, а какие можно вычислить по требованию. Например, если разрабатывается список TODO, в качестве минимального состояния стоит выбрать сам массив элементов TODO. Нет необходимости хранить отдельную переменную для количества элементов, в этом случае можно использовать длину списка.

Рассмотрим все данные, которые у нас есть в приложении. Это:

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

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

  • Передается ли он от родителя в качестве свойства? Если да, то это не состояние.
  • Меняется ли это с течением времени? Если нет, то это не состояние.
  • Можете ли вычислить его на основе состояния или свойств компонента? Если да, то это не состояние.

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

Итого, мы получили следующее состояние:

  • Поисковая фраза, введенная пользователем
  • Значение чекбокса

Шаг 4: Определить владельца состояния

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

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

Для каждого элемента состояния:

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

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

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

Отлично, мы приняли решение, что состоянием будет владеть компонент FilterableProductTable. Во-первых, добавим компоненту FilterableProductTable метод getInitialState(), который будет возвращать объект {filterText: ’’, inStockOnly: false} в качестве начального состояния приложения. Затем, передадим filterText и inStockOnly в компоненты ProductTable и SearchBar в виде свойств. И наконец, используем эти свойства, чтобы отфильтровать список в ProductTable и отобразить поля ввода и компоненте SearchBar.

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

Шаг 5. Добавить обратный поток данных

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

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

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

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

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

Вот и все

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