Начнем с макета
Представим, что у нас имеется некое 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 вполне подойдут для наименования ваших React компонентов!
Но откуда узнать, каким должен быть конечный отдельный компонент? Просто используйте те же методы для определения, что и при создании новой функции или объекта. Одним из таких методов является Принцип единственной ответственности, то есть компонент, в идеале, должен создавать только одну вещь/сущность. Если компонент создает несколько повторяемых в других компонентах сущностей, то он должен быть разложен на более мелкие компоненты.
Чем чаще вы будете отображать модель данных JSON в виде пользовательского интерфейса, тем быстрее вы придете к тому, что если модель данных построена корректно, то и ваш пользовательский интерфейс (и, следовательно, структура компонентов) выглядит красиво. Причина в том, что пользовательский интерфейс и модель данных, как правило, придерживаются одной и той же информационной архитектуры, а это означает, что разделение пользовательского интерфейса на составляющие часто является тривиальной задачей — "Просто разбейте его на компоненты, которые представляют ровно один кусочек вашей модели данных".
Как видно из макета — у нас есть пять компонентов в нашем простом приложении. Мы выделили разноцветными боксами каждый из компонентов.
- FilterableProductTable (оранжевый): содержит всю нашу таблицу
- SearchBar (синий): принимает весь пользовательский ввод
- ProductTable (зеленый): отображает и фильтрует набор данных основанных на пользовательском вводе
- ProductCategoryRow (бирюзовый): отображает заголовок для каждой категории
- ProductRow (красный): отображает строку для каждого товара
Если вы посмотрите на ProductTable, вы увидите, что заголовок таблицы (содержащий ярлыки "Name" и "Price") не выделен в отдельный компонент. Это вопрос предпочтений, и есть аргументы для того или иного варианта. Для этого примера, мы оставили заголовок в составе ProductTable, потому что он является частью визуализации набора данных, которая относится к ответственности ProductTable. Однако, если заголовок был бы более сложным (например: мы должны были бы добавить возможность для сортировки по столбцам), конечно, мы бы выделили бы его в отдельный компонент ProductTableHeader.
Теперь, когда мы определили компоненты в нашем макете, давайте оформим их в виде иерархии. Это легко. Компоненты, которые входят в другой компонент в макете, должны выглядеть как потомки в иерархии:
- FilterableProductTable
- SearchBar
- ProductTable
- ProductCategoryRow
- ProductRow
Шаг 2: Создаем статическую версию в React
HTML
CSS
JavaScript
Теперь у вас есть ваша иерархия Компонентов, пришло время реализовать свое приложение. Самый простой способ — начать со статической версии, в которой отображается модель данных и пользовательский интерфейс, но нет интерактивности. Разделение статической и интерактивной частей — хорошое решение, т.к. задача по реализации статической версии — это ввод большого количества текста с клавиатуры с наименьшим мыслительным процессом, тогда как реализация интерактивности наоборот требует большого мыслительного процесса и малого ввода с клавиатуры. Далее мы увидим — почему так.
Реализация статической версии — это отображение (рендеринг) модели данных, в процессе которого вы создаете Компоненты, которые используют другие Компоненты и передают данные посредством props (Свойства). props являются инструментом передачи данных от предка к потомку в иерархии Компонентов React. В React есть такое понятие, как state (Состояние) — никогда не используйте Состояние (state) при создании статической версии. Основное предназначение Состояния — интерактивность, оно необходимо для передачи и фиксирования данных, которые меняются с течением времени. Поскольку, в данный момент, вы создаете статическую версию приложения — вы не нуждаетесь в использовании Состояния.
Вы можете реализовывать приложение сверху-вниз или снизу-вверх. То есть, вы можете начать с построения Компонентов более высокого уровня иерархии (начиная с FilterableProductTable) или наоборот с низкого уровня (ProductRow). В более простых приложениях, как правило, легче идти сверху вниз, а в более крупных — снизу вверх и параллельно писать тесты по мере реализации Компонентов.
В конце этого шага, у вас будет библиотека повторно используемых Компонентов, которые отображают вашу модель данных. Компоненты будут иметь только метод render(), так как это статическая версия. Компонент в верхней части иерархии (FilterableProductTable) получит модель данных посредством props. Если вы внесете изменение в базовую модель данных и вызовете ReactDOM.render() заново — пользовательский интерфейс будет обновлен. Не правда ли — в этом нет ничего сложного, т.к это действительно очень просто? Односторонний поток данных React (также называемый односторонним связыванием) обеспечивает модульность и скорость.
Небольшое отступление: Свойства (props) и Состояние (state)
Шаг 3: Определяем минимальное (но достаточное) представление Cостояния (state) пользовательского интерфейса
Для того, чтобы сделать пользовательский интерфейс интерактивным, вам необходимо иметь возможность регистрировать изменения в базовой модели данных. В React это делается просто с помощью state.
Для построения корректного приложения, в первую очередь, вам необходимо обдумать минимальный набор изменяемых состояний необходимых для вашего приложения. Придерживайтесь принципа DRY: Don't Repeat Yourself (Не повторяйся). В идеале — минимальное представление Состояния вашего приложения не должно содержать чего либо, что может быть вычислено на основании имеющихся данных (в props и state) в момент времени, когда это необходимо. Например: Если вы строите приложение, отображающее список дел, оставайтесь в пределах массива, содержащего записи дел — не создавайте отдельной переменной Состояния, отображающей количество дел. Вместо этого, в тот момент когда вам необходимо отобразить количество дел — просто возьмите длину имеющегося массива.
Продумаем все единицы данных в нашем приложении. Мы имеем:
- Оригинальный список продуктов
- Поисковый текст, введенный пользователем
- Значение чекбокса
- Отфильтрованный список продуктов
Давайте пройдемся по каждому пункту и определим — является ли он Состоянием. Для каждой единицы данных нам надо задать три вопроса:
- Передаются ли эти данные посредством props от предка? Если да, то это скорее всего не Состояние.
- Остаются ли эти данные неизменными с течением времени? Если да, то это скорее всего не Состояние.
- Можете ли вы вычислить эти данные на основании имеющихся (в props и state) в вашем Компоненте? Если да, то это не Состояние.
Оригинальный список продуктов передается посредством props — значит это не Состояние. Поисковый текст и значение чекбокса могут изменяться с течением времени и не могут быть вычислены из имеющихся данных — это Состояния. И последнее: Отфильтрованный список продуктов может быть вычислен комбинированием оригинального списка продуктов, поисковым текстом и значением чекбокса — это не Состояние.
В итоге, наши Состояния:
- Поисковый текст, введенный пользователем
- Значение чекбокса
Шаг 4: Определяем где наше Состояние будет размещаться
HTML
CSS
JavaScript
Итак, мы определили минимальный набор Состояний приложения. Следующим шагом нам необходимо определить какой компонент изменяет или владеет этим Состоянием.
Запомните: В React работает односторонняя передача данных вниз по иерархии Компонентов. Возможно, из этого не сразу понятно какой компонент должен быть владельцем Состояния. Эта часть часто является достаточно сложной для новичков — поэтому следуйте следующим шагам, для выяснения этого вопроса:
Для каждой единицы Состояния вашего приложения:
- Определите все Компоненты, которые отображают (производят рендеринг) что-либо на основании этого Состояния.
- Найдите общего предка для этих Компонентов (единый Компонент по иерархии выше для всех Компонентов, которым необходимо это Состояние).
- Найденного общего предка или любой Компонент в иерархии вышего него можно назначить владельцем Состояния.
- Если общий предок отсутствует в вашей иерархии Компонентов — вам необходимо создать Компонент более высокого уровня просто для назначения его владельцем Состояния.
Итак, давайте применим эту стратегию к нашему приложению:
- Компонент
ProductTable
нуждается в Состоянии для фильтрации списка продуктов, в то же время КомпонентSearchBar
нуждается в Состоянии для отображения поискового запроса и состояния чекбокса. - Общим предком для них является Компонент
FilterableProductTable
. - Таким образом, концептуально, точка соприкосновения фильтрации списка и выбранных значений находится в Компоненте
FilterableProductTable
Отлично, мы определили, что наше Состояние должно быть размещено в Компоненте FilterableProductTable
. В первую очердь добавим свойство экземпляра this.state = {filterText: '', inStockOnly: false}
в constructor
Компонента FilterableProductTable
для определения начального Состояния нашего приложения. Затем передадим filterText
и inStockOnly
в Компоненты ProductTable
и SearchBar
посредством props. И конечным шагом — используем props для фильтрации строк в ProductTable
и установки значений полей формы в SearchBar
.
Вы можете запустить приложение и посмотреть как оно будет вести себя: установите значение filterText
в конструкторе Компонента FilterableProductTable
равным 'ball'
и перезагрузите приложение. Вы увидите, что таблица данных корректно обновлена.
Шаг 5: Добавляем обратный поток данных
HTML
CSS
JavaScript
К текущему шагу мы создали приложение, которое корректно передает и использует props и state двигаясь по иерархии Компонентов вниз. Настало время добавит поддержку передачи данных в обратном направлении: компонентам формы, которые расположены ниже по иерархии, необходимо каким-то образом обновлять Состояние в Компоненте FilterableProductTable
.
Механизм React для создания этого потока данных отлично проработан, чтобы создать легкое понимание того, как работает программа, но он требует немного больше ввода с клавиатуры, чем традиционное двустороннее связывание данных.
Если вы попробуете ввести текст или отметить чекбокс в текущей версии примера, вы обнаружите, что React игнорирует ваш ввод. Это умышленно установлено нами, т.к. мы установили значение свойства value
в input
всегда эквивалентным state
передаваемому из Компонента FilterableProductTable
.
Давайте подумаем — что мы хотим чтобы произошло. Мы хотим, чтобы каждый раз, когда пользователь вносит изменения в форму, обновлялось Состояние для отображения пользовательского ввода. Поскольку Компоненты должны обновлять только собственное Состояние, Компоненту FilterableProductTable
необходимо передать в Компонент SearchBar
механизм обратного вызова, который будет сигнализировать каждый раз, когда Состояние должно быть обновлено. Мы можем использовать событие onChange
в компонентах формы для сообщения об этом. Тогда, обратный вызов переданный Компонентом FilterableProductTable
вызовет setState()
и приложение будет обновлено.
Хотя это и выглядит сложным, но на самом деле это всего лишь несколько строк кода. И реально прозрачно видно как ваши данные передаются через все приложение.
На этом все
Надеюсь, этот туториал дал вам представление о том, как следует мыслить при построении компонентов и приложений React. Хотя нам пришлось набрать немного больше кода, чем вы привыкли, помните, что код читается в разы чаще, чем пишется, а наш код читается легко за счет прозрачности и модульности. Как только вы начнете создавать большие библиотеки или приложения — вы по настоящему оцените эту прозрачность и модульность, а возможность повторного использования неизменно приведет к тому, что вы будете набирать все меньше и меньше кода.
источник: https://m.habrahabr.ru