Text
                    О’КЕИ-ЬУ

Неасг

Сборник рецептов

Практические приемы работы с фреймворком Реас1

Дэвид Гриффитс, Дон Гриффитс

(Ы1У

йеаЛ СоокЬоок Кеаре$/ог Ма$1егт$ 1ке Кеас1 Ргатешогк Оаугс! Сп0кк$ апй Оам>п СпДШк Вефпд • Во$1оп • ЕагпЬат • 5еЬа$1оро1 • Токуо СУРЕШУ

Дэвид Гриффитс Дон Гриффитс Реас! Сборник рецептов Санкт-Петербург «БХВ-Петербург» 2023
УДК 004.438 ББК 32.973.26-018.1 Г85 Гриффитс, Д. Г85 КеасГ Сборник рецептов: Пер. с англ. / Дэвид Гриффитс, Дон Гриффитс. — СПб.: БХВ-Петербург, 2023. — 528 с.: ил. 18В1Ч 978-5-9775-6839-5 Книга посвящена практическому применению фреймворка КеасГ. Описано соз- дание простых приложений и приложений со сложным интерфейсом, рассмотрены вопросы маршрутизации в приложениях и управление их состоянием. Даны при- меры реализации интерактивного взаимодействия с пользователем, подключения к различным службам бэкенда, таким как В.Е8Т и ОгарЬОЬ, описана работа с биб- лиотеками компонентов. Подробно рассматривается безопасность приложений, процесс их тестирования, даны советы по обеспечению доступности. Приводятся практические рекомендации по повышению производительности и созданию про- грессивных веб-приложений. Для программистов УДК 004.438 ББК 32.973.26-018.1 Группа подготовки издания: Руководитель проекта Зав редакцией Компьютерная верстка Оформление обложки Павел Шалим Людмила Гауль Ольги Сергиенко Зои Канторович © 2022 ВНУ АиФопхеб Ки5в1ап НагЫаНоп оГШе Еп^ИзЬ еФ(юп оГКеаа СоокЬоок 18ВК 9781492085843 © 2021 Оаип ОпГГиЬв апд ОаУ14 ОгйГнЬх ТЫв 1гап$1а1юп 15 риЬКвЬеб апс! зоШ Ьу рептпзвюп оГО’ВеШу МеФа, 1пс, ууЫсЬ охупв ог соп1го1в а!1 Ю риЫгвй аш!ве11Йге вате. Авторизованный перевод с английского языка на русский издания Кеас1 СоокЬоок 18Вк 9781492085843 © 2021 Оахуп ОпГГнЬв апд Оаузб бпГГнЬв. Перевод опубликован и продается с разрешения компании-правообладателя О’КегПу МеФа, 1пс Подписано в печать 03 10 22 Формат 70x1001/16 Печать офсетная Усл печ л 42,57 Тираж 1300 экз Заказ № 5394. "БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул , 20 Отпечатано с готового оригинал-макета ООО"Принт-М", 142300, МО , г. Чехов, ул Полиграфистов, д 1 18ВК 978-1-492-08584-3 (англ.) 18ВК 978-5-9775-6839-5 (рус.) © Оэхуп ОпДйЬз, ВаУ1с! 6пЯНЬ$, 2021 © Перевод на русский язык, оформление ООО "БХВ-Петербург", ООО "БХВ”, 2023
Оглавление Введение............................................................13 Типографские соглашения.............................................15 Использование примеров кода.........................................15 Возможности онлайнового обучения от компании 0’К.еШу................16 Как связаться с нами................................................16 Благодарности.......................................................17 Глава 1. Создание приложений........................................19 1.1. Создаем простое приложение.....................................19 ЗАДАЧА...........................................................19 РЕШЕНИЕ..........................................................19 Обсуждение.......................................................23 1.2. Создание приложений с обширным информационным наполнением посредством СаТзЬу...............................................24 ЗАДАЧА...........................................................24 РЕШЕНИЕ..........................................................24 Обсуждение.......................................................27 1.3. Создание универсальных приложений с помощью Каххк..............28 ЗАДАЧА...........................................................28 РЕШЕНИЕ..........................................................28 Обсуждение.......................................................30 1.4. Создание серверного и клиентского кода посредством Кех1.]з.....30 ЗАДАЧА...........................................................30 РЕШЕНИЕ..........................................................31 Обсуждение.......................................................32 1.5. Создание крошечных приложений посредством Ргеас!...............33 ЗАДАЧА...........................................................33 РЕШЕНИЕ..........................................................33 Обсуждение.......................................................36 1.6. Создание библиотек посредством набора инструментов тчЬ.........37 ЗАДАЧА...........................................................37 РЕШЕНИЕ..........................................................37 Обсуждение.......................................................39 1.7. Добавление Кеас! в код КаПз посредством ХУеЬраскег.............39 ЗАДАЧА...........................................................39 РЕШЕНИЕ..........................................................39 Обсуждение.......................................................41 1.8. Создание пользовательских элементов посредством РгеасТ.........41 ЗАДАЧА...........................................................41 РЕШЕНИЕ..........................................................41 Обсуждение.......................................................44
1.9. Разработка компонентов посредством 81огуЬоок...................45 ЗАДАЧА...........................................................45 РЕШЕНИЕ..........................................................45 Обсуждение.......................................................48 1.10. Тестирование кода в браузере посредством Сургезз..............48 ЗАДАЧА...........................................................48 РЕШЕНИЕ..........................................................48 Обсуждение.......................................................49 Глава 2. Маршрутизация..............................................51 2.1. Создание интерфейсов, используя реагирующую маршрутизацию......51 ЗАДАЧА...........................................................51 РЕШЕНИЕ..........................................................52 Обсуждение.......................................................58 2.2. Размещение состояния в маршрутах...............................59 ЗАДАЧА...........................................................59 РЕШЕНИЕ..........................................................60 Обсуждение.......................................................64 2.3. Модульное тестирование посредством МетогуКои1ег................65 ЗАДАЧА...........................................................65 РЕШЕНИЕ..........................................................66 Обсуждение.......................................................67 2.4. Подтверждение ухода со страницы посредством компонента Рготр!..68 ЗАДАЧА...........................................................68 РЕШЕНИЕ..........................................................69 Обсуждение.......................................................75 2.5. Создание переходов посредством библиотеки Кеас( Тгапзйюп Сгоир.75 ЗАДАЧА...........................................................75 РЕШЕНИЕ..........................................................76 Обсуждение.......................................................81 2.6. Создание защищенных маршрутов..................................81 ЗАДАЧА...........................................................81 РЕШЕНИЕ..........................................................82 Обсуждение.......................................................86 Глава 3. Управление состоянием......................................87 3.1. Управление сложным состоянием посредством преобразователей.....87 ЗАДАЧА...........................................................87 РЕШЕНИЕ..........................................................88 Обсуждение.......................................................94 3.2. Создание возможности "Отмена"..................................95 ЗАДАЧА...........................................................95 РЕШЕНИЕ..........................................................95 Обсуждение......................................................101 3.3. Создание форм и проверка действительности их данных...........102 ЗАДАЧА..........................................................102 РЕШЕНИЕ.........................................................102 Обсуждение.......................................................ПО 3.4. Часы для измерения времени....................................110 ЗАДАЧА...........................................................НО РЕШЕНИЕ..........................................................ПО Обсуждение......................................................113
3.5. Мониторинг состояния сетевого подключения.............................114 ЗАДАЧА..................................................................114 РЕШЕНИЕ.................................................................114 Обсуждение..............................................................116 3.6. Управление глобальным состоянием посредством библиотеки Кедих.........116 ЗАДАЧА..................................................................116 РЕШЕНИЕ.................................................................116 Обсуждение..............................................................122 3.7. Сохранение состояния при обновлении страниц посредством Кедих Рег818(.123 ЗАДАЧА..................................................................123 РЕШЕНИЕ.................................................................124 Обсуждение..............................................................127 3.8. Вычисление производного состояния посредством Ке$е1ес1................127 ЗАДАЧА..................................................................127 РЕШЕНИЕ.................................................................128 Обсуждение..............................................................131 Глава 4. Проектирование для обеспечения интерактивности....................133 4.1. Создание центрального обработчика ошибок..............................133 ЗАДАЧА..................................................................133 РЕШЕНИЕ.................................................................134 Обсуждение..............................................................138 4.2. Создаем интерактивное справочное руководство..........................139 ЗАДАЧА..................................................................139 РЕШЕНИЕ.................................................................139 Обсуждение..............................................................146 4.3. Сложные взаимодействия посредством преобразователей...................146 ЗАДАЧА..................................................................146 РЕШЕНИЕ.................................................................147 Обсуждение..............................................................153 4.4. Взаимодействие с клавиатурой..........................................153 ЗАДАЧА..................................................................153 РЕШЕНИЕ.................................................................153 Обсуждение..............................................................156 4.5. Создание насыщенного содержимого посредством редактора Магкскпуп......156 ЗАДАЧА..................................................................156 РЕШЕНИЕ.................................................................156 Обсуждение..............................................................161 4.6. Анимация посредством классов С88......................................161 ЗАДАЧА..................................................................161 РЕШЕНИЕ.................................................................161 Обсуждение..............................................................163 4.7. Анимация средствами КеасГ.............................................163 ЗАДАЧА..................................................................163 РЕШЕНИЕ.................................................................164 Обсуждение..............................................................168 4.8. Анимация информационной графики посредством библиотеки ТЧуеепОпе......168 ЗАДАЧА..................................................................168 РЕШЕНИЕ.................................................................169 Обсуждение..............................................................175
Глава 5. Подключение к службам.................................................176 5.1. Преобразование сетевых вызовов в хуки..........................176 ЗАДАЧА...........................................................176 РЕШЕНИЕ..........................................................177 Обсуждение.......................................................182 5.2. Автоматическое обновление посредством счетчиков состояния......183 ЗАДАЧА...........................................................183 РЕШЕНИЕ..........................................................184 Обсуждение.......................................................191 5.3. Отмена сетевых запросов посредством маркеров...................192 ЗАДАЧА...........................................................192 РЕШЕНИЕ..........................................................192 Обсуждение.......................................................195 5.4. Сетевые вызовы посредством Кедах...............................195 ЗАДАЧА...........................................................195 РЕШЕНИЕ..........................................................196 Обсуждение.......................................................201 5.5. Подключение к СгарИрЬ..........................................202 ЗАДАЧА...........................................................202 РЕШЕНИЕ..........................................................202 Обсуждение.......................................................209 5.6. Уменьшение сетевой нагрузки при помощи очищенных запросов......210 ЗАДАЧА...........................................................210 РЕШЕНИЕ..........................................................211 Обсуждение.......................................................213 Глава 6. Библиотеки компонентов................................................214 6.1. Использование библиотеки Ма1епа11Эе8щп совместно с библиотекой Ма!спа1-1Л.215 ЗАДАЧА...........................................................215 РЕШЕНИЕ..........................................................215 Обсуждение.......................................................222 6.2. Простой пользовательский интерфейс посредством Кеас! Воо!$1гар.223 ЗАДАЧА...........................................................223 РЕШЕНИЕ..........................................................223 Обсуждение.......................................................227 6.3. Просмотр наборов данных посредством окна Кеас1 ХУшдои..........227 ЗАДАЧА...........................................................227 РЕШЕНИЕ..........................................................227 Обсуждение.......................................................230 6.4. Создание реагирующих диалоговых окон посредством библиотеки МаСепаМЛ......230 ЗАДАЧА...........................................................230 РЕШЕНИЕ..........................................................230 Обсуждение.......................................................232 6.5. Создание консоли администратора посредством Кеас! Аёпнп........233 ЗАДАЧА...........................................................233 РЕШЕНИЕ..........................................................233 Обсуждение.......................................................240 6.6. Использование Бетапбс 1Л вместо дизайнера......................240 ЗАДАЧА...........................................................240 РЕШЕНИЕ..........................................................241 Обсуждение.......................................................246
Глава 7. Безопасность.............................................247 7.1. Защищаем запросы, а не маршруты..............................247 ЗАДАЧА.........................................................247 РЕШЕНИЕ........................................................247 Обсуждение.....................................................255 7.2. Аутентификация посредством физических ключей.................256 ЗАДАЧА.........................................................256 РЕШЕНИЕ........................................................256 Обсуждение.....................................................266 7.3. Работа с протоколом НТТР8....................................267 ЗАДАЧА.........................................................267 РЕШЕНИЕ........................................................267 Обсуждение.....................................................271 7.4. Аутентификация посредством отпечатка пальца..................271 ЗАДАЧА.........................................................271 РЕШЕНИЕ........................................................271 Обсуждение.....................................................276 7.5. Подтверждение действий, предоставляя учетные данные..........277 ЗАДАЧА.........................................................277 РЕШЕНИЕ........................................................277 Обсуждение.....................................................283 7.6. Однофакторная аутентификация.................................284 ЗАДАЧА.........................................................284 РЕШЕНИЕ........................................................284 Обсуждение.....................................................288 7.7. Проверка приложения на устройстве Апдгок!....................289 ЗАДАЧА.........................................................289 РЕШЕНИЕ........................................................290 Обсуждение.....................................................291 7.8. Проверка безопасности посредством Е81ш1......................291 ЗАДАЧА.........................................................291 РЕШЕНИЕ........................................................292 Обсуждение.....................................................295 7.9. Удобные для браузера формы входа в систему...................296 ЗАДАЧА.........................................................296 РЕШЕНИЕ........................................................296 Обсуждение.....................................................297 Глава 8. Тестирование.............................................299 8.1. Работа с библиотекой КеасС Тез1т§ ЫЬгагу.....................300 ЗАДАЧА.........................................................300 РЕШЕНИЕ........................................................301 Обсуждение.....................................................307 8.2. Использование 81огуЬоок для тестирования отрисовки...........308 ЗАДАЧА.........................................................308 РЕШЕНИЕ........................................................308 Обсуждение.....................................................314 8.3. Тестирование без сервера посредством Сургезз.................315 ЗАДАЧА.........................................................315 РЕШЕНИЕ........................................................315 Обсуждение.....................................................322
8.4. Использование Сургезз для офлайнового тестирования.....................323 ЗАДАЧА...................................................................323 РЕШЕНИЕ..................................................................323 Обсуждение...............................................................326 8.5. Использование 8е1епшт для тестирования в браузере......................326 ЗАДАЧА...................................................................326 РЕШЕНИЕ..................................................................327 Обсуждение...............................................................333 8.6. Тестирование внешнего вида на разных браузерах посредством 1та^еМа§1ск.334 ЗАДАЧА...................................................................334 РЕШЕНИЕ..................................................................334 Обсуждение...............................................................341 8.7. Добавление консоли в браузер мобильного устройства.....................342 ЗАДАЧА...................................................................342 РЕШЕНИЕ..................................................................342 Обсуждение...............................................................345 8.8. Удаление произвольности из тестов......................................346 ЗАДАЧА...................................................................346 РЕШЕНИЕ..................................................................347 Обсуждение...............................................................349 8.9. Путешествие во времени.................................................350 ЗАДАЧА...................................................................350 РЕШЕНИЕ..................................................................350 Обсуждение...............................................................355 Глава 9. Доступность специальных возможностей...............................356 9.1. Использование ориентиров...............................................357 ЗАДАЧА...................................................................357 РЕШЕНИЕ..................................................................359 Обсуждение...............................................................362 9.2. Применение ролей.......................................................362 ЗАДАЧА...................................................................362 РЕШЕНИЕ..................................................................363 Обсуждение...............................................................371 9.3. Проверка доступности посредством Е81т1.................................372 ЗАДАЧА...................................................................372 РЕШЕНИЕ..................................................................372 Обсуждение...............................................................378 9.4. Динамический анализ посредством ахе ОеуТоо1з...........................379 ЗАДАЧА...................................................................379 РЕШЕНИЕ..................................................................379 Обсуждение...............................................................383 9.5. Автоматизация тестирования в браузере посредством Сургезз Ахе..........384 ЗАДАЧА...................................................................384 РЕШЕНИЕ..................................................................384 Обсуждение...............................................................387 9.6. Добавление в страницу кнопок пропуска содержимого......................388 ЗАДАЧА...................................................................388 РЕШЕНИЕ..................................................................389 Обсуждение...............................................................394
9.7. Добавление возможности пропуска областей страницы...............395 ЗАДАЧА............................................................395 РЕШЕНИЕ...........................................................396 Обсуждение........................................................404 9.8. Захват области действия в модальных окнах.......................404 ЗАДАЧА............................................................404 РЕШЕНИЕ...........................................................406 Обсуждение........................................................407 9.9. Создание считывателя экрана посредством ЗреесЬ АР1..............407 ЗАДАЧА............................................................407 РЕШЕНИЕ...........................................................408 Обсуждение........................................................412 Глава 10. Производительность.........................................413 10.1. Браузерные средства настройки производительности...............414 ЗАДАЧА...........................................................414 РЕШЕНИЕ..........................................................414 Обсуждение.......................................................421 10.2. Слежение за отрисовкой посредством Рго/Иег.....................421 ЗАДАЧА...........................................................421 РЕШЕНИЕ..........................................................422 Обсуждение.......................................................427 10.3. Создание модульных тестов с Рго/Пег............................427 ЗАДАЧА...........................................................427 РЕШЕНИЕ..........................................................427 Обсуждение.......................................................431 10.4. Точное измерение времени.......................................432 ЗАДАЧА...........................................................432 РЕШЕНИЕ..........................................................433 Обсуждение.......................................................434 10.5. Уменьшение размера приложений посредством разделения кода......436 ЗАДАЧА...........................................................436 РЕШЕНИЕ..........................................................437 Обсуждение.......................................................442 10.6. Объединение сетевых обещаний...................................443 ЗАДАЧА...........................................................443 РЕШЕНИЕ..........................................................444 Обсуждение.......................................................446 10.7. Отрисовка на стороне сервера...................................447 ЗАДАЧА...........................................................447 РЕШЕНИЕ..........................................................447 Обсуждение.......................................................457 10.8. Использование основных показателей веб-производительности......458 ЗАДАЧА...........................................................458 РЕШЕНИЕ..........................................................459 Обсуждение.......................................................461 Глава 11. Прогрессивные веб-приложения...............................462 11.1. Создаем сервис-воркеры посредством АУогкЪох....................462 ЗАДАЧА...........................................................462 РЕШЕНИЕ..........................................................465 Обсуждение.......................................................479
11.2. Создание прогрессивных веб-приложений посредством Сгеа(е Кеас! Арр...480 ЗАДАЧА.................................................................480 РЕШЕНИЕ................................................................480 Обсуждение.............................................................483 11.3. Кеширование сторонних ресурсов.......................................483 ЗАДАЧА.................................................................483 РЕШЕНИЕ................................................................483 Обсуждение.............................................................487 11.4. Автоматическая перезагрузка воркеров.................................487 ЗАДАЧА.................................................................487 РЕШЕНИЕ................................................................489 Обсуждение.............................................................492 11.5. Добавление извещений.................................................492 ЗАДАЧА.................................................................492 РЕШЕНИЕ................................................................493 Обсуждение.............................................................499 11.6. Модификации в режиме офлайн посредством фоновой синхронизации........500 ЗАДАЧА.................................................................500 РЕШЕНИЕ................................................................500 Обсуждение.............................................................505 11.7. Добавляем специализированный установочный пользовательский интерфейс.506 ЗАДАЧА.................................................................506 РЕШЕНИЕ................................................................507 Обсуждение.............................................................510 11.8. Предоставление ответов в режиме офлайн...............................511 ЗАДАЧА.................................................................511 РЕШЕНИЕ................................................................512 Обсуждение.............................................................515 Предметный указатель.......................................................517 Об авторах.................................................................525 Об обложке.................................................................526
Введение Эта книга содержит коллекцию фрагментов кода, которые в результате многолет- него опыта мы считаем полезными для разработки приложений Кеас1. Подобно кулинарным рецептам, эти примеры предназначены для использования в качестве отправной точки при создании собственных программ. Их следует модифицировать под конкретную ситуацию и заменять любые составляющие части (например, сер- вер примеров) на те, которые лучше отвечают вашим требованиям. Приведен об- ширный ассортимент рецептов — от общих советов по разработке веб-приложений до фрагментов кода более большого размера, которые можно обобщить в библио- теки. Большинство примеров созданы с помощью приложения Сгеа1е К.еас1 Арр. В на- стоящее время это распространенный подход при разработке проектов Кеас1. Каж- дый рецепт можно легко преобразовать для Ргеас! или 6а1$Ьу. Для сокращения объема кода мы обычно использовали хуки (Ьоокз) и функции, а не классовые компоненты. Мы также применяли средство РгеШег с настройками по умолчанию, чтобы наложить стандартное форматирование на весь код. Единствен- ное изменение состоит в уменьшении отступов и укорочении строк, чтобы помес- тить листинги на книжную страницу. Вы можете настроить формат кода в соответ- ствии со своими предпочтениями. В табл. 1 приведены необходимые инструменты и библиотеки. Таблица 1 Средство или библиотека Описание Версии АроНо СПеп1 Клиент СгарНОЬ 3.3.19 ах105 Библиотека НТТР 0.21.1 сНа! Вспомогательная библиотека для проверки модулей 4.3.0 сИготедпуег Инструмент для автоматизации проверки работы приложений с разными браузерами 88.0.0 Сгеа!е Кеас< Арр Инструмент для создания приложений Кеас! 4.0.3 Сургезз Автоматизированная система тестирования 7.3.0 Сургезз Ахе Авторизированная система тестирования доступности 0.12.2 6а1зЬу Инструмент для создания приложений Кеас! 3.4.1
Таблица 1 (продолжение) Средство или библиотека Описание Версии БгарЬОЬ Язык запросов для АР1-интерфейсов 15.5.0 ]зх-а11у Подключаемый модуль ЕЗЫп! для обеспечения доступности 6.4.1 Ма1епа1-1Л Библиотека компонентов 4.11.4 Мобе Среда исполнения для бауа8спр{ У12.20.0 прт Диспетчер пакетов для Мобе 6.14.8 гмт Инструмент для исполнения нескольких сред Мобе 0.33.2 ПУ/Ь Инструмент для создания приложений Реас! 0.25 х №Х1.]8 Инструмент для создания приложений Реас! 10.2.0 Ргеас! Облегченная версия фреймворка наподобие Реас! 10.3.2 Ргеас! Сиз1от Е1етеп1з Библиотека для создания пользовательских элементов 4.2.1 ргезе!-сгеа1е-геас1-арр Подключаемый модуль для 8!огуЬоок 3.1.7 РаЛз Фреймворк для разработки веб-приложений 6.0.3.7 Рагг1е Инструмент для создания приложений Реас! 4 0.4 Реас! Фреймворк для разработки веб-приложений 17.0.2 Реас! МесИа Компонент С88 для медиавыражений в коде Реас! 1.10.0 Реас1 Рои1ег (ООМ) Библиотека для управления маршрутами Реас! 5.2.0 Реас! ТезГтд Ыэгагу Библиотека для тестирования модулей Реас! 11.1.0 геас1-ап1тайопз Библиотека С88-анимации для Реас! 1 0.0 Реас1 Росиз коек Библиотека для захвата фокуса клавиатуры 2.5.0 геас1-тд-ед|(ог Редактор облегченного языка разметки Магкбо\л/п 3.3.6 Кеа с!-Кед их Библиотека поддержки Реас! для Ребих 7.2.2 Ребих Библиотека управления состояниями 4.0.5 Ребих-Регз1з1 Библиотека для хранения состояния Ребих 6.0.0 РиЬу Язык, используемый во фреймворке РаЛз 2.7.0р0 зе1егпит-\л/еЬс1гК/ег Фреймворк для тестирования веб-приложений в разных браузерах 4.0.0-Ье!а.1 81огуЬоок Система галереи компонентов 6.2.9 ТмеепОпе Библиотека анимации для Реас! 2.7.3 Турезспр! Расширения для обеспечения безопасности типов для бауа8спр! 4.1 2 \Л/еЬраскег Инструмент для добавления Реас! в приложения Райе 4.3.0 \Л/огкЬох Библиотека для создания сервис-воркеров 5.1.3 Уагп Диспетчер пакетов для Мобе 1.22.10
Типографские соглашения В книге используются следующие типографские соглашения: ♦ Курсив — для обозначения новых терминов, адресов ЦКЬ и электронной почты, названий и расширений файлов. ♦ моноширинный шрифт — для листингов программ, а также в тексте для обозначения элементов программ, например названий функций, баз данных, типов данных, переменных среды, операторов и ключевых слов. ♦ моноширинный полузюфный шрифт — для обозначения команд или другого текста, ко- торый вводится пользователем. ♦ Моноширинный курсив — для обозначения текста, который нужно заменить предос- тавляемыми пользователем значениями или значениями, определяемыми кон- текстом. Данное изображение обозначает подсказку или совет. Данное изображение обозначает общее замечание. Данное изображение обозначает предупреждение или предостережение. Использование примеров кода Дополнительный материал для книги (примеры кода, упражнения и т. п.) можно загрузить по адресу Ьйр8://§ИЬиЬ.сот/(1оёпЯкЬ$/Кеас<СоокЬоок-$оигсе. Вопросы по техническим аспектам примеров кода или проблемам с их использова- нием можно задать, отправив сообщение электронной почтой по адресу Ьоокцие8Йоп@огеПу.сот. Эта книга предназначена для того, чтобы помочь вам реализовать свои проекты. В общем, предоставляемые в этой книге примеры кода можно использовать в своих программах и документации, не спрашивая нашего разрешения, если только речь не идет о значительном объеме кода. Например, для использования нескольких фрагментов кода из этой книги в своей программе разрешения не требуется, а вот для продажи или распространения примеров из книг издательства О'КеШу такое разрешение необходимо. Цитирование теста из этой книги, включая код, разреше- ния не требует, но для включения значительного объема кода из книги в докумен- тацию своего продукта требуется разрешение.
При цитировании материалов из наших книг мы будем признательны за предостав- ление ссылки на источник, но обычно не требуем этого. Формат такой ссылки мо- жет включать название, имя автора, издателя и 18ВИ книги. Например: "Кеас( СоокЬоок Ьу Паухд СпГШЬз апс! Оа\\п СгхШШз (О'КеШу). Соруп^Ы 2021 Па\уп ОпйШ18 апд 1)ауШ бпйШв, 978-1-492-08584-3”. Если вы подозреваете, что ваше заимствование примеров кода может выходить за рамки принципа добросовестного использования или данных выше разрешений, то можете уточнить этот вопрос электронной почтой по адресу рег1т$81оп5@огеШу.сот. Возможности онлайнового обучения от компании О'КеШу В течение свыше 40 лет компания О'КеШу МесНа предоставляет возможности обу- чения в области технологий и бизнеса, распространяя знания и сведения, помогаю- щие компаниям добиваться успеха. Мы создали уникальное сообщество экспертных специалистов и новаторов, кото- рые делятся своими знаниями и опытом посредством книг, статей и нашей онлай- новой обучающей платформы. Онлайновая система обучения компании О’КеШу предоставляет доступ по запросу к учебным курсам в режиме реального времени, разнообразным обучающим возможностям, интерактивным средам кодирования, а также к огромному собранию печатного и видеоматериала от самой компании и от свыше 200 других издательств. Чтобы получить дополнительную информацию, посетите наш веб-сайт по адресу ЬКр://огеШу.сот. Как связаться с нами Если у вас есть какие-либо замечания или вопросы по этой книге, вы может задать их издателю по следующему адресу: О'КеШу МесНа, 1пс. 1005 6гауеп81ет НщЬ\\ау Ыог111 8еЬа$1оро1, СА 95472 800-998-9938 (в США или Канаде) 707-829-0515 (международный или местный) 707-829-0104 (факс) Для этой книги создан веб-сайт, который содержит список ошибок, там же приво- дятся примеры и предоставляется другая дополнительная информация. Веб-сайт находится по адресу ЬНр$://огеП.1у/геас1-сЬ. Свои комментарии о книге и технические вопросы присылайте электронной почтой по адресу Ъоокдие$йоп8@огеП1у.сот.
Чтобы узнать последние новости и информацию о наших книгах и курсах, посетите наш веб-сайт по адресу ЬНр://огеН1у.сош. Мы на ГасеЬоок: ЬНр://ГасеЬоок.сот/огеИ1у. И в ТчуЩег: ЬНр://Ьу1Нег.сот/оге1Путе(11а. А также на УоиТиЬе: Ы<р:/Лпулу.уои(иЬе.сот/оге111уте<11а. Благодарности Мы хотим поблагодарить нашего очень терпеливого редактора Корбина Коллинса (СогЫп СоПтз) за его помощь и советы в течение последнего года. Его спокойствие и доброжелательность были стабилизирующими факторами в процессе подготовки материала этой книги. Мы также хотим выразить свою благодарность главному ре- дактору компании 0’К.еШу Аманде Куинн (Атапда (}итп) за приобретение нашей книги, а также Дэнни Эльфанбауму (Оаппу ЕИапЬашп) и производственной бригаде компании О'КеШу за воплощение в реальность физической и электронной версий книги. Особое спасибо хотим сказать Сэму Уорнеру (8ат ХУагпег) и Марку Хобсону (Магк НоЬзоп) за их скрупулезную проверку материала в данной книге. Мы также благодарны разработчикам многих библиотек с открытым кодом, под- держивающих экосистему Кеас1. Огромное спасибо им всем, особенно за их опера- тивное реагирование на сообщения об ошибках или просьбы о помощи. Если наши рецепты окажутся полезными для вас, то это в первую очередь благода- ря работе этих людей. Но ответственность за все ошибки в коде и тексте мы берем полностью на себя.

Создание приложений Фреймворк Кеас1 — на удивление гибкая платформа разработки. Разработчики мо- гут использовать ее для создания как одностраничных приложений (8т§1е-Ра§е АррПсабоп— 8РА), с большим содержанием кода 1ауа8спр1, так и поразительно небольших подключаемых модулей. С ее помощью можно также вставлять код в приложения КаПз или создавать веб-сайты с богатым информационным наполне- нием. В этой главе мы рассмотрим разные способы создания приложений КеасТ, а также некоторые полезные инструменты, которые пригодятся вам в процессе разработки. Очень немногие разработчики начинают свои проекты 1ауа8спр1 с чистого листа, т. к. это длительный и трудоемкий процесс, требующий большого объема настроек и "танцев с бубном". К счастью, почти в каждом случае требуемый программный код можно создать с помощью специального инструмента. Вкратце рассмотрим эти многочисленные способы создания начального приложе- ния КеасГ, начиная с наиболее часто применяемого средства: сгеа<:е-геас1:-арр. 1.1. Создаем простое приложение ЗАДАЧА Приложения Кеас1 трудно создавать и настраивать с чистого листа. Это обусловле- но необходимостью принятия многих предварительных решений: какие использо- вать библиотеки, средства, возможности языка и т. п. Кроме того, природа создан- ных вручную приложений такова, что они отличаются одно от другого. Специфи- ческие особенности конкретных проектов увеличивают время, необходимое для того, чтобы новый разработчик вышел на эффективный уровень работы. РЕШЕНИЕ Средство сгеаЬе-геас^-арр предназначено для создания одностраничных приложе- ний с типовой структурой и оптимальным набором стандартных опций. Для созда- ния, тестирования и исполнения кода созданных таким образом проектов предна- значена библиотека Кеас( 8спр1з. В проектах задаются стандартные настройки сборщика пакетов ХУеЬраск и подключается стандартный набор языковых возмож- ностей.
Всякий разработчик, которому приходилось сталкиваться с одним приложением, созданным посредством инструмента сгеа^е-геасг-арр, сразу же может начать рабо- ту с любым другим таким приложением без каких бы то ни было проблем. Он по- нимает структуру проекта и знает, какие возможности языка можно использовать. Приложение содержит все возможности, присущие типичному приложению: от конфигурации компилятора ВаЬе! и загрузчиков файлов до библиотек для тестиро- вания и сервера для разработки. Если вам никогда раньше не приходилось работать с Кеас! или нужно создать типичное одностраничное приложение с минимальными усилиями, тогда мы сове- туем вам рассмотреть применение средства сгеаье-геас^-арр для разработки своего приложения. Один из вариантов использования средства сгеа1:е-геасх-арр — установить его гло- бально на своем компьютере, но в настоящее время такой подход не рекомендует- ся. Вместо этого следует создавать новые проекты, вызывая сгеа^е-геасх-арр через утилиту прх для запуска прт пакетов. Такой подход обеспечивает создание прило- жения с помощью самой последней версии средства сгеа^е-геасх-арр: $ прх сгеа^е-геасХ-арр ту-арр Исполнение этой команды создает папку с новым проектом, называющуюся ту-арр. По умолчанию в приложении задан .1а\а8спр1. Если требуется поменять язык на Туре8спр1, то вызов прх осуществляется следующим образом: $ прх сгеа1:е-геасХ-арр —Ретр1а1:е 1:урезсг1рЁ ту-арр Средство сгеапе-геасх-арр было разработано компанией ЕасеЬоок, поэтому не уди- вительно, что если у вас установлен диспетчер пакетов уагп, то в новом проекте он будет назначен по умолчанию. Чтобы задействовать диспетчер пакетов прт, можно или указать флаг —изе-прт, или же перейти в папку проекта, удалить файл уагп.1оск, а затем снова выполнить установку, используя прт: $ сд ту-арр $ гт уагп.1оск $ прт 1пз1:а].1 Для запуска созданного приложения нужно выполнить следующую команду: $ прт зЁагЁ # или уагп з^адХ В результате запускается веб-сервер на порту 3000 и в браузере открывается до- машняя страница, как показано на рис. 1.1. Сервер предоставляет приложение в виде одного большого пакета кода 1ауа8спр(. Код устанавливает все свои компоненты в этом теге «М> в файле риЫгс/ тдех.Ьйп!: <сИу 1с1="госХ"></с11^> Начальный код для генерирования компонентов приложения находится в файле 8гс/тс1ех.]8 (или в файле 8гс/тс1ех18х, если используется Туре8спр1). Этот код при- веден в листинге 1.1.
ЕдП 5 ГС/Арр. ] 5 апсХзауе ко ге1оад I Рис. 1.1. Домашняя страница нового приложения код для генерирования компонентов приложения . хи — • - ы х—. — а. - - . __________ _ . .... . - нпрогХ Кеасб Г г от 'геас^' йпрогХ КеасбООМ 1гот 'геасС-склп' нпрогЁ '. /1пс1ех. сзз' гтрогк. Арр ^гот ’./Арр' 1трогХ герохХйеЬ71ба1з Г г от ’./герогбКеЬАЛ^аХз' Кеас^ЕЮМ.гепбег( <КеасЪ. 5бг1сбМос1е> <Арр /> </Кеасб. ЗСг1сбМос1е>, доситепЁ. деРЕ1етепЁВу1с1 (’ гооб ’) ) // Если вы хотите выполнять замеры поддерживаемых метрик // производительности в своем приложении, передайте соответствующую // функцию в функцию герогбКеЬ71Ра1з(), чтобы сохранить результаты // в журнале (например, герогТИекАМХаТз(сопзо1е.1од)), // или же отправьте ее на узел аналитики (Соод1е). // Дополнительную информацию см. по адресу: ккХрз://ЫХ.1у/СВА-У11:а1з герогХНеЬ71Ха1з() Код из листинга 1.1 всего лишь прорисовывает один компонент, называющийся <Арр/>, который он импортирует из файла Арр.]з (или АррЛзх), находящегося в этой же папке. Код этого компонента приведен в листинге 1.2.
Листинг 1.2. Код компонента <л₽р/> ? Лгсрог!: 1одо Хгот ’ ./Ходо.зуд’ 1шрогХ ’./Арр.сзз' ЦшсИоп Арр () { геХигп ( <с11У с1аз5Мате=,’Арр"> <Неабег с1аззЫате="Арр-кеас1ег"> <1тд згс={1одо} с1аззЫате=’,Арр-1одо" а1±="1одо" /> <р> ЕсНЛ <сос!е>згс/Арр. ]5</сос1е> апд 5аVе Ьо геХоад. </р> <а с1аззМате="Арр-11пк" Ьге{="кХЪрз://геасХ]з.огд" <:а^де^=''_Ыапк’, ге1=”поорепег погеГеггег" > Ьеагп Кеас± </а> </Ьеадег> </с!гу> ехрогХ с!е€аи1Х Арр Браузер автоматически отображает любые правки в этом файле при исполняющем- ся приложении, обновляя страницу. После завершения разработки приложения, чтобы создать рабочий код, нужно сге- нерировать набор статических файлов, которые затем можно разместить на обыч- ном веб-сервере. Эта задача решается с помощью команды Ьипа: $ прш гип ЬиНс! В результате исполнения этой команды создается папка ЬшИ, в которой публикует- ся набор статических файлов (рис. 1.2). ь с о ам <л г- с с ° с 8Ё аай,*- с ~ ё сч й й — ? с-Я о» >-• « м Л5 "О » : тз а о -5 Ч Й * Е 3 я 45 с о о Е 2 5 “Г, '5 I Г I Т 1 » 1 Г-1 1—1 Рис. 1.2. Содержимое папки ЬиНс!
Многие из файлов в папке Ьш1д копируются из папки риЫю/. Код для приложения транспилировался1 в код 1ауа8спр1, совместимый с браузерами, и был сохранен в одном или нескольких файлах в папке 81абс/]8. Используемые приложением таблицы стилей собраны вместе и сохранены в папке 81абс/с88. К названиям некоторых из этих файлов добавлены идентификационные хеши для того, чтобы после выкладки приложения браузеры могли загружать самый свежий код, а не старые кешированные версии. Обсуждение Средство сгеаге-геас1:-арр не позволяет создавать новые приложения, но также слу- жит платформой для поддержания приложений Кеас! в актуальном состоянии, с самыми последними версиями инструментов и библиотек. Библиотеку сценариев геасТ можно обновить, как любую другую библиотеку, изменив номер версии и вы- полнив повторно команду прт хпзъаП. Пользователю не требуется самостоятельно управлять списком подключаемых модулей (р1и§т) компилятора ВаЬе1 или биб- лиотек средства Роз1С88 или поддерживать в актуальном состоянии файл \уеЬраск.сопП§.]8. Все эти задачи выполняются библиотекой геаср-зсгхр^з. Конечно же, все конфигурационные данные никуда не делись, но они спрятаны глубоко внутри папки библиотеки геась-зсгтрГ-Б, а именно в подпапке соп(щ. В этой же подпапке также находится файл \уеЬраск.сопй§.]8, который содержит информа- цию обо всех настройках компилятора ВаЬе1 и загрузчиках файлов, которые будут необходимы приложению. Поскольку Кеас1 8спр18 является библиотекой, ее можно обновлять как любую другую зависимость. Но если в дальнейшем вы решите, что больше не нуждаетесь в помощи для управ- ления настройками своего приложения, то можете удалить свое приложение из Среды сгеа1:е-геас!:-арр, ВЫПОЛНИВ команду е^есЁ. $ прт гип е^ес^ После выполнения этой команды вы получите полный контроль над настройками своего приложения. Но имейте в виду, что если затем вы обнаружите, что не совсем готовы к этому, то пути назад нет. Поэтому прежде чем удалять свое приложение из среды сгеаЁе-геас1:-арр, нужно хорошенько подумать. В некоторых случаях не обязательно задавать команду е^есЁ, чтобы получить возможность выполнять тре- буемые настройки, т. к. они могут быть доступны в среде сгеаие-геас1;-арр. Напри- мер, разработчики часто выполняют команду е]ес±, чтобы получить возможность работать с Туре8спр1. Но теперь то же самое можно сделать, назначив опцию —ьетр1аЁе 1:урезсг1р1: при создании своего приложения с помощью средства сгеа11е- геас1-арр. Другой распространенной причиной выхода из среды сгеа<:е-геас1:-арр была необхо- димость предоставления веб-служб через прокси-сервер. Приложениям Кеас!: часто 1 ТгапзрПег — транспилер (транспайлер, транспилятор), компилятор, преобразующий исходный код на одном языке программирования в исходный код на другом язык программирования. —Прим. пер.
требуется подключаться к какому-то отдельному серверному АР1-процессу. Раньше для этого разработчики настраивали сборщик пакетов УУеЬраск для предоставления доступа к удаленному серверу через прокси-сервер на локальном сервере для разработки. Теперь же вместо этого прокси-сервер можно указывать в файле раска§е.]зоп: "ргоху": "ЬЬГ.р: //туар15егуег", Если теперь код обращается к какому-либо адресу ИКЕ, который сервер не может найти на локальной машине (например, /арйЛЫп^), библиотека геасЁ-зсггр^з авто- матически направляет эти запросы на прокси-сервер по адресу ЬНр://туар18егуег/ ар1/(Ь1пц. С) Прежде чем удалять разрабатываемое приложение из среды сгеаге-геасс-арр, ^2 попробуйте выполнить требуемые настройки, используя возможности этой среды Дополнительную информацию по этому вопросу можно найти в документации сре- * ды по адресу (1Нрз://оге|1.1у/991ес1 Исходный код данного рецепта на языке Зауа$спр1 или ТуреЗспр! можно загрузить на веб-сайте СпП 1пЬ по адресу ЬНр8://огеП.1у/1}К0с1Х или Ьйр8://огеП.1у/оО8о9 со- ответственно. 1.2. Создание приложений с обширным информационным наполнением посредством Са1зЬу ЗАДАЧА Веб-сайты с обширным информационным наполнением, такие как, например, блоги или магазины, должны предоставлять большие объемы сложного содержимого эффективным образом. Средство сгеаСе-геасЪ-арр не подходит для создания веб- сайтов такого типа, поскольку оно предоставляет все содержимое в виде одного большого пакета кода .1ауа$спр1, который браузер должен полностью загрузить, прежде чем что-либо будет отображено. РЕШЕНИЕ Для создания веб-сайтов с обширным информационным наполнением можно ис- пользовать средство СаЬЬу. Это средство предназначено для загрузки, преобразования и доставки содержимого наиболее эффективным образом. Оно может создавать статические версии веб- страниц, в результате время отклика веб-сайтов, созданных с помощью этого сред- ства, часто значительно меньше, чем сайтов, созданных на основе средства сгеа^е- геас±-арр. Для Оа1зЬу предоставляется большое число подключаемых модулей, которые могут эффективно загружать и преобразовывать данные из хранилищ локальных данных,
источников ОгаркОЬ или сторонних систем управления контентом, таких как \УогйРге88. Средство Оа(зЬу можно установить глобально или же вызывать с помощью коман- ды прх: $ прх даЕзЬу пей ту-арр В данном случае исполнение команды даьзЬу пей создает папку с новым проектом, называющуюся ту-арр. При первом исполнении этой команды выводится запрос, какой диспетчер пакетов выбрать: уагп или прт. Чтобы активизировать созданное приложение, нужно перейти в его папку и запус- тить его на исполнение в режиме разработки: $ ей ту-арр $ прт гип йеуе1ор После этого приложение можно открыть в браузере по адресу 1Шр://1оса11ю81:8000, как показано на рис. 1.3. Проекты СаЬЬу имеют прямолинейную файловую структуру, как можно видеть на рис. 1.4. ОаТзЬу ОеТаиН 31аг1ег Н| реор1е УУекоте Юуоиг пеи'СаьЬу зПе. Ыту ЬиМ яошегЫп^ кге*Л. © 2021, ВиП1 мГЬ СаиЬу Рго]есГ - 1_1СЕ№Е - КЕАОМЕ.тй - даиЬу-Ьгом/зег -• дайЬу-сопйд.)? - даГ8Ьу-пос1е.]5 - даиЬу-з$г.]$ - пос!е_тос1и1е1/ - раскаде-1оск.]?оп - раскаде.]5оп -г 5ГС - сотропепЬ - кладе? раде? Рис. 1.3. Домашняя страница нового приложения СаТзЬу, открываемая по адресу ЬКр://1оса1Ъо81:8000 Рис. 1.4. Файловая структура проекта Са1зЬу
Основная часть кода приложения находится в папке зге. Каждая страница прило- жения Оа1зЬу имеет свой отдельный компонент КеасТ. В листинге 1.3 приведен код файла шбехдомашней страницы стандартного приложения. Листинг 1.3. Код файла тс1ех.]5 домашней страницы стандартного приложения ОаШЬу ттрогЁ * аз ВеасЁ Г г от "геас!:" 1трогГ { Ыпк } Ггот "да^зЬу" йпрогЁ { ЗЁа1:1с1таде } Егот "даЁзЬу-рТидтп-йпаде" ттрогХ Ьауои1з Г гот ".. /сотропеп^з/ТауоиС нпрогЁ Зео ^гот ".. /сотропеп1:з/зео" сопз1: ТпдехРаде = ()=>( <Ьауои1:> <8ео Ё11:1е="Ноте" /> <Ы>Н1 реор1е</Ъ1> <р>Ке1соте ёо уоиг пем Са^зЬу з1Ле.</р> <р>Ь1ом до ЬиНс! зотеЁЫпд дгеа1;.</р> <ЗЁаЁ1с1таде згс="../хтадез/даЁзЬу-азЁгопаиЁ.рпд" и1сД±1={300} диа1И:у={95} ГогтаИз={["АУТО", "ИЕВР", "АУ1Е"]} а]Л="А Са^зЬу азйгопаиЬ" з1:у1е={{ тагд1пВо1:1:от: '1.45гепГ }} /> <Р> <Ыпк Ёо="/раде-2/">Со 110 раде 2</Ыпк> <Ьг /> <Ыпк 1;о=’7из1пд-11уре5сг1р1:/">6о ёо "Взтпд ТуреЗсг1рЁ"</Ыпк> </р> </ЬауоиЁ> ) ехрогЬ беГаиИ ТпдехРаде Создавать маршрут для страницы нет необходимости, поскольку маршрут назнача- ется автоматически каждому страничному компоненту. Например, страница с пу- тем згс/раёе8/и81п§-1уре8сг1р1.18х автоматически предоставляется по адресу /изш§- (урезспр!2. Данный подход обладает многочисленными достоинствами. Прежде всего, разработчику не нужно управлять маршрутами страниц, что может быть со- пряжено со значительными трудностями в случае их большого количества. Кроме того, Ста^зЬу может предоставлять страницы намного быстрее. Чтобы понять поче- му, сначала разберемся, как создать сборку промышленного кода из приложения 6а1зЬу. 2 Да, это означает, что ОаьЬу поддерживает Туре$спр(.
Для этого нужно остановить сервер для разработки3, а затем выполнить следую- щую команду: $ прт гип ЬиНд Эта команда запускает на исполнение команду да^зЬу Ьипа, которая создает папку риЬПс, содержащую все богатство Оа1§Ьу. Для каждой страницы в ней есть два файла. Первый файл содержит сгенерированный код 1ауа8спр1. Например: 1389 06:48 сотропеп!:—5гс-раде5-из1пд-Ъурезсг1р1:-^5х-93Ь78сГа(1с08с17д203с6.]5 Можно видеть, что объем кода для файла изт^-ХурезспрМзх составляет всего лишь 1,389 байта, чего в комбинации с базовым фреймворком как раз достаточно, чтобы создать страницу. Иными словами, это не сценарий типа ”все свое ношу с собой", которые ИСПОЛЬЗУЮТСЯ В Проектах сгеаСе-геас1:-арр. Далее, для каждой страницы есть подпапка, содержащая файл НТМЬ. Для файла и$1п§-(уре8спр11$х этот файл с учетом полного пути от корневой папки называется риЫ1с/и81П^-1уре8спр1/тс1ех.Ъ1т1 и содержит сгенерированную статическую версию веб-страницы. В частности, он содержит НТМЬ-код, который в противном случае элемент, использующий файл изт§-1уре5Спр1Л8х, генерировал бы динамически. В конце страницы этот код загружает версию Зауа8спр( страницы, чтобы сгенери- ровать динамическое содержимое. Такая файловая структура означает, что веб-страницы приложений Оа&Ьу загру- жаются так же быстро, как и статические страницы. А с помощью встроенной биб- лиотеки геас^-ЪеХшег можно также сгенерировать заголовочные теги <ше1:а/>, содер- жащие информацию о дополнительных особенностях сайта. Оба этих свойства очень полезны при оптимизации веб-сайта для поднятия его позиций в результатах выдачи поисковых систем (8ЕО). Обсуждение Для добавления содержимого в приложения 6а1зЬу можно использовать "безголо- вую" систему управления контентом4, службу ОгарЬрЬ, статический источник ин- формации или какой-либо другой источник. К счастью, для 6а1зЬу предлагается множество подключаемых модулей, посредством которых можно подключать ис- точники данных к приложению, а затем преобразовывать содержимое из других форматов, например Магкдохуп, в код НТМЬ. Полный список доступных подключаемых модулей предоставляется на веб-сайте Са1§Ьу (Ьйр$://огеН.1у/9СлуЬу). В большинстве случаев рекомендуется выбирать требуемые подключаемые модули при создании проекта. ОаТзЬу также поддерживает стартовые шаблоны, предостав- 3 В большинстве операционных систем это можно сделать, нажав комбинацию клавиш <С1г1>+<С>. 4 Англ. кеаеИем соп(еп( тапа^етеп! зумет. Только серверная часть системы управления контентом (СМ8), построенная с нуля как репозиторий контента, что делает контент доступным через АР1 для отображения на любом устройстве. Термин "безголовый" происходит от концепции отделения "головы" (внешний интер- фейс, т. е. веб-сайт) от "тела" (задний конец, т. е. репозиторий контента). — Прим. пер.
ляющие начальную структуру и настройки приложения, что позволяет ускорить процесс разработки веб-сайтов. В созданном нами ранее приложении применен довольно простой стартовый шаблон, назначенный по умолчанию. Управление модулями, подключаемыми к приложению, осуществляется посредством файла §а18Ьу-сопГщ.)8, который находится в корневой папке приложения. Но в доступе имеется масса стартовых шаблонов для приложений ба&Ьу, предва- рительно сконфигурированных для подключения к различным источникам данных и установленными настройками для 8ЕО, стиля, кеширования для работы офлайн, приложений Р\УА (Рго§ге881Уе \УеЬ АррНсабоп — прогрессивное веб-приложение) и т. д. Какое приложение с обширным информационным наполнением вы бы ни разрабатывали, можно найти стартовый шаблон, близкий к вашим требованиям. С дополнительной информацией о стартовых шаблонах для приложений Оа^зЬу можно познакомиться на веб-сайте ОаЬЬу (М1р8://огеН.1у/ууг11с18). Там же можно найти и список подсказок по наиболее полезным инструментам и командам СаТзЬу (Ы1р8://огеП.1у/Г7хЬГ). Исходный код данного рецепта можно загрузить на веб-сайте ОкНиЬ по адресу ЬПр8://огеП.1у/1)гЬ$у. 1.3. Создание универсальных приложений с помощью Канг1е ЗАДАЧА Иногда в начале разработки приложения не ясно, какие основные архитектурные подходы реализовать. Создавать ли одностраничное приложение? Если производи- тельность играет важную роль, использовать ли серверный г-код? Вам нужно будет решить, какой будет ваша платформа развертывания, а также применять ли 1ауа8спр1 или Туре8спр1 для написания своего кода. Многие инструменты требуют принятия решения по их выбору на начальной ста- дии работы над проектом. Если вы позже передумаете, то в дальнейшем изменить подход к сборке и развертыванию приложения будет сложно. РЕШЕНИЕ Помочь отложить решение о подходе к разработке и развертыванию приложения может средство Кахх1е (Ь((р8://огеП.1у/Зр7лс). С помощью этого средства можно создавать универсальные приложения (Ь«р8://огеН.1у/С4960), т. е. приложения, которые могут исполнять свой код 1ауа8спр1 на сервере, на клиенте, или на той и другой стороне. Благодаря архитектуре подключаемых модулей расширения (р1и§т агсЬйесШге), разработчик может изменить способ разработки приложения на ее дальнейших этапах. Можно даже изменить средство разработки с Кеас1 на РгеасТ или выбрать какой-либо совершенно другой фреймворк, например Е1т или Уие.
Приложение Кахх1е создается посредством команды сгеаРе-гагх1е-арр:$ $ прх сгеаРе-гагг!е-арр ту-арр В результате исполнения этой команды создается папка с указанным названием (в данном примере это ту-арр), в которой и размещается новый проект Ка/21е. Сервер для разработки запускается исполнением следующих двух команд: § сб ту-арр $ прт гип зРагР В результате исполнения этих команд динамически генерируется код как для кли- ентской версии приложения, так и для серверной и на порту 3000 запускается сер- вер (рис. 1.5). М/е1соте 1о Па221е То де(8!аПес1. ес1Н згс/лрр. цв ог згс/ноте.зз апб вауе ю ге1оаб. Росз |88ив8 Сот шипку 81аск Рис. 1.5. Домашняя страница нового приложения Рагг1е, открываемая по адресу ИР1р://1оса1КозР3000 Сборка окончательной версии приложения осуществляется с помощью следующей команды: $ прт гип ЬиИй В отличие от приложений сгеаРе-геаср-арр, при сборке приложений В.ахх1е создается не только код клиента, но также и рабочий сервер Ыос!е. Созданная таким образом сборка приложения помещается в подпапке Ьи11с1 папки проекта. При исполнении серверного кода будет генерироваться статический код для клиента. Рабочий веб-сервер запускается исполнением сценария в файле Ьш1с!/$егуег по следующей команде: $ прт гип зРагР:ргос1 Рабочий веб-сервер можно использовать всюду, где доступен 1Мос1е.]$. Как сервер, так и клиент исполняет один и тот же код, что делает приложения Ках71е универсальными. Это возможно благодаря наличию разных точек входа для 5 Название команды преднамеренно сделано похожим на команду сгеаРе-геасР-арр. Разработчик Кахх1е Джаред Палмер (.1агес1 Ра1тег) считает средство сгеаРе-геасР-арр одним из источников своего вдохнове- ния для его создания.
клиента и сервера. Сервер исполняет код в файле 8гс/зегуег.]8, а браузер— код в файле 8гс/сНеп1.]8. Код в каждом из этих файлов затем прорисовывает то же самое приложение, исполняя код в файле 8гс/Арр.]з. Приложение Кахг1е можно исполнять как одностраничное приложение. Для этого нужно удалить файлы 8гс/тс1ех.]8 и 8гс/8егуег.]8, а затем создать файл тдех.Ыт! в папке риЬНс. В этом файле нужно создать элемент <с^/> с идентификатором га = "гооГ', а затем выполнить повторную сборку приложения, запустив следую- щую команду: $ пос!е_1пос1и1ез/.Ып/гагхХе ЬиИд —1;уре=5ра Чтобы создавать одностраничные приложения с самого начала, нужно добавить опцию -1:уре=зра в сценарии зТагЁ и ЬиНа в файле раскаде.]8оп. В результате в папке ЬшШ/риЬНс будет создано законченное одностраничное при- ложение, которое можно развернуть на любом веб-сервере. Обсуждение Такая гибкость средства К.ахх1е возможна благодаря его структуре, состоящей из набора конфигурируемых подключаемых модулей. Каждый такой модуль пред- ставляет собой функцию высшего порядка, которая принимает конфигурацию АУеЬраск и возвращает ее модифицированную версию. Например, один модуль может транспилировать код Туре8спр1, а другой — выполнять сборку библиотек КеасТ. Чтобы переключить приложение на фреймворк Уие, нужно просто установить со- ответствующие модули. Полный список доступных подключаемых модулей предоставляется на веб-сайте Ках21е (Ьир8://оге11.1у/ЛХ^Ру). Исходный код данного рецепта можно загрузить на веб-сайте СпШиЬ по адресу ЬПр8://огеП.1у/ гВК9г. 1.4. Создание серверного и клиентского кода посредством МехГ]з ЗАДАЧА Средство Кеас1 создает клиентский код, даже если этот код генерируется на серве- ре. Но иногда при сравнительно небольшом объеме кода для АР1-интерфейса с этим кодом может быть предпочтительнее работать как с частью того же прило- жения Кеас1.
РЕШЕНИЕ Сгенерировать код приложений Кеас1 и серверный код можно с помощью фрейм- ворка КехГ]8. Концевые точки АР1-интерфейса и клиентских страниц используют соглашения о маршрутизации по умолчанию, что упрощает их создание и развер- тывание, в отличие от управления маршрутизацией вручную. Полная информация о фреймворке КехГ]з предоставляется на его веб-сайте (Ьйр5://пех1]5.ог2). Приложение №х1.]б создается при помощи следующей команды: $ прх сгеа!:е-пех1:-арр ту-арр Если установлен диспетчер пакетов уагп, то в приложении будет использован этот диспетчер. Но можно также и задать диспетчер пакетов прт, указав флаг — изег-прт: $ прх сгеаСе-пех1-арр —изе-прт ту-арр В результате исполнения данной команды будет создана указанная подпапка (ту- арр в данном случае), в которой будет размещено новое приложение №хЕ}с. Чтобы запустить созданное приложение, нужно перейти в его папку и запустить его на исполнение в режиме разработки (рис. 1.6): $ ей ту-арр $ прт гип йеу Средство Кех1.]з позволяет создавать страницы, не беспокоясь при этом об управ- лении конфигурацией маршрутизации. Таким образом, добавленный в папку ра@е$ компонент сразу же становится доступным для раздачи сервером. Например, ком- понент ра^ез/тдехЗя создает домашнюю страницу стандартного приложения. УУе1соте (о Мех1.]з! Ве1 зсаНед Ьу едГСпд р&дев/Чпйех. ] $ ОоситепШкоп Ьват-> Р1пй 1п-дер<Н 1пЛхтЛюл аЬшЛ М«Л.ГааСигев впд АР1. Ьвагп аЬои! №х1 ]а 1п ап 1п1жас1Ке соигвв уЛН дЫххев! Ехатр1еа-> 1>вр*оу-» [Хвсоузг ала смрюу ЬобвеДОа ехатр^е Ыех1.}в рго}аси. 1па1апву верк>у уоиг ЫехЦв вне 1о а риЬМс ОЙ. Уегсв). ______________________________________Рспуегед Ьу ДУ»гс»1__________________________________ Рис. 1.6. Страница приложения МехЦз, открываемая по адресу №1р://1оса11юз1:3000
Данный способ похож на подход, применяемый в Са^зЬу6, но отличается добавле- нием серверного кода. Приложения КехЦз обычно содержат определенный объем серверного кода АР1- интерфейса, чего, как правило, нет в приложениях Кеас1, которые часто собираются отдельно от серверного кода. В частности, в подпапке ра^ез/арт можно найти при- мер концевой точки сервера, называющейся Ье11о.]з. Содержимое этого файла при- ведено в листинге 1.4. Листинг 1.4. Содержимое файла концевой точки сервера // ЫехТ.дз АР1 гоиТе зиррогТ: Ъ<Лрз://пехЪ}з.огд/с1осз/ар1-гоиТез/1п11гос1исТ1оп ехрогЁ деГаи11: (гед, гез) => { гез.зТаТиз(200).дзоп({ паше: ’ЛоИп Сое' }) } Маршрутизация, которая подключает код к концевой точке ар1/Ье!1о, выполняется автоматически. Средство Кех1.]§ транспилирует код приложения и сохраняет его в скрытой папке .пех1, которая в дальнейшем может быть развернута на службе, например на плат- форме Уегсе! (ЬКр8://уегсе1.сош) самого №х1.]з. При желании можно сгенерировать статическую сборку приложения, выполнив следующую команду: $ пойе_тоби1ез/.Ып/пех11 ехрогТ Созданный в результате исполнения команды код клиента сохраняется в папке оЩ. Команда преобразовывает каждую страницу приложения в статический НТМЬ- файл, что ускоряет ее загрузку в браузере. В конце страницы загружается версия Дауа8спр1, чтобы сгенерировать динамическое содержимое. Созданные посредством команды ехрогт статические версии приложений №х1.]з не содержат никакого кода серверного АР1-интерфейса. Средство №х1.]з поддерживает большое количество опций получения данных, что позволяет получать данные из статического содержимого или из "безголовых" сис- тем управления контентом (ЬНр$://огеП.1у/Х1та8). Обсуждение Средства Кех1.]з и 6а1зЬу во многих аспектах схожи. Их главные отличительные особенности — скорость предоставления содержимого при небольшом объеме 6 См. раздел 1.2.
конфигурирования. Скорее всего, №х1.]$ будет наиболее полезным для команд раз- работчиков, которые имеют дело с очень небольшим объемом серверного кода. Исходный код данного рецепта можно загрузить на веб-сайте СИШиЬ по адресу ЬПр8://оге11.1у/9^ЬЛ8. 1.5. Создание крошечных приложений посредством Ргеас! ЗАДАЧА Объем приложений Кеас1 может быть достаточно большим. Даже простое прило- жение Веас1: может с легкостью транспилироваться в сборки кода 1ауа8спр1 разме- ром в несколько сотен килобайт. А в данном случае требуется создать приложение с возможностями платформы КеасТ, но намного меньшего размера. РЕШЕНИЕ Для создания приложения с возможностями Кеас1, не расплачиваясь за это сборкой кода 1ауа8спр( большого объема, можно использовать средство Ргеас1. Несмотря на очень похожие названия, средство Ргеас1 — это не Кеас1. Это отдель- ная библиотека, которая предназначена для создания приложений, как можно больше похожих на приложения Кеас(, но намного меньшего размера. Большой размер приложений Кеас( объясняется принципом работы этого фрейм- ворка. Компоненты приложений Неас! не создают элементы непосредственно в мо- дели ПОМ (ЭоситеШ ОЬ]ес1 Моде! — объектная модель документов) браузера, а в виртуальной модели ПОМ, а затем обновляют настоящую модель ПОМ через ко- роткие интервалы времени. Такой подход позволяет быстро прорисовывать базо- вую модель БОМ, поскольку настоящую модель ПОМ нужно обновлять только при наличии реальных изменений. Но это имеет свою отрицательную сторону в виде большого объема кода, необходимого для поддержания виртуальной модели ООМ приложений Кеас1 в актуальном состоянии. Поскольку фреймворку Кеас1 необхо- димо содержать всю синтетическую модель событий, аналогичную соответствую- щей модели браузера, приложения Кеас1 могут иметь большой размер и занимать некоторое время для загрузки. Один из способов нейтрализации указанного недостатка состоит в отрисовке стра- ниц на сервере (88К — 8егуег-8гс1е Кепдегт§), но подобный метод может оказаться сложным для настройки7. Иногда нужно лишь загрузить небольшой объем кода, для таких случаев и был создан фреймворк Ргеас1. Хотя библиотека Ргеас1 похожа на библиотеку К.еас1, она имеет небольшой размер. На момент подготовки материала данной книге размер основной библиотеки Ргеас1 7 См. разделы 1.2 и 1.3.
составляет около 4 Кбайт, что позволяет добавлять в веб-страницы возможности в стиле Кеас! при объеме кода немногим больше, чем обычный браузерный код 1ауа8спр!. Кеас! можно использовать или как небольшую библиотеку ЗауаБспр!, включенную в веб-страницу (подход без поддержки инструментов), или как полнофункциональ- ное приложение Ла\'а8спрГ Подход без применения инструментов предоставляет базовые возможности. Основная библиотека Ргеас! не поддерживает расширение 18Х, что означает отсут- ствие под держки компилятора ВаЬе1, что, в свою очередь, не позволяет реализовать современные возможности языка 5а\а8спр1. В листинге 1.5 приведен пример кода страницы с использованием библиотеки РгеасТ. Листинг 1.5. Код страницы с использованием библиотеки Ргеас! <Г11ип1> <Ьеас1> <Ё11:1е>Ыо Тоо1з!</111Ё1е> <зсг!р1: згс="ЫХрз://ипркд.сот/ргеас^?итсГ></5сг1р1:> </Ьеас1> <Ьобу> <Ъ1>Ко Тоо1з РгеасЬ Арр!</Ы> <сИ.л/ 1с1="гооЬ"></(11у> <зсг1р1:> уаг И = м1пс1ом.ргеас<:.к; уаг гепдег = мгпскж.ргеасЁ.гепсЗег; уаг тоипЁ = йоситепЬ.де^ЕТешепЁВуТд(' гоо!:') ; гепдег( И('ЬиЬЁоп', { опСИск: Гипс1:1оп() { гепбеПМ'сЦу', пи11, 'Не11о'), тоигЛ); } Ь •СИск!'), тоипЁ ); </зСГ1рЁ> </Ьос1у> </Ы;т1> Это приложение устанавливается в элементе <сИу/> с идентификатором ^"гоо^", где оно будет отображать кнопку. При нажатии этой кнопки содержимое элемента сЦу заменяется строкой "Не11о”. Более простое приложение Ргеас! вряд ли можно придумать.
Но настоящие приложения Ргеас! редко разрабатываются таким образом. В дейст- вительности они обычно создаются на основе простой цепочки сборок, которая как минимум поддерживает современный язык 1ауа8спр1. Средство Ргеас! поддерживает весь диапазон приложений 1ауа8спр!. С другой сто- роны, полноценное приложение Ргеас! можно создать с помощью средства Ргеас! СЫ. Этот инструмент для создания проектов Ргеас! аналогичен инструментам типа сгеа^е-геаси-арр. Для создания приложения Ргеас! посредством этого инструмента предназначена следующая команда: $ прх ргеас!1-с11 сгеа<:е йеГаиИ. ту-арр [ ф Данная команда использует для создания приложения шаблон по умолчанию. Но доступны и другие шаблоны для создания проектов, например, с компонентами Ма!епа1 или языком ТуреЗспр!. Дополнительную информацию см. на странице 6ИНиЬ фреймворка Ргеас! (М!р8://огеП.1у/1УОиа). В результате исполнения приведенной команды будет создана указанная подпапка (ту-арр в данном случае), в которой будет размещено новое приложение Ргеас!. Приложение запускается следующей последовательностью команд: $ сб ту-арр $ прт гип с!еу В результате запустится сервер на порту 8080, раздающий страницу, показанную на рис. 1.7. Ргеас! Арр Ноте Ме ЗоКп Ноте Т(11815 1Не Ноте сотропепГ Рис. 1.7. Страница приложения Ргеас! Сервер создает веб-страницу, которая вызывает пакет ДауаЗспр!, собранный из кода в файле 8гс/1пс1ех.]8. В результате получается полномасштабное приложение наподобие приложения Ргеас!. Например, код в компоненте Ноте (8гс/гои!е8/Ьоте/тс1ех.]8) выглядит во многом как код приложения Исае! с полной поддержкой языка 58Х (листинг 1.6). Листинг 1.6. Код приложения Ргеас!, созданного при помощи Ргеас! С1_1 нпрогТ { И } Ггот 'ргеасЁ'; хтрог!: 5Ёу1е !гот ' ./з!:у1е.сзз'; сопзё Ноте =()=>( <с11^ с1азз={зЁу1е.Ьоте}>
<Ъ1>Ноте</Г11> <р>ТИ13 13 Ноше сотропепЁ.</р> </сНу> ); ехрогЁ деГаи1Ё Ноше; Единственное, чем этот компонент значительно отличается от стандартного компо- нента Кеас1, так это импортированием функции ъ из библиотеки ргеасг, вместо импортирования КеасЁ ИЗ библиотеки геас±. Код 38Х в приложениях Ргеас! преобразуется в последовательность вызовов функции и, чем и объясняется надобность в ее импортировании. По этой же при- /тЧк чине приложения, созданные посредством версий сгеа^е-геасХ-арр более ранних, ж чем версия 17, также требовали импортирования объекта геасг. Начиная с версии 17, в сгеаЁе-геас^-арр осуществляется преобразование в ^Х (М1рв://оге|1.1у/НОш89), что избавляет от необходимости импортировать геасг каждый раз. Можно наде- яться, что в будущих версиях Ргеас! будет осуществлено подобное изменение. Но размер приложения увеличился, составляя теперь чуть свыше 300 Кбайт. Это довольно много, но мы все еще находимся в режиме разработки. Чтобы увидеть настоящую мощь Ргеас!, остановим сервер для разработки (нажав клавиши <С1т1>+<С>), а затем выполним сборку приложения, исполнив следующую команду: $ прш гип ЬиНс! В результате будет создана папка Ьш1с1, в которой появится сгенерированная стати- ческая версия приложения. Первым положительным результатом этого будет соз- дание статической копии домашней страницы, которая станет быстро отображать- ся. А второй результат — удаление всего лишнего кода из приложения, уменьшая его размер. При раздаче этой собранной версии приложения стандартным веб- сервером браузер после отображения страницы загрузит всего лишь около 50-60 Кбайт. Обсуждение Средство Ргеас( предоставляет замечательные возможности. Несмотря на значи- тельно иной принцип работы, чем Кеас{, оно обладает практически такой же мощью, будучи намного меньшим по размеру. И тот факт, что с его помощью мож- но создавать проекты в диапазоне от примитивного встроенного кода до полно- масштабных одностраничных приложений, означает, что использование его для собственной разработки вполне достойно рассмотрения, если объем кода критиче- ски важен. Дополнительную информацию о фреймворке Ргеас1 можно найти на его веб-сайте (Ьир$://ргеасф.сот). Исходный код примера приложения без включения инструментов и код примера полномасштабного приложения можно загрузить на веб-сайте СйНиЬ по адресу Ьйр8://огеП.1у^9РКГ и М1р5://огеП.1у/Е0АУ9 соответственно.
Приложения Ргеас1 можно сделать еще более похожими на приложения Кеас! с помощью библиотеки ргеасЁ-сотрат (ЬНр8://огеН.1у/ЗУХОу). Наконец, библиотека 1пГегпо18 (Ьйр8://1пГегпо]8.ог§) предоставляет еще одну аль- тернативу Кеас! наподобие подхода Ргеас1. 1.6. Создание библиотек посредством набора инструментов лигёт ЗАДАЧА В крупных организациях часто одновременно разрабатывается несколько приложе- ний К.еас1. Например, консалтинговая компания может создавать несколько прило- жений для разных организаций. А компания по разработке программного обеспе- чения может разрабатывать разные приложения с одинаковым оформлением или функциональностью, для чего, скорее всего, потребуются общие компоненты в не- скольких приложениях. При создании компонентного проекта необходимо сформировать структуру папок, выбрать набор инструментов и набор языковых возможностей, а также реализовать сборочную цепочку, которая может собрать компонент в развертываемый формат. Этот процесс может быть таким же трудоемким, как и ручное создание всего при- ложения К.еас1. РЕШЕНИЕ С помощью набора инструментов ™ь можно создавать как полномасштабные при- ложения, так и отдельные компоненты КеасЕ Он также позволяет создавать компо- ненты для использования в проектах Ргеас! и 1пЕето18, но здесь мы будем концен- трироваться на рассмотрении компонентов К.еас1. Чтобы использовать набор инструментов пнЬ в проектах Кеас1, нужно установить его глобально: $ прт хпзЁаИ -д пкЬ После этого можно будет создавать новые проекты, исполняя команду пмь: $ пыЬ пем геасТ-сотропепТ ту-сотропеп1з Вместо одного компонента можно создать полноценное приложение тиЬ во фрейм- ворке геасТ, ргеасТ или гпГегпо, заменив опцию геасТ-сотропепЁ на геасЁ-арр, ргеасТ-асТ или 1п{егпо-арр соответственно. Также можно создать и обычный про- ект Зауа8спр1, указав опцию Vап^11а-арр. После ввода этой команды система задаст несколько вопросов о том, какой тип библиотеки выбрать. В частности, будет задан вопрос, хотите ли вы создать сборку модулей сценариев ЕСМА8спр1 (Е8):
СгеаЁЁпд а геасЁ-СотропепЁ рго^есЁ_ (Создается проект компонента геасЁ...) ? Со уои иапЁ ёо сгеаЁе ап Е5 тобиЁез ЬиИд? (У/п) (? Хотите ли создать сборку модулей ЕЗ? (Да/нет)) Данная опция включает в приложение оператор экспорта, с помощью которого сборщик пакетов ХУеЬраск может решить, нужно ли включать компонент в клиент- ское приложение. Также задается вопрос, хотите ли вы создать определение ПМП (Пшуегза! МосЫе ВеГшйюп — универсальное определение модуля): ? Со уои ыапЁ ёо сгеаЁе а СМС ЬиНб? (у/Ы) (Хотите ли вы создать сборку с определением СМС? (Да/нет)) Такое определение будет полезным в том случае, когда разрабатываемый компо- нент нужно будет включить в элемент <зсгёрё/> веб-страницы. Для нашего примера мы не будем создавать сборку с определением ПМП. Получив ответы на все вопросы, средство пкЬ создаст указанную в команде подпап- ку (ту-сотропеЩ в данном случае), а в ней — проект компонента пиЬ. Проект при- мера состоит из простого приложения-обертки, которое запускается исполнением следующих двух команд: $ сд ту-сотропепЁ $ прт гип зёэгё В результате запустится сервер на порту 3000, раздающий страницу, показанную на рис. 1.8. арр Вето УУекоте 1о Кеас( сотропеп1$ Рис. 1.8. Страница компонента пшЬ Приложение содержит один компонент, который определяется в файле згс/шс1ех.)8 (листинг 1.7). --------——....—~~——..................... . ~ । Листинг 1.7. Исходный код компонента проекта п«ь ЁтрогЁ КеасЁ, ( СотропепЁ } Г г от 'геасЁ' ехрогЁ беГаиЁЁ с!азз ехЁепйз СотропепЁ { гепбегО { геЁигп ( <сИу> <?12>Ие1соте ёо КеасЁ сотропепЁз</Ь2> </6гу> ) } }
Теперь можно продолжить разработку компонента, как любого другого проекта Кеас1. Сборка компонента для развертывания осуществляется следующей коман- дой: $ пргс гип Ьи11с1 Сборка будет находиться в файле НЬ/тс1ех.]8, который можно разместить в храни- лище для использования в других проектах. Обсуждение Дополнительная информация по созданию компонентов п«ь предоставляется в ру- ководстве пыЬ по разработке компонентов и библиотек (ЬИр8://огеН.1у/ХНН^а). Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬПр8://огеН.1у/Р4Хг]. 1.7. Добавление Кеас1 в код КаПз посредством УУеЬраскег ЗАДАЧА Фреймворк Кайз был создан до того, как интерактивные приложения 1ауа$спр1: на- брали популярность. Приложения Кайз следуют более традиционной модели раз- работки веб-приложений, в которой страницы НТМЬ создаются в ответ на запросы браузера. Но иногда может потребоваться добавить в приложение КаЙ8 элементы с большими интерактивными возможностями. РЕШЕНИЕ Приложения Кеас( можно вставить в веб-страницы КаП с помощью библиотеки ХУеЬраскег. Для начала создадим приложение КаЙ8, содержащее библиотеку ХУеЬраскег: $ гаНз пей шу-арр —меЬраск=геасЁ В результате будет сформирована подпапка ту-арр, в которой будет создано при- ложение КаЙ8, уже сконфигурированное для работы с сервером ХУеЬраскег. Прежде чем запускать это приложение, перейдем в его папку и создадим пример страни- цы/контроллера: $ сд ту-арр $ гаИз депегаЁе сопЁгоИег Ехатр1е 1пбех Созданная таким образом страница арр/у1е\У8/ехатр1е/1пдех.Ыт1.егЬ имеет сле- дующее содержимое: <Ь1>Ехатр1е#1Пс1ех</Ъ1> <р>Е1пс1 те 1п арр/у1еы5/ехатр1е/1пс1ех.Ьип1.егЬ</р> Теперь нам нужно создать небольшое приложение Кеас1, чтобы вставить его в эту страницу. В КаЙ8 приложения ХУеЬраскег вставляются в виде небольших пакетов
кода Дауа8спр1 в коде КаПз. Мы создадим новый пакет, содержащий простой ком- понент счетчика, в файле арр/]ауа8спр1/раск8/соип1ег.]з (листинг 1.8). Листинг 1.8. Код компонента счетчика Г' < ~ хтрогЁ КеасТ, { изеЗТаТе ) ^гот ’геасЁ' хтрогТ КеасТЕЮМ ^гот ' геасТ-сЗот' сопзТ СоипТег = (ргорз) => { сопзЪ [соип!:, зеЗ:Соип1;] = изеЗЪа^е(0) геТит ( <с1гу с1аззЫате="Соип1:ег"> Уои сИскес! 1±е ЬиТТоп {соип1;) Тгтез. <ЬиТТоп опС11ск={() => зеТСоипТ ((с) => с + 1) }>СИск!</ЬиМюп> </с11у> ) } с!осшпеп1:.асЗси^епЁ1,15Тепег (' ВОМСопТепТЬоасЗесЗ', 0 => { КеасЁБОМ. гепсЗег ( <СоипТег />, сЗоситепТ.ЬосЗу. аррепсЗСНИсЗ (скэситепр. сгеа!:еЕ1етепТ (' <33. V1)) ) }) При каждом нажатии кнопки приложение обновляет значения счетчика. Теперь вставим этот пакет в нашу веб-страницу примера, добавив в стандартную страницу одну строку кода (выделена полужирным шрифтом): <Ь 1 >Ехатр1 е # 3.п<3ех< /И 1 > <р>Е1п<3 те 1п арр^1еиз/ехатр1е/1пс1ех.Ир1Г11.егЬ</р> <%= ]агазсг±рР_раск_Ъад 'соцпрег' %> Далее запустим сервер КаИз: $ гаИз зегуег На момент работы над материалом данной книги для запуска сервера КаПв нужно, чтобы был установлен диспетчер пакетов уагп. Установить его глобально можно, выполнив команду прт 1пзта11 -д уагп. Теперь введем в адресную строку браузера адрес страницы 1Шр://1оса1Ьо81:3000/ ехатр1е/1П(1ех.Ыт1, и она должна открыться в браузере (рис. 1.9). Ехашр1е#т<1ех Нпд те т арр/У1е\у&/ехатр1еЛп<кхЪ1т1.е1Ь Той Ьауе сИскед (Не ЬиПоп 3 Нтез ^Сйск! ] Рис. 1.9. Приложение РеасЗ, встроенное в страницу КаПа
Обсуждение Как вы уже, наверное, догадались, за кулисами браузера библиотека УУеЬраскег преобразовывает приложение с помощью копии сборщика пакетов ХУеЬраск. Настройки можно задавать, редактируя конфигурационный файл арр/сопй^/ \уеЬраскег.ут1. Библиотека ХУеЬраскег используется попутно с кодом КаПз, а не вместо него. Такой подход следует рассмотреть, когда приложению КаП§ необходимо придать немного дополнительной интерактивности. Дополнительная информация о библиотеке ХУеЬраскег предоставляется на ее веб- сайте ОйНиЬ (Ьйр8://оге11.1у/аТХ0Ь). Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ы1ря://огей.1у/НЗд1Р. 1.8. Создание пользовательских элементов посредством Ргеас* ЗАДАЧА В ряде случаев возникают обстоятельства, затрудняющие добавление кода Кеас1 в существующее содержимое. Например, в некоторых конфигурациях систем СМ8 пользователям не разрешается вставлять дополнительный код 1ауа8спр! в код страницы. В таких случаях было бы полезным иметь какой-либо стандартный спо- соб безопасно добавлять код 5ауа8спр! в страницу. РЕШЕНИЕ Применение пользовательских элементов— стандартный способ создания новых элементов НТМЬ для веб-страниц. По сути, они расширяют язык НТМЬ, делая дос- тупными для пользователя дополнительные элементы. В этом рецепте мы рассмотрим, как с помощью облегченного фреймворка Ргеас! создавать пользовательские элементы, которые затем можно разместить на сторон- нем сервере. Начнем с создания нового приложения Ргеас!. Это приложение будет предоставлять пользовательский элемент, который можно будет использовать в других веб-стра- ницах8: $ ргеас* сгеа*е де^аиИ ту-е1етеп* Затем перейдем в папку созданного приложения и добавим к проекту библиотеку ргеасГ-сизГоп-еХетепГ: $ сс! ту-е!етеп* $ прт 1П5*а11 ргеас*;-сиз*от-е!етеп* 8 Дополнительную информацию по созданию приложений Ргеас* см. в разделе 1.5.
Эта библиотека позволит нам регистрировать в браузере новые пользовательские НТМЬ-элементы. Теперь нам нужно отредактировать файл 8гс/т<1ех.]5 проекта, чтобы он регистриро- вал новый пользовательский элемент, который мы назовем сотропеп^/Сопуейег/ пк1ех.]8. Для этого добавим в данный файл следующий код: ипрогЕ гедгзЕег Егот 'ргеасЕ-сизЁот-е1етепЕ1 йпрогЁ СоггуегЁег Егот ' . /сотропепЕз/Сот/егЕег' гедЕзЕег(СопуегЕег, 'х-сотлегбег', ['сиггепсу']) Метод гед1з€ег указывает браузеру, что нужно создать новый пользовательский НТМЬ-элемент с названием <х-сопуегСег/>, имеющий одно свойство, называющееся сиггепсу. Это свойство определяется в файле 8гс/сотропеп(8/Сопуег!ег/1гк1ех.]8 (лис- тинг 1.9). Листинг 1.9. Код для определения свойства сиггепсу йпрогЁ { Ь } Егот 'ргеасЕ' ЕтрогЕ { изеЕЕЕесЕ, изеЗЕаЕе } Егот 'ргеасЕ/Ьоокз' ЕтрогЕ ' зЕу1е/Еп<Еех.сзз' сопзЕ гаЕез = { дЬр: 0.81, еиг: 0.92, дру: 106.64 } ехрогЕ с1еЕаи1Е ({ сиггепсу = 'дЬр' }) => { сопзЕ [сигг, зеЕСигг] = изеЗЕаЕе(сиггепсу) сопзЕ [атоипЕ, зеЕАтоипЕ] = изеЗЕаЕе (0) изеЕЕЕесЕ(() => { зеЕСигг(сиггепсу) }, [сиггепсу]) геЕигп ( <с!гу с1аззЫате="Сот7егЕег"> <р> <1аЬе1 ЪЕт1Гог=’,сиггепсу">Сиггепсу: </1аЬе1> <зе1есЕ пате="сиггепсу" уа1ие={сигг) опСкапде={ (еллб) => зеЕСигг(еуЕ.ЕагдеЕ.уа1ие)} > (ОкдесЕ.кеуз(гаЕез).тар((г) => ( <орЕ1оп Vа1ие={^}>{^}</орЕ^оп> )) } </зе1есЕ> </р> <р с1аззКате="СопуегЕег-атоипЕ"> <1аЬе1 ЪЕт1Еог="атоипЁ">Атоип6: </1аЬе1> <1приЕ пате="атоипЕ" З12е={8) 6уре="питЬег"
Vа1ие= {атоип!:} оп1приЁ={ (еу!з) => зеЁАтоипГ (рагзеЕ1оа1: (еус. Сагден.уа1ие))} /> </р> <р> Созё : {((атоипЁ I I 0) / гагез [сигг]).ЪоЬоса1е5Ъг1пд (’еп-иЗ', { з у1е: 'сиггепсу', сиггепсу: ' 1150’, })} </р> </сИу> ) Согласно спецификации названия пользовательских элементов должны начинать- ся с прописной буквы, не содержать заглавных букв и включать дефис9. Таким об- ; разом обеспечивается отсутствие конфликтов с названиями стандартных элемен- Наш компонент Согп/ег1:ег представляет собой конвертер валют, использующий для простоты фиксированный курс. Если теперь запустить сервер Ргеас! командой $ прт гип сЗеу то по адресу Ы1р:/Лоса1Ьо81:8080/Ьипд1е.]8 будет доступен код 1ауа8спр1 для наше- го пользовательского элемента. Чтобы задействовать этот элемент, создадим обычную статическую веб-страницу, содержащую код, приведенный в листинге 1.10. Листинг 1.10. Код НТМ1. для веб-страницы с пользовательским элементом <Н€т1> <Неас1> <зсг1р<: згс="Ы1:рз://ипркд.сот/ЬаЬе1-ро1уГ111/с113Ё/ро1уЕ111.т1п.^з"> </зсг!рЬ> <зсг1р11 згс="ЬМр5: //ипркд. сот/@ыеЬсотропепЬз/иеЬсотропепГз ] з"> </зсг±рГ:> <!— Замените следующий адрес адресом своего пользовательского элемента —> <зсг1рЪ Ёуре="1Iеx1^/^аVазс^^р^^,, згс="№Ър://1оса1ЪозЁ:8080/Ьипс11е.]з"> </зсг1р1:> </кеас!> <Ьос1у> <Ы>Сиз1:от ИеЬ Е1етеп1;</Ъ1> зЁу1е="1:1оа11: гхдЪЁ; с1еаг: ЬоЬИ"> 9 Подробнее о пользовательских элементах см. спецификацию АУНАТУУС (ЬПр»://оге11.1у7КО]тР).
<!— Этот тег вставляет приложение РгеасЕ —> <х-сопуегсег сиггепсу="дру"/> <р>ТЬ15 раде соШагпз ап ехатр1е сиз^от еХетеп! саИеб <сос1е>&1Ё;х-сопуегЁег/&д1:;</сос1е>, иЫсН 13 Ьегпд зегуес! ^гот а сИ^^егепЁ 1осаЁ1оп</р> </Ьос1у> </Лт1> Эта веб-страница содержит определение нашего пользовательского элемента в по- следнем элементе <зсг1р1/> элемента <Ьеаа/>. Чтобы обеспечить доступность поль- зовательского элемента как новым, так и старым браузерам, здесь же размещены ссылки (ипрк^.сот) на пару оболочек совместимости. Теперь, когда к веб-странице подключен код пользовательского элемента, мы мо- жем вставлять тег <х-сопуег1:ег/> в код страницы, как любой другой обычный тег НТМЬ. В рассмотренном примере мы также передаем свойство сиггепсу базовому компоненту Ргеас(. При передаче свойств пользовательских элементов базовому компоненту их на- звания указываются прописными буквами, независимо от регистра букв в названии в определении. Теперь мы можем раздавать эту страницу с пользовательским элементом с любого веб-сервера, не имеющего отношения к серверу РгеасТ На рис. 1.10 показано, как она должна выглядеть. Си$(ош УУеЬ Е1етеп1 ТЫз ра$с сотншш ап схатр1е сияют с1етеп1 саПеЗ <к-сагп/ег1вг/>, ‘МйсЬ 1з Ьет$ асгусй Ггот а «ИНегет !осаЬсп АтоипГ: |юо| Рис. 1.10. Обычная веб-страница со вставленным в нее пользовательским элементом Ргеас! Обсуждение Файл с кодом пользовательского элемента не обязательно должен находиться на том же самом сервере, что и веб-страница, в которой он используется. Это означа- ет, что пользовательские элементы можно применять для публикации виджетов на любой веб-странице. Поэтому может быть не лишним проверять заголовок ке^егег на всех входящих запросах к компоненту, чтобы предотвратить его несанкциони- рованное использование.
В нашем примере пользовательский элемент предоставляется сервером разработки средства РгеасЕ Для рабочей версии элемента будет желательно создать статиче- скую сборку компонента, которая, скорее всего, будет значительно меньшего раз- мера10 11. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬНр8://огеП.1у/аВ7ВР. 1.9. Разработка компонентов посредством ЗДогуЬоок ЗАДАЧА Компоненты Кеас! — надежные строительные блоки для приложений КеасЕ Долж- ным образом разработанные компоненты можно использовать в других приложе- ниях КеасЕ Но при разработке компонента нужно приложить некоторые усилия, чтобы проверить, как он работает при всех обстоятельствах. Например, в асин- хронном приложении Кеас! может отображать компонент с неопределенными свойствами. Будет ли компонент тем не менее отображен правильно или с ошиб- ками? Но при разработке компонентов как части сложного приложения создать все ситуа- ции, с которыми ваш компонент должен справляться, может оказаться довольно трудной задачей. Кроме того, специализированным разработчикам ИХ11 придется тратить много времени, если им нужно будет проходить по всему приложению, чтобы просмот- реть один разрабатываемый ими компонент. Было бы неплохо каким-то образом отображать только сам компонент и передавать ему примеры наборов свойств. РЕШЕНИЕ Данная задача решается при помощи средства 81огуЬоок, которое позволяет ото- бражать библиотеки компонентов в различных состояниях. Это средство можно было бы назвать галереей компонентов, но это, скорее всего, будет преуменьшени- ем его возможностей. В действительности это инструмент для разработки компо- нентов. Чтобы добавить возможности 8(огуЬоок в проект, сначала создадим новое прило- жение Кеас1, а затем перейдем в папку созданного приложения: $ прх сгеаРе-геас1-арр ту-арр $ сд ту-арр 10 Подробнее о средстве Ргеас! ш. раздел 1.5. 11 Чзег Ехрепепсе — впечатление пользователя от взаимодействия с интерфейсом приложения. Разработчик 11Х отвечает за создание пользовательского интерфейса и других элементов для обеспечения наилучшего качества 11Х. — Прим. пер.
А теперь добавим 81огуЬоок к проекту: $ прх зЬ 1П1Г Затем запускаем сервер 81огуЬоок: $ прт гип зЁогуЬоок Сервер 81огуЬоок запустится на порту 9000, и в браузере откроется его страница (рис. 1.11). При работе со 81огуЬоок само приложение кеас! исполнять не нужно. 0$ЪогуЬоок Сапу» Оосв Щ 3 Е 88 Я Й 63 С Япй сотрапапк Я •|х*м»н (пкобисчоп • Ф ВиПоп • Ф НмОег >в Раде Ше1соте Ю ЗЮгуЬоок 5ЮгуЪоок Ье1рв уои Ш1Й 1)1 сотропепгз *п 1воШ'ол (гот уоиг арр'з Ьияпевв 1одк, <1аи, аги) сотел. ТКи таков И ему № бе**к>р Кэгб-го-геасЪ Ш1ев. Зауе №езе 1)1вШев ав аГоНое № геИвк биНпд беуеЬртегЦ, 1ев1)пд, ог ОА. Вгонке ехатр1е Логкев пош Ьу паИдаНпд (о (Ъет 1п ГЪе зЮеЬаг У1ен 1Ье1г собе 1п №е »с/асог1и <№есгогу Ш 1еат Ьон гЬеу шик Же гесоттегк) ЬиИшпд 1Л в а сотролелС'бКуеп ргосевв йагНпд *ЛГ1 ают!с сотропеп1в апб егчИод нКЬ радев. сон в।в и не ₽ге»«11 *ог рори!аг 1оо1> Еазу иГир Гог ТуреБсНрГ, БС55 апР тоге. • Ви1№ сопПаигеМоп Нон № сив1от1га неЬраск агк) ВаМ 51у11пд Нон ю 1ом1 аги) соппдиге С55 НЬганев • от. РгоуЙвг? алс1 гтюслтд Тог с)аи ЬЬгаНез Рис. 1.11. Страница приветствия в 5(огуьоок В 81огуЬоок прорцровка одного элемента с примером свойств называется рассказом (§1огу). В установке 81огуЬоок по умолчанию образцы историй находятся в подпап- ке 81опе8 корневой папки проекта. Например, в листинге 1.11 приведен код файла рассказа $1опе$/Ви11оп.81опе8.|8: Листинг 1.11. Код файла рассказа 81ог!е8/ВиНоп.8(ог1е8.)$ 1трог1: КеасЁ Ггот 1 геас± ’ ; 1трог1: { Ви^Топ } ^гот ’./ВиЧоп'; ехрогЁ <1еГаи1Ё { 1Д1е: ' Ехатр1е/ВиТТоп', СотропепЁ: ВиЁЁоп, агдТурез: { Ьаскдгоипс1Со1ог: { соп1;го1: 'со1ог' }, }, };
сопз!: Тетр1аЁе = (агдз) => <Ви^оп {...агдз} />; ехрогЁ сопзё Рггтагу = Тетр1аЁе. Ыпс! ({}); Ргйпагу.агдз = { ргнпагу: {:гие, 1аЬе1: ' ВиМ:оп', ); ехрогЪ сопз!: Зесопбагу = Тетр1а^е.Ыпс1 ({}) ; Зесопйагу.агдз = { 1аЬе1: ' ВиМюп ’, }; ехрогЁ сопз!: Ьагде ~ ТетрЬаЬе. Ыпй ({}); Ьагде.агдз = { З12е: '1агде', 1аЬе1: ' ВиЬЬоп', }; ехрогЁ сопзё ЗтаИ = ТетрЬаЬе.ЫпсЦ {}); ЗтаН.агдз = ( зЬге: ’зтаН', 1аЬе1: 'ВиЬЬоп1, }; Средство 81огуЬоок отслеживает файлы с названием *.з1опез.]з в папке с исходны- ми файлами, и для него не имеет значения, где они находятся. Поэтому их можно сохранять в любом месте. Типичный подход — сохранять файлы рассказов в папке компонента, который они представляют. Таким образом, при копировании этой папки в другое место также копируются и рассказы как актуальная документация. На рис. 1.12 показано, как выглядит в 81огуЬоок рассказ ВиПоп.з1опе8.]з. Рис. 1.12. Пример рассказа
Обсуждение Несмотря на свою кажущуюся простоту, 8(огуЬоок представляет собой эффектив- ное средство разработки, позволяющее концентрироваться на одном компоненте за раз. Чем-то сродни визуальной проверке модулей, оно позволяет испытать компо- нент в нескольких возможных сценариях работы, чтобы удостовериться в его пра- вильном поведении. Для 81огуЬоок также предоставляется большой выбор дополнительных модулей расширений (ййр8://огеП.1у/Зк$Уа), с помощью которых можно обеспечить допол- нительные функции: ♦ проверка на наличие проблем с доступностью (Ассез81ЬПйу); ♦ добавление интерактивных элементов управления для задания свойств (КпоЬз); ♦ включение онлайновой документации с каждым рассказом (Посз); ♦ запись снимков НТМЬ для проверки эффекта от изменений (81огузко18). И много чего другого. Дополнительная информация о средстве 8(огуЬоок предоставляется на его веб- сайте (ЬИрз://з1огуЬоок.]8.ог2). Исходный код данного рецепта можно загрузить на веб-сайте бкНиЬ по адресу Ь«рз ://огеИ Лу/СухТХ. 1.10. Тестирование кода в браузере посредством Сургезз ЗАДАЧА Большинство проектов Кеас1 содержат библиотеку для тестирования. Наиболее распространенные из них — это, наверное, библиотека @1езбп§-НЬгагу/геас1, кото- рая устанавливается вместе со средством сгеа^е-геасЁ-арр, и Епхуте, которая ис- пользуется средством Ргеаск Но наилучшее тестирование — это тестирование в настоящем браузере, со всеми вытекающими из этого дополнительными усложнениями. При традиционном под- ходе тестирование в браузере может быть нестабильным и требует частого техни- ческого обслуживания, т. к. при каждом обновлении браузера нужно также обнов- лять и его драйверы (например, СНготеПпуег). Присовокупите к этому необходимость генерирования тестовых данных на сервере бэкенда, и тестирование в браузере может быть сопряжено со сложностями на всех этапах от организации до управления. РЕШЕНИЕ Фреймворк для тестирования Сургезз (ЬНрз://угту.сургеззЛо) позволяет избежать многих недостатков традиционного тестирования в браузере. Он исполняется в браузере, но не требует внешнего средства доставки веб-контента. Вместо этого
взаимодействие с браузером происходит через сетевой порт и внедряет код 1ауа$спр( для исполнения большей части тестового кода. Чтобы рассмотреть работу этого средства, создадим приложение сгеаье-геас^-арр: $ прх сгеа!:е-геас1:-арр —изе-прт ту-арр Затем перейдем в папку созданного приложения и установим фреймворк Сургезз: $ сд ту-арр $ прт 1П5Ёа11 сургезз —зауе-деу Прежде чем запускать Сургезз на исполнение, ему нужно указать, как найти наше приложение. Для этого создадим в папке приложения файл сургезз.]зоп и сохраним в нем следующий код с указанием СКЬ-адреса нашего приложения: ( "ЬазеигГ*: ”ЪГХр: //1оса1Ъоз1:: 3000/’’ ) Теперь можно запустить на исполнение наше основное приложение: $ прт зГагЬ И наконец, открыть приложение Сургезз: $ прх сургезз ореп При первом запуске Сургезз устанавливаются все требуемые для него зависимости. Теперь в папке сургезз/1п1е§гаПоп создадим файл зсгеепзЬо^з с кодом теста (лис- тинг 1.12), который открывает домашнюю страницу и делает снимок кода. дезсгхЬе(’зсгеепзкоГ’, () => { 11(’зЬои1д Ье аЫе 1:о !:аке а зсгеепзко!’, О => { су.У1511: (’/’); су.зсгеепзЪо!(’1гоп^раде’); В; }); Обратите внимание на то, что тест написан в формате 1ез1. После сохранения файл теста отобразится в главном окне Сургезз (рис. 1.13). Двойной щелчок мышью по файлу теста запустит его в браузере. Откроется до- машняя страница приложения, и тест сохранит снимок экрана в файл сургезз/ зсгеепзкоТз/зсгееп-зко^з/ГгопГраёе.рпё. Обсуждение В табл. 1.1 приведено несколько примеров команд Сургезз. Это всего лишь несколько команд для взаимодействия с веб-страницей. Но Сургезз способен на большее. В частности, он может модифицировать код в браузере, чтобы
Ц ЙвагсЛ... ► Яип ав вреса - 1МТЕОНАПОМ ТЕ5Т8 О асгеепвЬо1.]8 Рис. 1.13. Главное окно Сургезз Таблица 1.1. Примеры команд фреймворка Сургеев Команда Описание су.сопТахпз('Егед’) Выполняет поиск элемента, содержащего строку ТгесГ су.деР (’ .Ыогтап') .с11ск() Выполняет щелчок мышью по элементу класса "Могтап" су.деК'хприГ1).руре('Н1!') Вводит строку "НН" в поле ввода су.деР('Ы') .зсгоШгЛсАЦеиО Прокручивает страницу до вывода в область просмотра элемент <Ы/> изменить время (команда су.с1оско), файлы куки (команда су.зеЁСоокгео), содер- жимое локального хранилища (команда су.с1еагЬоса15югадео). Но самое впечат- ляющее то, что он может эмулировать запросы к серверу АР1 и ответы от него. Для этого он модифицирует встроенные в браузер сетевые функции таким образом, что код: су.гоиТе("/ар1/зе^ег?*", [{зоше: ' Бара'} ]) перехватывает все запросы к конечной точке сервера, начинающейся с /ар1/зегиег?, и возвращает массив 18СЖ [ {зоте: = Бара1} 1 - Возможность эмулирования сетевых ответов может полностью изменить процесс разработки приложений, поскольку это разделяет этапы разработки фронтенда и бэкенда. При тестировании в браузере можно просто указать требуемые данные, без необходимости организовывать настоящий сервер и базу данных. Дополнительная информация о фреймворке Сургезз предоставляется на его веб- сайте (кНрз://огеП.1у/еХ09С). Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ь<1рз://огеП.1у/3|8у1.
Маршрутизация В рецептах этой главы рассматривается использование маршрутизации в Кеас! и библиотека геас^-гоиГег-дош. В библиотеке геась-гоигег-йош применяется декларативная маршрутизация, в ре- зультате чего маршруты обрабатываются, как любой другой компонент Кеас1. Хотя, в отличие от кнопок, текстовых полей и блоков текста, маршруты Кеас1 не имеют визуального представления, во всех других отношениях они подобны этим элемен- там. Маршруты располагаются в виртуальном НОМ-дереве компонентов. Они от- слеживают изменения в текущем местонахождении (11КЬ) браузера и позволяют включать и выключать части интерфейса. Таким образом одностраничные прило- жения создают видимость многостраничных веб-сайтов. При правильном использовании маршрутов приложение может выглядеть как лю- бой другой веб-сайт. Например, пользователи могут делать закладки для разделов приложения таким же образом, как и для страниц веб-сайта, например Википедии. Также они могут перемещаться по истории посещенных страниц, и интерфейс бу- дет вести себя соответствующим образом. Если вы только начинаете изучать Кеас1, то будет очень полезным рассмотреть возможности маршрутизации более по- дробно. 2.1. Создание интерфейсов, используя реагирующую маршрутизацию ЗАДАЧА Большинство приложений предназначено как для мобильных, так и для настольных компьютеров, поэтому качественные приложения Кеас! должны правильно рабо- тать на устройствах с экранами разных размеров. Чтобы приложение функциони- ровало должным образом, необходимо выполнить сравнительно простые модифи- кации С88, позволяющие настроить размер текста и организацию экранных элементов, а также более существенные модификации, в результате которых впе- чатления от перемещений по веб-сайту для пользователей мобильных и настольных устройств будут существенно различаться. Приложение в нашем примере отображает список с именами и адресами людей. На рис. 2.1 показан вариант исполнения этого приложения на персональном компьютере. Но такая организация элементов приложения не особенно хорошо подходит для работы на мобильных устройствах, экран которых может иметь достаточно места
только для отображения или списка имен людей, или их адресов, но не позволяет воспроизвести оба этих элемента одновременно. Каким образом можно при помощи Кеас! предоставить пользователям мобильных и настольных устройств возможности работы с приложением, соответствующие их аппаратным средствам, не создавая при этом двух полностью разных приложений? Рис. 2.1. Представление приложения, исполняющегося на настольном компьютере РЕШЕНИЕ Решение данной задачи состоит в создании реагирующих маршрутов. Реагирую- щий маршрут модифицируется соответственно размеру экрана пользователя. Для отображения информации об определенном человеке наше текущее приложение использует только один маршрут, а именно /реор1е/йд. При переходе по этому маршруту браузер отображает страницу, показанную на рис. 2.1. Здесь с левой стороны отображается список людей, а с правой — дополни- тельная информация о человеке, выбранном в этом списке. Мы модифицируем наше приложение для обработки дополнительного маршрута по адресу /реор1е. Затем мы сделаем каждый из этих маршрутов реагирующим, чтобы отображать информацию на устройствах разных типов соответствующим образом. Подробности этой модификации показаны в табл. 2.1. Таблица 2.1. Разные маршруты для разных типов устройств Маршрут Мобильное устройство Настольное устройство /реор1е Отображается список людей Выполняется переадресация по реор1е:5оте1д реор1е:1с1 Отображается подробная инфор- мация для выбранного : 1с1 Отображается список людей и подробная информация для выбранного :
Что нам требуется, чтобы реализовать этот подход? Прежде всего, если для прило- жения еще не установлена библиотека геас^-гои^ег-дош, нужно установить ее: $ прт 1П5<:а11 геасР-гоиЁег-скхп Библиотека геаср-гоирег-бот позволяет координировать текущее местонахождение браузера с состоянием приложения. Далее необходимо установить библиотеку геасг-тесИа, чтобы иметь возможность создавать компоненты Кеас€, реагирующие на изменения размеров экрана: $ прт 1пзЬа11 геасЁ-тесНа Затем создадим реагирующий компонент РеорТеСопьаЬпег для управления маршру- тами, которые мы хотим создать. На экранах небольшого размера этот компонент будет отображать или список людей, или подробную информацию для одного вы- бранного в этом списке человека. А на экранах большого размера он будет отобра- жать объединенное представление списка людей слева и подробную информацию о выбранном человеке справа. В компоненте Реор1еСопра1пег будет присутствовать компонент мебта из библиотеки геасЁ-тесИа, который функционирует подобно правилу бтеШа С88: позволяет созда- вать вывод для экранов с размерами из заданного диапазона. Он принимает в каче- стве параметра свойство диегу, позволяющее задавать набор экранных размеров. Для разделения экранов мобильных и настольных устройств мы определим только один размер экрана — малый (зтаП), как показано в листинге 2.1. Листинг 2.1. Определение размера экрана <МесИа диег1ез={{ зтаИ: " (тах-ы1сН±: 700рх) " }}> </МесИа> Компонент ме<Иа принимает в качестве параметра один компонент-потомок, ожи- дая, что это будет функция. Этой функции в качестве параметра передается объект з1ге, указывающий размер текущего экрана. В нашем случае у объекта з1ге будет атрибут зтаП, который мы можем использовать, чтобы решить, какие другие ком- поненты отображать. В листинге 2.2 приведен полный код компонента медха с уче- том этих дополнений. Листинг 2.2. Полный код компонента меЗха <МесИа диег1ез={{ зтаИ: ’’ (тах-мЫЪЬ: 700рх) ” }}> { 51хе => зхге.зтаИ ? [КОМПОНЕНТЫ ДЛЯ МАЛЫХ ЭКРАНОВ] : [КОМПОНЕНТЫ ДЛЯ БОЛЬШИХ ЭКРАНОВ] } </МесНа>
Прежде чем приступать к рассмотрению кода, возвращаемого для экранов разных размеров, полезно разобраться, каким образом компонент РеорХеСопЁаХпег будет вставляться в наше приложение. Для этого рассмотрим код основного компонента Арр (листинг 2.3). Листинг 2.3. Код основного компонента Арр 1 ХтрогХ ( ВгоизегВоиГег, Ыпк, ВоиТе, ЗизЛск } Хгот 1 геасГ-гои^ег-склп' ХтрогХ РеорХеСопХаХпег Ггот './РеорХеСопХаХпег' Й1ПСЁ1ОП Арр() { геХигп ( <ВгомзегВоиТег> <8и1Ъск> <ВоиЬе ра1Ь="/реор1е"> <РеорХеСоп1:аХпег /> </ВоиХе> <Ыпк Хо=’7реорХе">РеорХе</Ыпк> </5м1Хск> </ВгомзегВои1:ег> ) } ехрогТ де^аиХТ Арр Здесь компонент вгоизегвоигег из библиотеки геас1-гои1ег-с1от связывает наш код и НТМЬ5-историю АР1-интерфейса в браузере. Чтобы дать всем нашим маршрутам доступ к текущему адресу браузера, нам нужно поместить их в компонент ВгоизегВоиХег. В компоненте вгоизегвои^ег находится компонент 5мХГсЬ, который проверяет нахо- дящиеся в нем компоненты на наличие маршрута воихе, соответствующего текуще- му местонахождению браузера. В данном случае имеется только один маршрут, задающий пути, начинающиеся с /реор1е. В случае совпадения отображается ком- понент РеорХеСопХаХпег. При отсутствии совпадений весь код до конца компонента пропускается и отображается ссылка Мпк на путь /реор1е. Поэтому, когда пользователь открывает домашнюю страницу приложения, он увидит только ссыл- ку на страницу РеорХе. Код будет считать совпадающими маршруты, начинающиеся с заданного пути, если только не задан точный атрибут. В таком случае маршрут будет отображаться только при условии совпадения полного пути. Таким образом, если мы находимся внутри компонента РеорХеСопхаХпег, значит, мы уже на маршруте, путь которого начинается с /реорХе/... При исполнении прило- жения на устройстве с экраном малого размера нам нужно отобразить или список
людей, или подробную информацию для одного выбранного в этом списке челове- ка, но не оба эти компонента. Данная задача выполняется с помощью компонента зюхсь (листинг 2.4). Листинг 2.4. Код компонентаГзйЫ» для выбора отображаемого компонента <МесЫа диег1ез={{ зтаИ: " (тах-и1сК:И: 700рх) " }}> { 312е => 312е.зша11 ? [КОМПОНЕНТЫ ДЛЯ МАЛЫХ ЭКРАНОВ] <5мзЛск> <Кои<;е раЬЬ=' /реорТе/: 16' > <Регзоп/> </Кои1е> <Реор1еПзТ/> </5к1Лс11> : [КОМПОНЕНТЫ ДЛЯ БОЛЬШИХ ЭКРАНОВ] } </МесНа> При исполнении на устройстве с малым экраном компонент МесНа вызывает свою дочернюю функцию, передавая ей значение, соответствующее истинности выраже- ния згге.зтаП. Код исполнит компонент ЗиИсЬ, который отрисует компонент Регзоп, если текущий пусть содержит идентификатор 16. В противном случае компонент ЗуНёсЪ не найдет совпадения с данным маршрутом кои^е и отрисует компонент Реор1е1ЛзГ:. Игнорируя то обстоятельство, что у нас еще не готов код для работы с большими экранами, если исполнить имеющийся код на мобильном устройстве и щелкнуть по ссылке Реор1е на домашней странице, то будет выполнен переход по пути реор1е, в результате чего приложение отрисует компонент РеорХеЫзи. Данный компонент отображает набор ссылок на людей с путями в виде /реорк/тд1. При выборе из этого списка какого-либо человека выполняется повторная отрисовка компонентов, но на этот раз компонент Реор1есоп1:а1пег отобразит подробности выбранного человека (рис. 2.2). Пока что все идет по плану и можно двигаться дальше. А дальше нам нужно сде- лать так, чтобы приложение работало и на устройствах с большим экраном. Для этого нам необходимо генерировать реагирующие маршруты для компонента Реор1еСогла1пег для случаев неистинности выражения зтге.зшаи. Если текущий маршрут представлен в виде /реор1е/М, то можно одновременно отображать компо- ненты Реор1еЫз1 и Регзоп слева и справа соответственно (листинг 2.5). 1 Здесь мы не приводим код для компонента Реор1еЪ1з1, но его можно загрузить на СйНиЬ (Ь«р5://огеН.1у/ «/МО).
Юр Яиззе! СаПег Неапеу Ю: 28977789 Асккезз. 1095 ЗоНппу Пк1де 1оиа 81452*3853 ОерагкпелГ С1о№тд Наггтсоп 5и'|Н СайеШеапеу Еуег! Солтсу Ноу1 Каика МЛезКеИике Гига 5<д№пЫ?га Роу<е Тату Мак1упл Вегзд Рис. 2.2. Исполнение приложения на мобильном устройстве: отображение списка людей (слева) со ссылками на подробную информацию о каждом человеке (справа) | Листинг 2.5. Одновременное отображение компонентов РеорШиъ и Регвоп <сИ\7 зЕу1е={ (сИзр!ау: 'Т1ех'}}> <Реор1еЫзЁ/> <Регзоп/> </с!злг> Но, к сожалению, этот код не учитывает случай, когда текущий путь просто /реор!е. Нам нужно добавить другой компонент ЗюлсИ, который будет или отображать под- робную информацию для выбранного человека, или перенаправлять по пути /реор1е/йгз1-рег8оп-1д для первого человека в списке (листинг 2.6). I Листинг 2.6. Компонент 8*1±сЬ <с11У зЕу!е= {{<±1зр1ау: ' Г1ех')) > <Реор1еЫзЕ/> <5и1ХсЬ> <КоиТе раМ1=’/реор1е/:1сГ> <Регзоп/> </НоиЁе> <РесИгесЁ Ео={ '/реор1е/$(реор1е[0] .1с1}' }/> </8и1ёсИ> </сНу> Но компонент кесИгест в действительности не выполняет собственно перенаправле- ние браузера. Он просто обновляет текущий путь до значения /реорТе/ШзЕ-регзоп-
1а, в результате чего компонент Реор1еСопЁа1пег повторно отрисовывает страницу. Это похоже на вызов функции Ызъогу.ризио в 1ауа8спр1, с тем исключением, что к истории браузера не добавляется дополнительная страница. Если пользователь переходит до узла /реор!е, то браузер просто обновляет свое местонахождение до /реор1е/Г1Г81-рег8оп-1<1. Если теперь на настольном компьютере или планшете с большим экраном перейти к узлу /реор1е, то на экране отобразится список людей, а справа от него — подроб- ная информация о выбранном человеке (рис. 2.3). Рис. 2.3. Отображение на большом экране компонента по маршруту ЬИр://1оса1Ьоз1:3000/реор1е Окончательная версия кода компонента Реор1еСопЁа1пег приведена в листинге 2.7. трог!: МесНа Г г от ’ геас^-тесИа' нпрогЁ { КесИгесЬ, КоиЁе, ЗкКзсЪ ) 1гот 'геасЁ-гои^ег-бот' нпрогХ Регзоп Г г от './Регзоп* 1трог1: Реор1еЫзЁ Г гот './Реор1еЫзЁ' йпрогГ реор1е €гот './реор1е' сопзЪ Реор1еСоп1:а1пег =()=>( геЬигп ( <МесИа диег1ез={( зта11: '(тах-из.с1ГЬ: 700рх)’, Н
{(з12е) => зхге.зтаП ? ( <3и11:с?1> <КоиЁе раЁИ="/реор1е/:1с1"> <Регзоп /> </Рои1:е> <Реор1еЫзЁ /> </5м11:сЪ> ) : ( <сИу з1:у1е={{ сИзр!ау: 'Нех' }}> <Реор1еЫз1: /> <8«1ёс11> <ЕоиЁе ра1±=,’/реор1е/:1сГ> <Регзоп /> </Кои1:е> <РесИгес1: ёо={ '/реор1е/${реор!е [0] .1с1}'} /> </8ы1<:ск1> </сИу> ) } </МесИа> ) } ехрогг (ЗеРаи!!; РеорРеСопИахпег Обсуждение При первом знакомстве декларативная маршрутизация внутри компонентов может выглядеть непривычно. Для тех, кто раньше использовал только модель централи- зованной маршрутизации, декларативные маршруты на первых порах могут выгля- деть неопрятными, поскольку они разбрасывают логику приложения по несколь- ким компонентам, вместо того, чтобы содержать ее в одном файле. Вместо созда- ния компактных компонентов, которым не известно ничего о внешнем мире, мы создаем компоненты, обладающие всей информацией об используемых в при- ложении путях, что может отрицательно влиять на их портативность. Но реагирующие маршруты демонстрируют настоящую мощь декларативной мар- шрутизации. Если вас беспокоит то, что компонентам известно слишком много о путях в приложении, возможен подход с сохранением строк путей в общедоступ- ном файле. Таким образом у вас будут как компоненты, которые модифицируют свои действия в зависимости от текущего пути, так и централизованный набор конфигураций путей. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу НИрз ://огеП.1у/17лМП.
2.2. Размещение состояния в маршрутах ЗАДАЧА Часто бывает полезным управлять внутренним состоянием компонента, отображая его в маршруте. Например, в листинге 2.8 приведен код компонента КеасХ, который отображает две информационные вкладки: одна для маршрута /реор1е, а другая для маршрута /оГЯсез. | Листинг 2.8. Код компонента КеасХ, отображающий две вкладки * ХшрогХ { изеЗХаХе } Хгош 1геасХ' йпрогЪ Реор1е Хгогп './РеорХе' нпрогХ ОХХХсез Хгот 1 ./ОШсез' ХтрогХ './АЬоиХ.сзз' сопзХ ОХдАЬоиЪ = ()=>{ сопзё [ХаЫд, зеХТаЫс!] = изе31а^е('реорХе') геХигп ( <с!1лл сХаззЫате="АЬоиХ"> <сИу сХаззЫате="АЬоиХ-ХаЬз',> <с1гу опСИск= {() => зе^ТаЫй (' реор1е')} с!аззЫате={ ГаЫб === 'реорХе' ? ’АЬоиГ-ХаЬ асХХуе' : ' АЬоиХ-'ХаЬ' } РеорХе </сИу> «Ну опСХ1ск={ () => зеХТаЫсЦ ’оХХХсез')} сХаззЫате={ ХаЫд === 'оХХХсез' ? 'АЬоиХ-ХаЬ асХХуе’ : 'АЬоиХ-ХаЬ' } > ОХХХсез </<±И7> </д1У> {ХаЫд === 'реорХе' && <РеорХе />) {ХаЫд === 'оХХХсез' && <0ХХ1сез />} </(Цу> ) ехрогХ (ХеГаиХХ: ОХс1АЬои1
Когда пользователь щелкнет по одной из вкладок, обновляется переменная гаЫс! и отображается соответствующий компонент Реор1е или ошсе (рис. 2.4). Но с этим кодом есть одна проблема. В частности, компонент в целом работает, но если выбрать вкладку ОГПсез, а затем обновить страницу, компонент сбрасывается снова к вкладке Реорк. В результате для страницы нельзя создать закладку на вкладке ОГПсез. Более того, создать ссылку, ведущую непосредственно на вкладку ОГЛсез, не возможно нигде в приложении. Также снижается вероятность определе- ния работы вкладок в качестве гиперссылок оборудованием для обеспечения спе- циальных возможностей, поскольку они не отрисовываются таким образом. Реор1е ОН|се§ • К|р Риззе! • Нагпзоп2\мЙ • СагГегНеапеу Рис. 2.4. По умолчанию компонент 01с1АЬоиг отображает подробную информацию о людях РЕШЕНИЕ Для решения данной проблемы мы переместим состояние ъаыа из компонента в текущее местонахождение браузера. Таким образом, вместо отрисовки компонен- та в узле /аЬоЩ с последующим использованием событий опСИск для изменения внутреннего состояния, мы будем использовать маршруты к /аЬои(/реор1е и /аЬоЩ/оГйсез, отображающие соответствующие вкладки. Выбор вкладок, таким об- разом, выстоит обновление страницы браузером. Также мы сможем создать ссылку или закладку страницы для данной вкладки. Наконец, мы сделаем вкладки настоящими гиперссылками, которые будут опреде- ляться как таковые при перемещении по странице посредством как клавиатуры, так и экранного считывателя. Чтобы реализовать все это, нам нужно установить только одну библиотеку: геаси- гои^ег-йош: $ прт 1пз1:а11 геас1:-гои1:ег-с1о1п Библиотека геасг-гои^ег-дот позволит нам синхронизировать текущий ОКЬ-адрес браузера с отображаемыми на экране компонентами. Она уже используется в нашем существующем приложении для отображения ком- понента 01аАЬои1 по пути /оНаЬоШ, как показано во фрагменте кода из файла Арр.]8 (листинг 2.9). Листинг 2.9. Код из файла Арр.]$ для отображения компонента ОХалЬоаЪ <8юЛс11> <КоиЁе раЁк="/о1с1аЬои1:">
<01дАЬоиЬ/> </КоиЬе> <р>СНоозе ап орИоп</р> </8и1ГсЬ> Полный код из этого файла можно загрузить из репозитория СйНиЬ по адресу ЬНр8://огеП.1уЛУтХ18. Мы создадим новую версию компонента 01с1АЬоит:, называющуюся АЬоиц и устано- вим его по своему собственному маршруту (листинг 2.10). Листинг 2.10. Установка компонента АЬоиЪ по своему маршруту <8ы1Хск> <ВоиЁе раЁЪ="/о1с!аЬоиЁ"> <01с1АЬои11/> </Кои1:е> <ВоиИе раЁк="/аЬои1:/:^аЫс1?,,> <АЬоиЬ/> </КоиСе> <р>Сйоозе ап орМоп</р> </8к1ХсЬ> Таким образом мы сможем использовать обе версии кода в нашем примере прило- жения. Новая версия компонента будет выглядеть идентично старой. Мы извлечем иден- тификатор иыа из компонента и переместим его по текущему пути. В результате присвоения компоненту койне пути /аЬои!/1аЫс1? наш компонент будет устанавливаться всеми тремя маршрутами: /аЬои!, /аЬоЩ/оГЯсез и /аЬои1/реор1е. Символ знака вопроса (?) означает, что параметр <;аЫс1 не является обязательным. Первая часть задачи выполнена: мы поместили состояние компонента в путь, кото- рый отображает его. Теперь нам нужно обновить компонент, чтобы взаимодейство- вать с маршрутом, а не с переменной внутреннего состояния. Обе вкладки компонента 01дАЬои1: были подписаны на оповещения от опСНск (лис- тинг 2.11). Подписка вкладок Реор1е и ОТЯсез на оповещения от опСНск «Цу опСИск={() => зеЬТаЫ<1("реор1е")} с1аззНате={ЪаЫс1 === "реорХе" ? "АЬоиЁ-ЁаЬ асНуе" : "АЬоиЪ-ЬаЬ”} > Реор1е </<Цу> «Цу опСИск={() => зеЪТаЫд("о^Лсез")} с1аззЫаше={^аЫс1 === "сЦНсез" ? "АЬои^-ЪаЬ асЦуе" : "АЬоиЁ-1:аЪ"}
О^Нсез </Й1У> Мы преобразуем эти подписки в ссылочные компоненты Ыпк, ведущие к /аЬои1/ реор1е и /аЬои(/оГНсе8. Более точно мы преобразуем их в компоненты пауЫпк. Ком- понент ^аV^^пк похож на ссылку, но обладает возможностью задавать дополнитель- ное название класса, если он ссылается на текущее местонахождение. В результате в исходном коде можно обойтись без логики с1аззКате (листинг 2.12). Листинг 2.12. Использовании ссылочного компонента Ыаиьхпк <МауЫпк Ёо="/аЬоиЁ/реор1е" с1аззМате="АЬои1:-'ЬаЬ" асЁ^еС1аззКате=,,асТ1Уе"> РеорХе </ЫауЬ1пк> <ЫауЬ1пк Ёо="/аЬоиТ/о{Г1сез" с1аззКате="АЬоиР-ЁаЬ" асРзл/,еС1аззМате="ас±1л7е,'> ОХЛсез </ЫауЫпк> Значение переменной хаьм больше не задается. Вместо этого мы переходим на новую страницу с новым значением ЁаЫб в пути. Но каким образом мы считываем это значение таыа? В коде компонента 01с!АЬоит содержимое текущей вкладки отображается следующим кодом: {1:аЫс1 === ”реор1е'’ && <Реор1е/>} {РаЫс! === "оРР1сез" && <ОГХ1сез/>} Этот код можно заменить компонентом ЗиихИ и парой компонентов ноиХе, как пока- зано в листинге 2.13. Листинг 2.13. Новый код для отображения содержимого текущей вкладки <5и/11:сЬ> <КоиЬе ра<±='/аЬоиЁ/реор1е'> <Реор1е/> </РоиХе> сКоисе раЁИ='/аЬои(:/оХГ1сез'> <ОГГ1сез/> </КоиРе> </5шЛсЬ> Все почти готово. Нам осталось только решить, что делать для пути /аЬоих, который не содержит таыа.
В старом компоненте охадьоих переменной хаЫд присваивается значение по умолча- нию при первом создании состояния: сопзХ [ХаЫс1, зеХТаЫд] = изеЗХаХе("реорХе") Этот же эффект можно получить, добавив в конец кода компонента змХхсИ команду КесНгесъ. Компонент ЗшЛсН будет по порядку обрабатывать свои дочерние компо- ненты, пока не найдет совпадающий маршрут ЕоиХе. Если для текущего пути не бу- дет обнаружено совпадающего маршрута, исполнение перейдет к команде КесНгесх, которая изменит адрес на /аЬои1/реор1е. В результате выполняется повторная отри- совка компонента АЬоих и по умолчанию выбирается вкладка РеорХе (листинг 2.14). Листинг 2 Л4. Код для отображения содержимого текущей вкладки с добавленной командой Вайхгесъ I________________________________________________:----------------- <8м1ХсЬ> <КоиХе раХИ='/аЬоиХ/реорХе'> <РеорХе/> </КоиХе> <КоиХе раХЬ=’/аЬоиХ/оХНсез'> <0ХХ1сез/> </ЕоиХе> <КесИгесХ Хо='/аЬоиХ/реорХе1/> </Зи1ХсЬ> вг Исполнение команды КесНгесх можно сделать зависимым от значения текущего пути, снабдив ее атрибутом Хгот. В таком случае атрибуту Ггош можно присвоить значение /аЬоих, чтобы по маршруту /аЬои(/реор1е перенаправлялись только мар- шруты, совпадающие с /аЬоиХ Полный код нового компонента дьоих приведен в листинге 2.15. Листинг 2Л5. Полный код нового компонента АЬоиЬ * ХтрогХ { ЫауЫпк, ЕесЛгесХ, КоиХе, ЗмХХск } Ггот 1 геасХ-гоиХег-дот' йпрогХ './АЬоиХ.сзз' йпрогХ РеорХе Хгот './РеорХе1 йпрогХ ОХХХсез Г гот ’./ОХХХсез' сопзХ АЬоиХ =()=>( <сИу сХаззМате="АЬоиХ"> <дй7 сХаззЫате="АЬоиХ-ХаЬз"> <ЫауЫпк Хо="/аЬоиХ/реорХе" сХаззКате="АЬоиХ-ХаЬ" асХ^еСХаззМате="асХ1Уе"
Реор1е </ЫауЫпк> <ЫауЫпк Ёо="/аЬои1;/отсез" с1аззКате="АЬои€-1:аЬ" ас^п7еС1аззЫа1пе="асТ1л7е" 0И1сез </ЫауЫпк> </сИ\7> <Зи1Тс±1> <Кои1:е ра<±=’7аЬоиЁ/реор1е"> <Реор1е /> </КоиЁе> <РоиЁе раЁЪ="/аЬоиР/оШсез"> <0Шсез /> </ЕоиТе> СЕесИгесЁ То="/аЬоиТ/реор1е" /> </ЗчнХсЬ> </с11У> ехрог€ с!е^аи1€ АЬои1: Нам больше не нужна внутренняя переменная таью, и теперь мы имеем чисто дек- ларативный компонент (рис. 2.5). Реор1е ОГЛсез ЗоШК ОаксЛа 18627 Зрогег Меи/з Мах!тесИез1ег ЗоиЛл О а ко 1а 04691 УУ|ЗСОП81П 910 иие|!\л/|Гг 1.аке !_аке Тгоу УУ15СОП51П 25072 Рис. 2.5. Переход по адресу Ьйр://1оса1Ьо81/аЬои(/оН1се5 посредством нового компонента Обсуждение Перенос состояния из компонентов в строку адреса может упростить код, но это всего лишь побочный эффект, который по счастливой случайности оказался полез-
ным. Настоящая польза от этого состоит в том, что наше приложение начинает вес- ти себя в меньшей степени как приложение и в большей как веб-сайт. Мы можем делать закладки страниц, а кнопки Назад и Вперед браузера работают правильно. Перенос управления в маршруты — это не какое-то абстрактное решение для целей разработки, а способ сделать поведение приложения более ожидаемым для пользо- вателей. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу ЬПр8 ://огеП.1у/туАС]. 2.3. Модульное тестирование посредством МетогуКоиХег ЗАДАЧА В приложениях В.еас1 маршруты используются для того, чтобы реализовать воз- можности браузера в большем объеме. Это позволяет делать закладки страниц, соз- давать ссылки глубоко в приложение, а также перемещаться вперед и назад по истории просмотра страниц. Но, начиная применять маршруты, мы делаем компонент зависимым от иных фак- торов, чем он сам, а именно от ОКЬ-адреса браузера. Это может показаться не такой и большой проблемой, но тем не менее имеет свои последствия. Предположим, что мы хотим выполнить модульное тестирование компонента, ос- ведомленного о своем текущем маршруте. Для примера осуществим модульное тестирование компонента аьочё из раздела 2.2 (листинг 2.16)2. Листинг 2Л6. Код модульного тестирования для компонента АЬоиъ -,в дезсгтЬе (' АЬоиЁ согсропепХ', () => { И (' зкоиХс! зИом реорХе', () => { гепбег (<АЬои1: />) ехрес± (зсгееп. де<:ВуТех1; (1 К1р Виззе!')) . ЁоВеХпТЬеОоситепЁ () }) }) Модульное тестирование отрисовывает компонент, а затем ищет в выводе имя "Клр ВиззеГ. Но в результате выполнения этого теста выдается следующее сообщение об ошибке: сопзоХе.еггог пос!е_то<1и1ез/]здот/ИЬ/]збот/У1гЁиа1-сопзо1е.]з:29 Еггог: ВпсаидЬЁ [Еггог: Хгп/агХапЁ ГаИед: Уои зкоиХс! поё изе <^аV^^пк> ои^зЫе а <Вои1:ег>] В этом примере используется библиотека Кеас1 Тезйп^ ХлЬгагу.
Ошибка была вызвана тем, что компонент ыауЫпк не смог найти компонент воиьег, расположенный выше в дереве компонентов. Это означает, что прежде чем тести- ровать компонент, его необходимо обернуть в компонент вои^ег. Кроме того, нам может потребоваться создать модульный тест, который проверяет, что компонент АЬоиь работает должным образом при его установке на каком-либо конкретном маршруте. Но даже если мы предоставим оболочку в виде компонента Кои1ег, как нам эмулировать конкретный маршрут? Данная проблема присуща не только модульному тестированию. При использова- нии библиотечного средства типа 8(огуЬоок2 3 полезно продемонстрировать, как компонент будет выглядеть после его установки в данном пути. Нам нужно что-то наподобие настоящего браузерного маршрутизатора, но с воз- можностью управлять его поведением. РЕШЕНИЕ Библиотека геаск-гоиЁег-йош содержит как раз такой маршрутизатор МетогуВои1:ег. Для внешнего мира данный маршрутизатор выглядит точно так же, как маршрути- затор вгоизегКои^ег. Разница между этими двумя маршрутизаторами состоит в том, что маршрутизатор вгоызегвоисег является интерфейсом для нижележащего АР1 истории браузера, а маршрутизатор метогувои^ег такой зависимости не имеет. Он может отслеживать текущее местонахождение и перемещаться вперед и назад по истории, но делает это с помощью простых структур памяти. Взглянем еще раз на наш модульный тест, выдающий ошибку. Но на этот раз вме- сто простой отрисовки компонента аЬоцё обернем его в компонент метогувои^ег (листинг 2.17). Листинг 2.17. Компонент АЬоиЬ в обертке компонента МетогуВоиЪег дезсг1Ье('АЬоиЁ сотропепЁ', О => { 1Ё('зИои1(1 зИон реор1е', () => { гепдег( <МетогуВоиЁег> <АЬои<: /> </МетогуВоиЁег> ехресТ: (зсгееп.деЁВуТехТ; ('К1р Виззе!')) .^оВеТпТкеОосигпепГ () Теперь, поскольку компонент мешогувои^ег вставляет в контекст эмулированную версию АР1, тест работает должным образом, т. к. это делает ее доступной для всех 2 См. раздел 1.9.
его дочерних компонентов. Теперь благодаря доступности истории компонент АЪощ может отрисовывать ссылку Ыпк или маршрут нои^е. Но компонент мептогукоиг.ег обладает дополнительным преимуществом. Поскольку он эмулирует АР1 истории браузера, ему можно присвоить полностью выдуманную историю при помощи свойства гпШаЗ-ЕгЛгтез. Этому свойству нужно задать значе- ние массива элементов истории. Массив с одним значением рассматривается как текущий ПКЬ-адрес. Таким образом можно создавать модульные тесты для про- верки работы компонента, установленного на определенном маршруте (лис- тинг 2.18). Листинг 2.18. Модульный тест компонента, установленного на определенном маршруте дезсггЬе('АЬоиТ сотропепТ', () => { 1Т('зЬои1с1 зИом о^Нсез 1Г 1п гоиТе', О => { гепйег( <МетогуКои1:ег 1П1Ё1а1Еп1:г1ез={ [{ раТЬпате: 1/аЬоиЁ/о^Лсез1 }]}> <АЬоиЁ /> < /МетогуКоЩег> ) ехресТ(зсгееп.деЁВуТехЬ('8ои1± Бакова')).ЁоВеХпТЬеБоситепТ() }) }) Можно было бы использовать компонент вгокзегвоитег средства 81огуЬоок, как в настоящем браузере, но компонент мешогуКоигег также позволяет эмулировать текущий ПКЬ, как это делается в рассказе тоАЬоиЁОШсез средства 81огуЬоок (рис. 2.6). Обсуждение Маршрутизаторы позволяют разделить подробности конечного пункта перехода и то, как этот переход реализуется. Из данного раздела очевидно одно из преиму- ществ такого подхода: мы можем эмулировать ИКЬ браузера, чтобы исследовать работу компонента на разных маршрутах. Такое разделение позволяет изменять способ перехода приложения по ссылкам без угрозы сбоя. В случае преобразования одностраничного приложения в приложение с отрисовкой на сервере, компонент БгокзегВоиЬег заменяется компонентом зсащснощег. Ссылки, которые раньше ис- пользовались для вызовов АР1 истории браузера, станут нативными гиперссылка- ми, вызывающими загрузку браузером нативных страниц. Маршрутизаторы пре- доставляют хороший пример преимущества метода отделения политики (что нужно сделать) от механизмов (как это сделать). Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу ЬНр8://огеП.1у/ХМ\У8е.
Рис. 2.6. Компонент МетпогуВои^ег позволяет эмулировать маршрут /аЬои^/оТйсез 2.4. Подтверждение ухода со страницы посредством компонента Рготр! ЗАДАЧА Иногда будет полезным уточнить у пользователя, находящегося в процессе выпол- нения какой-либо задачи, например редактирования текста, действительно ли он хочет уйти с данной страницы. Пример, как может выглядеть представление такого запроса, показан на рис. 2.7. Рис. 2.7. Запрос подтверждения ухода с текущей страницы
Эта на первый взгляд простая задача может оказаться сложной, поскольку требует обнаружения нажатия пользователем кнопки Назад, а затем реализации каким- либо способом перехвата перехода назад в истории браузера и, возможно, отмены этого перехода. Что если такой возможностью нужно оснастить несколько страниц приложения? Существует ли простой способ создать эту возможность так, чтобы ее можно было использовать в любом нуждающемся в ней компоненте? РЕШЕНИЕ Данную задачу можно решить с помощью компонента Рготръ библиотеки геасЁ- гоисег-йош, который запрашивает у пользователей подтверждение, действительно ли они хотят уйти с данной страницы. Единственная составляющая, необходимая для реализации данного решения, — это сама библиотека геасЁ-гоиЁег-аош. Установим ее: прт 1пзЁа11 геасЁ-гоиЁег-скэт Предположим, что по маршруту ЛтройаЩ у нас находится компонент тшрогЁапЁ, который позволяет пользователю редактировать текст (листинг 2.19). Листинг2.19. Компонент хщрогъапъ для редактирования текста ХгсрогЁ КеасЁ, { изеЕЁЁесЁ, изеЗЁаЁе } Г г от 'геасЁ' сопзё ТтрогЁапЁ =()=>{ сопзё 1п1Ё1а1Уа1ие = 11п1Ё1а1 Vа1ие' сопзё [йаЁа, зеЁБаЁа] = изеЗЁаЁе(1П1Ё1а17а1ие) сопзё [сНгЁу, зеЁБЁгЁу] = изеЗЁаЁе(Га1зе) изеЕХГесЁ(() => { 1Ё (даЁа !== 1П1Ё1а17а1ие) { зеЁБХгЁу(Ёгие) } }, [даЁа, 1П1Ё1а1Уа1ие]) геЁигп ( <сНу с1аззЫате="1трогЁапЁ’,> <ЁехЁагеа опСЪапде={(еуЁ) => зеЁСаЁа(еУЁ.ЁагдеЁ.уаХие)) со!з={40} гомз=(12} > {с1аЁа} </ЁехЁагеа> <Ьг /> <ЬиЁЁоп опСИск={() => зеЁСЁгЁу(Ёа1зе)} сИзаЫес1={ !сИгЁу}>
Заме </Ьи^оп> </<±гу> ехрогЁ с!еГаи11: 1трогЁап1: Компонент 1шрогЁапЁ уже самостоятельно отслеживает, отличается ли текст в поле <:ех1:агеа от исходного значения. Если да, то переменной (Игру присваивается значе- ние Ёгие. Но как нам спросить у пользователя, нажавшего кнопку Назад при уста- новленной переменной «Игру, действительно ли он хочет уйти со страницы? Добавив в код компонент Рготрр, как показано в листинге 2.20. Листинг 2.20. Добавление в код компонента РгоорЪ геРигп ( <сНу с1аззКате="1трогРапР"> <РехРагеа опСкапде={(еVР) => зеЁВаЁа(е^.Ёагде!:.уа1ие)} со1з={40} гоыз={12} {баРа} </РехРагеа> <Ьг /> <ЬиРРоп опСИск=( () => зеРБггРу(Га1зе)} сИзаЫес1={ !сИгРу}> 5ауе </ЬиРРоп> <РготрР иЪеп={с11гРу} теззаде={() => ' Бо уои геа11у ыапР Ро 1еаVе?,} /> </сИу> При нажатии пользователем кнопки Назад в процессе редактирования текста ком- понент ргогорр выводит на экран диалоговое окно с запросом подтвердить уход со страницы (рис. 2.8). Как видим, добавить запрос на подтверждение несложно, но при этом по умолча- нию выводится простое диалоговое окно функции рготрс 5ауа8спр1. Было бы не- плохо иметь выбор, каким именно образом запрашивать подтверждение ухода. Реализовать такую возможность также не представляет большой сложности. Чтобы продемонстрировать это, добавим в приложение библиотеку компонентов Ма1е- па1-1Л: $ прт 1пзРа11 '@таРег1а1-и1/соге'
Рис. 2.8. Диалоговое окно с запросом подтвердить уход со страницы Эта библиотека реализует в Кеас! стандарт Ма1епа1 Эезщп компании Ооо§1е. Мы используем ее в качестве примера, насколько легко заменить стандартное диалого- вое окно приглашения чем-то более индивидуальным. В действительности компонент Рготрь не отрисовывает никаких элементов пользо- вательского интерфейса. Он просто подает текущему маршрутизатору запрос на отображение запроса на подтверждение. По умолчанию маршрутизатор вгоызегвоиьег отображает стандартное диалоговое окно приглашения 1ауа8спр1. Это окно можно заменить на специализированное пользовательское окно. При добавлении маршрутизатора вгоизегкои^ег в дерево компонентов ему можно передать свойство, называющееся де^изегСопПппаиоп, как показано в листинге 2.21. Листинг 2.21. Добавление в дерево компонентов свойства двШзегСопПгтаЪхоп <сИу с1аззЫаше="Арр"> <Вгоызегйои1:ег де1:и8егСоп11ппа1:1оп={ (теззаде, саНЬаск) => { // Сиз^от соде доез Неге }} > <ЗиИсЬ> <Вои!:е ра!±=' /йпрог^ап!:' > <1трогЁап^/> </Во1Де> </Зи1ёсЪ> </ВгоизегВои1:ег> </Щу> Свойство де^изегСопПппаиюп представляет собой функцию, которая принимает два входных параметра: сообщение, которое нужно отобразить, и функцию обратного вызова.
Когда пользователь нажимает кнопку Назад, компонент Рготрр исполняет функцию дерцзегСопНппаРРоп, а затем ожидает перехода к функции обратного вызова со зна- чением йгие ИЛИ Га!зе. Функция обратного вызова возвращает ответ пользователя асинхронно. Компонент РготрЁ будет ожидать до тех пор, пока мы не спросим пользователя, что он хочет делать. Вот таким образом мы можем создать индивидуализированный интерфейс. Создадим средствами библиотеки Ма1епа1-1Л пользовательское диалоговое окно А1егЁ, которое будем выводить вместо стандартного диалогового окна приглашения 1ауа$спр1 (листинг 2.22). Листинг 2.22. Пользовательское диалоговое окно на основе библиотеки Ма(епа1-111 пирог! Ви!!оп Ргот '@та!ег!а1-и!/соге/Ви!!оп' нпрог! В1а1од !гот ' @та!ег!а1-и1/соге/01а1од' хтрог! 0!а1одАс!1опз Ггот 1@та!ег!а1-и!/соге/С!а1одАс!1опз’ йпрогЁ В!а1одСоп!еп! Ггот '@та!ег!а1-и!/соге/В!а1одСоп!еп!' !трог! 01а1одСоп!еп1:Тех! Ргот 1@та!ег!а1-и1/соге/С1а1одСоп!еп!Тех!' 1трог! С1а!одТ1!1е Г г от ' @та!ег!а1-и!/соге/0!а1одТ1!1е' сопз! А1ег! = ({ ореп, !1!1е, теззаде, опОК, опСапсе! }) => { ге!игп ( <Б1а1од ореп={ореп) опС1озе={опСапсе!} аг!а-1аЬе!1ес1Ьу="а1ег!-<±1а1од-!!!1е" аг!а-с!езсг1Ьес1Ьу="а1егЁ-с11а1од-с1езсг1р<:1оп" <0!а1одТ1!1е 1с1="а1ег!-с1!а1од-!!1:1е,’>{12!!1е}</В!а1одТ!!1е> <0!а!одСоп!епС> <С!а!одСоп!еп!Тех! 1с1="а1ег!-с1!а1од-с1езсг!р!!оп"> {теззаде) </В!а1одСоп!еп!Тех!> </01а1одСоп1:еп1:> <В1а1одАсЁ1опз> <Ви!1:оп опСИск={ опСапсе!} со!ог="рг!тагу"> Сапсе! </ВиЁ1:оп> <Ви!!оп опСИск={опОК} со1ог="рг!тагу" аи!оГосиз> ОК </Ви!Соп> </В1а!одАс1:!опз> </0!а!од> ) }
Конечно же, какой-либо особой причины, по которой мы должны выводить именно диалоговое окно, у нас нет. Можно было бы отобразить обратный счетчик или со- общение ЗпаскВаг4 или автоматически сохранить все выполненные пользователем правки. Но мы решили выводить индивидуализированное диалоговое окно А1ег1. Чтобы использовать компонент А1егь в нашем интерфейсе, первым делом нам нуж- но создать свою функцию дехизегсопххпт.аххоп. Мы сохраним сообщение и функцию обратного вызова, а затем зададим значение булевой переменной, сообщающее, что мы хотим открыть диалоговое окно А1ег1. Соответствующий код приведен в лис- тинге 2.23. Листинг 2.23. Код для реализации функции деШзегСопГхгиаЬ.оп сопзЬ [сопХтгтОреп, зеЬСопПгтОреп] = изеЗЬабе(ХаХзе) сопзХ [сопПппМеззаде, зеЬСопХХппМеззаде] = изеЗЬабеО сопзб [сопХХгтСаХХЬаск, зеЬСопХХгтСаХХЬаск] = изеЗЬаЬеО геТигп ( <с!гу сХаззЫате="Арр"> <ВгомзегКоиЬег деьизегСопХ1гтаЫоп={ (теззаде, саХХЬаск) => { зеЬСопНгтМеззаде (теззаде) // Озе ХЫз зеЬЬег Хопп Ьесаизе саИЬаск 13 а ХипсЫоп // Используем данную установочную форму, поскольку обратный вызов является функцией зеЬСопХ1гтСаХХЬаск(() => саИЬаск) зеХСопХХгтОреп (Ьгие) }} Следует заметить, что при сохранении функции обратного вызова вместо простого оператора зеЬСопХХгтСаХХЬаск (саИЬаск) применяется оператор зеЬСопНгтСа11Ьаск( () => саИЬаск). Это объясняется тем, что возвращаемые хуком изезьаье сеттеры (зеПег — установщик) не сохраняют передаваемые им функции, а исполняют их. Затем МЫ можем задать значения сопГХгтМеззаде, сопГХппСаХХЬаск И сопНгтОреп для отрисовки компонента АХегь в интерфейсе. В листинге 2.24 приведен полный код решения из файла Арр.]з. Листинг 2.24. Полный код решения йпрогЬ { изеЗЬабе } Хгот 1геасб' нпрогХ '. /Арр. сз з' 4 ЗпаскВаг — виджет библиотеки Ма1епа1 Ое81^п, который используется для отображения сообщений в нижней части приложения.
ЬгпрогЬ { ВгоизегВоиЬег, Ыпк, ВоиЁе, ЗиёёсЬ } Г гот ' геасЁ-гоиЬег-склп' ЁтрогЬ ТтрогЬапЁ Ггот ' ./ТтрогЬапЬ' ЬтрогЁ А1егЁ Ггот '. /А1егЬ' ГипсЫоп Арр() { сопзё [сопНппОреп, зеЬСопНгтОреп] = изеЗЬаЬе (Ьа1зе) сопзё [сопЫгтМеззаде, зеЬСопНгтМеззаде] = изеЗЬаЁеО сопзё [сопНгтСаНЬаск, зеССопНгтСаНЬаск] = изеЗЬаЬеО геЬигп ( <сИу с1аззЫате="Арр"> <ВгомзегКоиЬег деЁизегСопНгтаЫоп={ (теззаде, саНЬаск) => { зеЬСопЫгтМеззаде (теззаде) // Ьзе ГМз зеЬЬег Гогт Ьесаизе саНЬаск ±з а ГипсЫоп зе(:СопГ1гтСа11Ьаск( () => саНЬаск) зеЬСопЫгтОреп (Ьгие) . }} <А1егЬ ореп={сопН ппОреп} 1^^1:1е="^еаVе раде?" теззаде^ {сопЫгтМеззаде} опОК={() => { сопНгтСаНЬаск (Ёгие) зеЁСопЫгтОреп (Ьа1зе) }} опСапсе1={() => { сопНгтСаНЬаск Ца1зе) зеЁСопНгтОреп (Га1зе) }> /> <ЗиИ:сЪ> <ВоиЬе раЬЬ="/1трог1:апЬ"> <1трогЬапЬ /> </ВоиЬе> <с!1у> <Ь1>Ноте раде</Ы> <Ыпк Ьо="/1трогЬапЬ">Со ёо ЁтрогЬапЁ раде</Ыпк> </61у> </Зн1ёсЪ> </ВгомзегКоиЬег> ) }
Теперь, когда пользователь прекращает редактирование текста, возвращаясь на предыдущую страницу, отображается индивидуализированное диалоговое окно с запросом подтвердить это действие (рис. 2.9). Рис. 2.9. Индивидуализированное диалоговое окно А1еП Обсуждение В этом разделе мы реализовали собственную версию диалогового окна Ргош1, ис- пользуя библиотеку компонентов. Но это не означает, что мы ограничены простой заменой одного диалогового окна другим. Нет абсолютно никакой причины, поче- му при попытке пользователя уйти со страницы мы не могли бы делать что-то дру- гое, например сохранить выполняемую им работу, чтобы он мог вернуться к ней в дальнейшем. Асинхронная природа функции де^изегсоп^ппаьхоп делает такую гибкость возможной. Это еще один пример, как библиотека геасъ-гогяег-склп абстра- гирует сквозную функциональность, позволяя разработчику не заниматься подроб- ностями ее реализации. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу Ьир8://огеИ.1у/1ГуоЕ. 2.5. Создание переходов посредством библиотеки Кеас1 ТгапвИюп бгоир ЗАДАЧА В нативных и десктопных приложениях часто встречается анимация для визуально- го соединения разных элементов. Например, щелчок по элементу списка вызывает его разворачивание и подробное отображение всего содержимого. А смахивание влево или вправо можно использовать для обозначения, принимает ли пользователь предлагаемую опцию или нет. Таким образом, анимационные эффекты часто применяются для обозначения изме- нений 1ЖЬ-адреса. Они укрупняют элементы, позволяя рассмотреть их подробно-
сти, выполняют переход от одного элемента списка к другому. Также можно отра- жать изменения ЫКЬ-адреса соответствующей анимацией. Но как создать анимацию при переходе от одного адреса к другому? РЕШЕНИЕ Для решения данной задачи нам потребуются библиотеки геаси-гоиеег-дот и геасЁ- ЁгапзХЁХоп-дгоир! $ прт ХпзЁаХХ геасЁ-гоиЁег-дот $ прт ХпзЁаХХ геасЁ-ЁгапзтЁХоп-дгоир Будем анимировать компонент дьочё, с которым мы работали в предыдущих разде- лах5. Компонент аьопё имеет две вкладки — Реорк и ОШсез, которые отображают- ся для маршрутов /аЬоиХ/реор!е и /аЬоиХ/оГНсез соответственно. При щелчке по любой из этих вкладок содержимое старой вкладки будет удалено с эффектом постепенного исчезновения (Габе-оиХ) и заменено новым содержимым с постепенным появлением (Габе-ш). Хотя в данном случае мы используем эффекты постепенного исчезновения и появления, с таким же успехом мы могли бы реали- зовать и более сложную анимацию, например эффект скольжения влево или вправо для удаления и помещения содержимого вкладок на экран6. Но простая анимация на основе эффектов постепенного исчезновения и появления позволит более ясно продемонстрировать сам принцип работы. В компоненте аЬопё содержимое вкладок отрисовывается компонентами РеорХе и ойХсез в соответствующих маршрутах (листинг 2.25). Листинг 2.25. Код для отрисовки вкладок Реорк и ОТйсез нпрогЁ { ^аV^^пк, РесНгесЁ, ЕоиЁе, ЗшЛск } Хгогп ' геасЁ-гоиЁег-с1от' хтрогЁ './АЬоиЁ.сзз1 ХтрогЁ РеорХе Хгот './РеорХе' ХтрогЁ ОХНсез Хгот './ОГНсез' сопзё АЬоиЁ =()=>( <<±гу сХаззЫате="АЬоиЁ"> <<±И7 сХаззЫате="АЬоиЁ-ЁаЬз"> <^аV^^пк Ёо="/аЬоиЁ/реорХе" сХаззНате="АЬоиЁ-ЁаЬ" асЁ1Л7еСХаззЫате="асЁгуе" > РеорХе </КауЫпк> 5 См. разделы 2.2 и 2.3. 6 Это часто встречается в компонентах с вкладками сторонних разработчиков. Подобная анимация укрепля- ет в уме пользователей впечатление движения слева направо по вкладкам.
<ЫауЫпк 1:о="/аЬоиЬ/о^Г1сез" с1аззПа1пе="АЬои1:-ЁаЬ" аск1УеС1аззЫате="асЁ1уе" ОШсез </^аV^^пк> </с!1У> <5кИск> <Коике ракИ="/аЬоик/реор1е"> <Реор1е /> </КоиЁе> <КоиЪе ра1к=,'/аЬои1;/о^11се5,’> <ОШсез /> </КоиЁе> <Кес11гес1: 1ю="/аЬоиЁ/реор1е'’ /> </5м11:ск> </с11^> ) ехрогЁ деГаиИ АЬои1: Теперь нам нужно анимировать компоненты, находящиеся внутри компонента Зызлси. Для этого нам потребуются следующие две вещи: ♦ способ для отслеживания изменения местонахождения (ГКГ-адреса); ♦ способ для анимации содержимого вкладки при таком изменении. Узнать об изменении местонахождения браузера можно, получая его текущее местонахождение ИЗ хука изеЬосаЫоп библиотеки геаск-гои1:ег-с1от: сопзк 1осак1оп = изеЬосаМоп () Теперь нам осталось решить более сложную задачу: собственно анимацию. Далее приводится довольно сложная последовательность событий, но будет полезным не спешить и полностью разобраться с тем, что происходит. При переходе от одного компонента к другому с эффектом анимации нам нужно удерживать на странице оба компонента. В то время как компонент оШсе посте- пенно исчезает, компонент Реор1е постепенно появляется7. Этого можно добиться, удерживая оба компонента в группе перехода. Группа перехода— это набор ком- понентов, некоторые из которых исчезают, а другие появляются. Такую группу можно создать, заключив нашу анимацию в обертку из компонента тгапзхМопСгоир. Нам также потребуется компонент сззтгапззхлоп, чтобы координи- ровать подробности анимации С88. Таким образом, в обновленном коде компонент зызлсъ заключен в двойную оболоч- ку компонентов тгапзШопСгоир и С23ТгапзИ11оп (листинг 2.26). 7 Для размещения обоих компонентов в одном и том же месте в процессе исчезновения и появления в коде используется относительное позиционирование.
Листинг 2.26. Обновленный код с оболочками ТгалзаЛхопСгоир и С32Тгапз±ъ±оп 1трог{ { ^аV^^пк, ВесИгесЁ, Кои{е, ЗкИсЬ, изеЬосаЁгоп, } {гот ' геас{-гои{ег-с!от' ипрог{ Реор1е {гот './Реор1е' йпрогЁ ОГНсез {гот './0{{1сез' 1трог{ { С58Тгапз1{1оп, ТгапзгМопСгоир, } {гот 'геас{-{гапз1{1оп-дгоир' 1шрог{ 1. /АЬоиЁ. сзз' нпрогЁ './{аде.сзз' сопз{ АЬои{ = () => { сопзё 1осаЁ1оп = изеЬоса^хоп() геЁигп ( <сНу с1аззКате="АЬои{"> <д1У с1аззЫате="АЬои{-{аЬ5"> <^аV^^пк Ёо="/аЬои{/реор1е" с1аззНате="АЬои{-{аЬ" ас{1УеС1аззМате="ас{1уе" • Реор1е </^аV^^пк> <ЫауЫпк {о="/аЬои{/оГ11сез" с1аззЫате="АЬои{-ЁаЬ" ас{д^еС1аззМате="ас11Уе" > ОРЛсез </ЫауЫпк> </с12^> <Тгапз1{1опСгоир с1аззЫате="АЬои{-1:аЬСоп{еп1:"> <С53Тгапз1Ё1оп кеу={1оса{1оп.кеу} с1аззЫатез="Гаде" Мтеои{={500} <Зи1ёсЪ 1осаМоп={1оса{1оп}> <Рои{е ра{Г1='7аЬоиГ/реор1е"> <Реор1е /> </Еои{е>
<КоиЬе раЁЬ="/аЬоиЁ/оЁИсез"> <0ГГ1сез /> </КоиЬе> <Кес11гес1: Ъо="/аЬои1:/реор1е', /> </ЗюЛсЬ> </С53Тгапз1Ыоп> </Тгапз1ЫопСгоир> </с11у> ) } ехрогЁ с!еЁаи1Ё АЬоиЁ Обратите внимание на то, что ключу группы сззтгапз1Ё1оп передается значение 1осаИоп. кеу, а компоненту зиЬёсЬ передается значение 1осаЁ1оп. Значение 1осаЁ1оп.кеу— это хешированное значение текущего ЫВЬ-адреса. Передача этого значения группе перехода будет удерживать компонент сззтгапз1л1оп в виртуальной модели ООМ до завершения анимации. Выбор пользователем одной из вкладок изменяет 1ЖЬ-адрес, в результате чего выполняется обновление компонента АЬоиЁ. Компонент тгапз1ЁюпСгсир будет удерживать существующий компонент сззтгапзтЁоп в дереве компонентов до наступления тайм-аута, т. е. в течение 500 миллисекунд. Но теперь у него также будет и второй компонент сззтгапзШоп. Каждый из этих компонентов сззтгапзЬЫоп будет сохранять активными свои дочер- ние компоненты (рис. 2.10). Рис. 2.10. Компонент Тгапз1ЫопСгоир удерживает старый и новый компоненты СЗЗТгапзгЫоп активными в виртуальной модели ООМ Нам нужно передать значения ТКЬ-адресов компонентам Зи1ёсЬ: одному для старой вкладки, а другому — для новой, которые нам нужны для того, чтобы продолжать отрисовывать свои маршруты. Теперь можно приступать к реализации собственно анимации. Компонент сззтгапзхЫоп принимает в качестве параметра свойство с1аззКатез, которому было присвоено значение гаде. Обратите внимание на множественное число в названии свойства с1аззыатез. Это нужно для того, чтобы отличить его от стандартного атри- бута с1аззМате.
Компонент сззтгапзШоп использует свойство с1аззнатез для создания четырех раз- ных классов: ♦ ^аде-епЁег ♦ ^ас1е-епЁег-асГ:1уе ♦ Габе-ехИ: ♦ 1ас1е-ех1с-ас1:1уе Класс Гайе-епГег включает компоненты, которые будут выводиться на экран в ско- ром времени. Класс Гаде-еп^ег-асГлуе содержит компоненты, которые отображаются в настоящее время. А классы ГасЗе-ехИ и Габе-ехИз-ас^уе содержат компоненты, которые начинают исчезать или находятся в процессе анимации исчезновения соответственно. Эти названия классов будут добавлены в компоненте сззтгапз1л1оп их непосредст- венным потомкам. При выполнении анимации из вкладки ОГйсез на вкладку Реор1е старый компонент сззтгапзШоп добавит класс ГасЗе-асЫуе в код НТМЬ вкладки Реор1е, а в код НТМЬ вкладки ОГНсез добавит класс ^аде-ехИ-асЁЗлге. Теперь осталось только определить сами процессы анимации С88 (листинг 2.27). Листинг 2.27. Определение анимаций С88 . Гас1е-еп1;ег { орасуЁу: 0; } . Гаде-егЛег-асЁ^е { орас1Ру: 1; ЁгапзлЛхоп: орасхЁу 250тз еазе-1п; } . ГасЗе-ехаЛ { орасаЛу: 1; } . ГасЗе-ехИз-асМуе { орасаЛу: 0; 1:гапз1Р1оп: ораса.Ьу 250тз еазе-а.п; } В классах 1асЗе-епЁег коэффициент непрозрачности компонентов плавно изменяется от 0 до 1 за счет переходов С88. А в классах ГасЗе-ехЗл коэффициент непрозрачности изменяется обратно от 1 до 0 аналогичным образом. Обычно целесообразно хра- нить определения анимационных классов в отдельном файле С88. Это позволит использовать их для других анимаций. На этом задача анимации решена. При выборе пользователем одной из вкладок со- держимое старой вкладки будет постепенно исчезать из вида, а ему на смену также постепенно будет появляться содержимое новой вкладки (рис. 2.11).
Реййе | ОЙсез К(рРи55е1 • Нагпзоп ЗуйЙ • Саг1егНеапеу Ееайе ' Сйсез $ои№ Оакома 18627 Зрогег Мет Мах1гпесКе5(ег 5ои1Ь Оако1э 04691 УУ15СОП51П 910 ШейшИх Гаке Ьаке Тгоу УУюсопяп 25072 УУ15СОП51П 910 ЬиеНм/Их 1_аке каке Тгоу УУ|5СОПЯП 25072 Рис. 2.11. Содержимое вкладки 0№сез постепенно заменяется содержимым вкладки Реор!е Обсуждение При некачественном исполнении анимации могут быть довольно раздражительны- ми. Для применения каждой анимации должно быть какое-то основание. Если вы выбираете анимацию просто потому, что она вам лично нравится, почти наверняка пользователям она нравиться не будет. В общем, прежде чем реализовать анима- цию, лучше всего выяснить некоторые аспекты: ♦ Сделает ли анимация взаимосвязь между двумя маршрутами более ясной? При- меняется ли анимация для увеличения изображения, чтобы можно было увидеть его подробности, или для продольного перемещения, чтобы можно было уви- деть какой-либо элемент, имеющий отношение к текущему элементу? ♦ Сколько должна длиться анимация? Больше чем полсекунды будет, скорее всего, слишком долго. ♦ Как анимация отразится на производительности? Переходы С88 обычно оказы- вают минимальный эффект на производительность приложения, если браузер передает соответствующую работу графическому процессору. Но что будет в случае старого браузера на мобильном устройстве? Исходный код данного рецепта можно загрузить на веб-сайте СгйНиЬ по адресу ЬНрз://огеН.1у/11Си75. 2.6. Создание защищенных маршрутов ЗАДАЧА Во многих приложениях возникает необходимость предотвращения доступа к оп- ределенным маршрутам до тех пор, пока пользователь не выполнит вход. Но как можно защитить одни маршруты, оставив открытым доступ к другим? Можно ли отделить механизмы безопасности от элементов пользовательского интерфейса для входа в систему и выхода из нее? Как это реализовать, не генерируя большой объем кода?
РЕШЕНИЕ Рассмотрим один из способов реализации защиты маршрутов в приложении Кеас1. Данное приложение содержит домашнюю страницу (маршрут /), общедоступную страницу со свободным доступом (маршрут /риЬНс), а также две закрытые страни- цы (маршруты /рпуа1е1 и /рпуа1е2), которые нужно защитить. Код приложения приведен в листинге 2.28. Листинг 2 28. Код приложения с маршрутами, требующими защиты 1шрогТ НеасЁ Нот 'геасЬ' хтроН './Арр.сз 5' 1трогЁ { ВгомзегКоиЬег, Вои<:е, 5маЛсЬ } 1г от ' геасЁ-гои^ег-бот' бтроН РиЬНс Нот './РиЬНс' бтроН РгПаЬе1 Пот '. /РгдлгаЬе!' бтроН РгбуаЬе2 Г г от 1 ./РгПаЁе2' бтроН Ноте Г г от './Ноте' ГипсНоп Арр () { геЬигп ( <6П с!аззЫате="Арр"> <ВгомзегКоиЬег> <8«1ЬсЬ> <Еои!:е ехасЬ раЬЬ="/"> <Ноте /> </НоиЬе> <В.ои1:е раЬЬ="/рг1уаЁе1"> <Рг1уаЁе1 /> </КоиЬе> <Еои^е раЬИ="/рг1уа1:е2"> <РггиаЬе2 /> </Кои1:е> <Кои<:е ехаИ раИ="/риЬИс"> <РиЬНс /> </КоиЁе> </8м1ЬсЬ> </ВгоизегКои1:ег> </бП> ) } ехрог!: бе^аиН Арр Для разработки системы безопасности мы будем использовать контекст, в кото- ром хранятся данные компонента, доступные для его потомков. Например, мар- шрутизатор вгоизегЕоиЬег с учетом контекста передает информацию о маршрутах находящимся в нем компонентам Роиге. Мы создадим специальный контекст, называющийся зесигИуСоптехи (листинг 2.29).
| Листинг 2.29. Код контекста Зеошгх^опРехб '_______________________________....... ,. . . ... 1трогб Реасб Г г от 'геасб' СОП31: ЗесигзЛуСопбехб = Неасб. сгеабеСопбехб ({}) ехрогб с1еГаи1б ЗесигхРуСопбехР По умолчанию наш контекст имеет значение пустого объекта. Нам нужен какой- либо способ, чтобы добавить в контекст функции для входа в систему и выхода из нее. Эта задача решается созданием компонента Зесиг1туРгоу1дег (листинг 2.30). Листинг 2.30. Код компонента ЗесигИуРгоухйаг ипрогб { изеЗбабе } Ггот 'геасб' бтрогб ЗесигббуСопбехб б гот 1. /ЗесигббуСопбехб' сопзб ЗесигббуРгоубйег = (ргорз) => { сопзб [1оддесПп, зебЬоддесПп] = изеЗбабе(ба!зе) гебигп ( <Зесиг1буСопбехб. Ргсл/ХсЗег уа!ие={{ 1од1п: (изегпате, раззиогй) => { // Примечание для группы разработки: // Возможно, здесь нужен более высокий уровень безопасности... 16 (изегпате === ' бгес!' && раззиогс! === ’раззиогсГ) { зебЬоддесПп(бгие) } Ь 1одоиб: () => зебЬоддесПп(ба!зе), 1оддес11п, }} > {ргорз. сЬНдгеп} </Зесиг1буСопбехб. Ргчту1с1ег> ) } ехрогб с!ебаи1б Зесиг1буРгоу1с1ег В реальной системе код был бы значительно другим. Скорее всего, компонент для входа в систему и выхода из нее можно было бы создать, используя веб-службу или стороннюю систему безопасности. Но в нашем примере компонент ЗесигббуРгоубйег отслеживает, был ли выполнен вход, используя простое булево значение 1оддесПп. Этот компонент сохраняет в контексте следующие три элемента: ♦ функцию для входа в систему (1од1п); ♦ функцию для выхода из системы (1одоиб); ♦ булево значение, обозначающее, выполнен ли вход или нет (1оддеспм).
Эти три элемента будут доступны любому компоненту, находящемуся в компонен- те 5есиг1ЕуРгоуЕ.<1ег. Чтобы обеспечить доступ к этим функциям любому компоненту внутри компонента ЗесигЕЕуРгоуЕбег, добавим в код специальный хук изеЗесигЕЕу (листинг 2.31). Листинг 2.31. Код хука изеЗесигЕЕу ЕтрогЕ ЗесигЕЕуСопЕехЕ Егот '. /ЗесигЕЕуСопЕехЕ' ЕтрогЕ { изеСопЕехЕ } Егот 'геасЕ' сопзЕ изеЗесигЕЕу = () => изеСопЕехЕ(ЗесигЕЕуСопЕехЕ) ехрогЕ с!еЕаи1Е изеЗесигЕЕу Создав компонент зесигЕЕуРгоуЕбег, нам нужно использовать его для защиты не- скольких из существующих маршрутов приложения. Для этого создадим еще один компонент, ЗесигеКоиЕе (ЛИСТИНГ 2.32). Листинг 2.32. Код компонента ЗесигеКоиЕе ЕтрогЕ ЬодЕп Егот './ЪодЕп' ЕтрогЕ { КоиЕе } Егот 'геасЕ-гоиЕег-йот' ЕтрогЕ изеЗесигЕЕу Егот './изеЗесигЕЕу' сопзЕ ЗесигеКоиЕе = (ргорз) => { сопзЕ { 1оддес11п } = изеЗесигЕЕу () геЕигп ( <КоиЕе {...ргорз}>{1оддес11п ? ргорз.сЬЕЕбгеп : <ЬодЕп />}</ЕоиЕе> ) } ехрогЕ (ЕеЕаиЕЕ ЗесигеКоиЕе Компонент ЗесигеКоиЕе получает текущее состояние компонента ЕоддесПп от компо- нента ЗесигЕЕуСопЕехЕ, ИСПОЛЬЗуЯ ДЛЯ ЭТОГО хук изеЗесигЕЕу. ЕСЛИ пользователь ВЫ- ПОЛНИЛ вход, то компонент ЗесигеКоиЕе отрисовывает содержимое маршрута. В про- тивном случае отображается форма ьодЕпГогт для входа в систему8. Эта форма вызывает функцию ЕодЕп, которая при успешном входе в систему по- вторно отрисовывает компонент ЗесигеКоиЕе, а затем отображает защищенные дан- ные. Для совместного использования всех этих новых компонентов файл Арр.)з моди- фицируется, как показано в листинге 2.33. Листинг 2.33. Конечная версия содержимого файла Арр приложения ЕтрогЕ './Арр.сзз' ЕтрогЕ { ВгомзегКоиЕег, КоиЕе, ЗыЕЕсЪ } Егот 'геасЕ-гоиЕег-йот' 8 Здесь мы не приводим содержимое компонента ЬодЕп, но весь его код доступен в репозитории СйНиЬ этой книги.
ипрогЁ РиЬНс Пот './РиЬНс' нпрогЬ Рг1уаЁе1 Нот '. /РгПаЬе!' мпрогЬ РгПаРе2 Нот '. /Рг1уаЁе2' 1трогЬ Ноте Пот './Ноте' ипрогЬ ЗесигИуРгоуЫег Нот '. /ЗесигдЛуРгоухбег' 1трогЬ ЗесигеКоиЬе Нот './ЗесигеВоиЬе' ГипсНоп Арр () { геРигп ( <сИу с1аззЫате="Арр"> <ВгонзегКоиЬег> <Зесиг1ЬуРгоУ1с1ег> <8мИск> <ВоиЬе ехась раЬк="/”> <Ноте /> </ВоиЬе> <5есигеКоиЬе раТЬ="/рг1УаЬе1"> <РгНаЬе1 /> </ЗесигеВоиЬе> <ЗесигеКоиЬе раЬЬ="/рг1уаЬе2"> <РгНаЬе2 /> </ЗесигеВоиЬе> <КоиРе ехасР раЬЬ="/риЫ1С"> <РиЬИс /> </ЕоиЬе> </ЗмПск> </Зесиг1ЬуРгоУ1с1ег> </ВгомзегВоиЬег> </с11у> ) } ехрогр с!еНи1Ь Арр Вся система маршрутизации заключается в оболочку из элемента ЗесигИуРгоуЫег, делая функции 1од1п(), 1одоиЬ() И компонент ГоддеЫп доступными для каждого компонента ЗесигеКоиГе. На рис. 2.12 показано отображение начальной страницы при исполнении этого приложения. При выборе ссылки РиЬНс Ра^е открывается соответствующая страница (рис. 2.13). Ноте Рпуате Раде 1 Рг । уахе Раде 2 РиЬНс Раде Апуопе сап гее ГМг раде. Рис. 2.12. Домашняя страница со ссылками на другие страницы приложения Рис. 2.13. Просмотр общедоступной страницы РиЬНс Раде возможен без входа в систему
Но при выборе ссылки Рпуа1е Ра^е 1 открывается страница с формой для входа в систему (рис. 2.14). Содержимое этой страницы (рис. 2.15) можно просмотреть только после входа в систему, введя имя пользователя /гес1 и пароль раимогс!. 1_од1п Раде Уои пеес! (о 1од 1п. (1э1ГЛ: 1гу Тгей/разгшогс!) изегпате: Рис. 2.14. Для просмотра содержимого страницы Рпуа1е Раде 1 необходимо выполнить вход в систему РпуаХе раде 1 Н1дЫу зесге! НогтаНоп Неге. | Ьодои< | Рис. 2.15. Содержимое страницы с ограниченным доступом Рпуа1е Раде 1 становится доступным после ввода правильного имени пользователя и пароля Обсуждение Настоящая безопасность может быть обеспечена только защищенными службами бэкенд-сервера. Но безопасные маршруты предотвращают возможность случайного попадания пользователей на страницы, которые не могут получать данные с сер- вера. Для лучшей реализации компонента ЗесипТуРгоутйег нужно было бы восполь- зоваться каким-либо средством авторизации по стандарту ОАщН или другими службами безопасности стороннего разработчика. Но отделение компонента ЗесигтЁуРгоугдег от пользовательского интерфейса службы безопасности (компонен- ты ьод1п и ьодоит:) и от основного приложения позволяет в дальнейшем изменять механизмы безопасности без необходимости модификации больших объемов кода самого приложения. Чтобы увидеть поведение компонентов в процессе выполнения пользователями входа в систему и выхода из нее, можно всегда создать имитированную версию компонента 5есигИуРгоУ1с1ег для использования в модульных тестах. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу ЪНр8://огеП.1у/Ки173.
Управление состоянием При управлении состоянием в Кеас1 требуется сохранение не только данных, но и зависимостей по данным. Зависимости являются неотъемлемым элементом для работы КеасТ, обеспечивая эффективное обновление страниц только тогда, когда это необходимо. Вследствие этого управление зависимостями по данным — ключевой аспект управления состоянием в Кеас1. В этой главе мы рассмотрим большинство инстру- ментов и методов, используемых для обеспечения эффективного управления зави- симостями по данным. Ключевое звено в первом рецепте— преобразователь данных (6а1а гес1исег)— функция, которая принимает один объект или массив и возвращает его модифици- рованную версию. Эта простая концепция лежит в основе большей части управле- ния состоянием в Кеас1. Мы рассмотрим нативное использование функций преоб- разования в Кеас(, а также применение библиотеки Кедих для управления данными посредством преобразователей на уровне приложений. Также мы рассмотрим селекторные функции, которые принимают предоставлен- ные преобразователями состояния и возвращают их данные. Селекторные функции помогают нам игнорировать нерелевантные данные, тем самым значительно повы- шая производительность кода. Попутно мы обратим внимание на простые способы проверки наличия подключе- ния к Интернету, управления данными с форм и другие приемы и хитрости для обеспечения функционирования наших приложений должным образом. 3.1. Управление сложным состоянием посредством преобразователей ЗАДАЧА Многие компоненты Кеас( очень просты. Они не делают ничего больше, кроме как отрисовывают раздел НТМЬ и, возможно, отображают несколько свойств. Но некоторые компоненты могут быть довольно сложными, потенциально тре- бующими управления несколькими частями внутреннего состояния. Рассмотрим, например, простую числовую игру-головоломку (рис. 3.1).
Головоломка Головоломка Головоломка 4 1 2 7 6 3 Ж* 5 8 1сзрос| 4 1 2 /63 7 5 8 ИсБРОС 1 2 3 4 5 6 7 8 ИсБРОС Готово! Рис. 3.1. Простая числовая игра-головоломка Данный компонент отображает матрицу из нескольких пронумерованных плиток с одним свободным местом. Щелчок по плитке рядом со свободным местом позво- ляет переместить ее в это место. Суть игры состоит в расположении всех плиток в возрастающем порядке их номеров от 1 до 8. Компонент отрисовывает лишь небольшой объем НТМЬ-кода, но требует довольно сложной логики и сохранения данных. В частности, ему нужно будет запоминать местонахождение плиток, знать, можно или нет перемещать данную плитку и как переместить ее, знать, когда игра завершена, а также уметь выполнять другие дей- ствия, например перемешивать плитки для сброса игры. Весь этот код вполне можно поместить в один компонент, но такой подход услож- нит его тестирование. Для тестирования можно было бы воспользоваться библио- текой Кеас1 Теяйп§, но с учетом того, что программа будет выполнять очень не- большой объем отрисовки кода НТМЬ, это, скорее всего, будет нерационально. РЕШЕНИЕ Для работы с компонентами со сложными внутренними состояниями или требую- щими сложных манипуляций состояний можно использовать преобразователь (гедисег). Преобразователь представляет собой функцию, которая принимает два параметра: ♦ объект или массив, представляющий данное состояние; ♦ действие, описывающее требующуюся модификацию этого состояния. Функция возвращает модифицированную требуемым образом версию переданного ей состояния. Параметр действия может быть любым, но обычно это объект со строковым атри- бутом Ёуре и полезная нагрузка рауГоас! с дополнительной информацией. Строку ьуре можно рассматривать как название команды, а нагрузку рау1оаб — как ее параметры.
Например, если пронумеровать позиции плиток от 0 (верхняя левая) до 8 (нижняя правая), то дать указание преобразователю переместить плитку из верхнего левого угла можно следующей командой: {1уре: 'тсд/е', рау1оас1: 0} Чтобы полностью определить внутреннее состояние нашей игры, нам требуется объект или массив, например простой массив строк: ['1', '2', '3', пи!1, '5', '6', '7', '8', '4'] Это представляло бы следующее размещение плиток: 12 3 5 6 7 8 4 Но более гибким подходом будет наличие объекта для представления состояния, в котором атрибут 11е1т.з представляет текущую организацию плиток: { И.ешз: ['1', '2', '3', пи11, '5', '6', '7', '8', '4'] } Этот способ предпочтительнее, поскольку он позволит нашему преобразователю возвращать значения других состояний, например завершена ли игра или нет: { Изешз: '2', '3', '4', '5', '6', '7', '8', пи11], сошр1е1:е: ггие } Таким образом, мы решили, какое действие выполнять (перемещение), и знаем, какой будет структура состояния. Это означает, что мы выполнили достаточный объем проектирования, чтобы создать тестовый код (листинг 3.1). > Листинг 3.1. Первый тестовый код нпрогЁ гейисег 1гот ’./гебисег' 6езсг1Ье('гедисег’, () => { 1Ё('зЬои1с1 Ье аЫе тсл/е 1 сЗомп дар Ье1ои', () => { 1еЪ з^а^е = { хГетз: '2', '3', пи!1, '5', '6', '7', '8', '4'], ) з1:аЁе = гебисег(зЁа1:е, { Ёуре: 'тоуе', рау1оаб: 0 }) ехресТ; (зЁа1;е. 1Ёетз) . ЁоЕдиа! ([ пи11, '2', '3', '1', '5', '6', '7',
'8', '4', 1) }) 1Р (' зИоиХс! зау мкеп 1Р 13 сошрХеЁе', О => { 1еЁ зЁаЁе = { 1Ёетз: ['1', '2', '3', '4', '5', '6', '7', пи11, '8'], } зЁаЁе = гебисег(зЁаЁе, { Хуре: 'тсл/е', рауХоад: 8 }) ехресЁ(зЁаХе.сотрХеЁе) .ЁоВе(Ёгие) зХаЁе = гебисег (зРаЁе, { Хуре; 'тсл/е', рау!оас1: 5 }) ехресЁ(зРаЁе.сотрХеЁе) . ХоВе(Ёа1зе) }) }) В первом тестовом сценарии мы передаем преобразователю расположение плиток в одном состоянии, а затем проверяем, что он возвращает плитки в новом состоя- нии. Во втором тестовом сценарии мы выполняем два перемещения плиток, а затем про- веряем на наличие атрибута сотрХехе, сообщающего, что игра завершена. Теперь можно приступать к разработке кода самого преобразователя (листинг 3.2). Листинг 3.2. Код преобразователя ХипсЁ1оп ЬгуЗыар (пеыХЁетз, розШоп, 1) { 1Г (пем!Ретз[Ё] === пиИ) { сопзг Ёетр = пемХХетз[роззХхоп] пеиХЧетз[роз1Г1оп] = пеиХ^етз[ё] пеи!Ёетз[Ё] = Ёетр } } ГипсЁхоп аггаузЕдиа!(а, Ь) { Хог (1еЁ 1=0; 1 < а.ХепдРЬ; 1++) { И (а [1] !== Ь[1]) { геЁигп ЁаХзе } } геРигп Сгие } сопзё СОВКЕСТ = ['X', '2', '3', '4', '5', ' 6', 'Т, '8', пиИ] ЁипсЁгоп гедисег(зЁаЁе, асЁ1оп) { зю-ёсЬ (асЁхоп.Ьуре) { сазе 'тсл/е': { сопзр роз1Ё1оп = асЁ1оп.рау1оас1 сопзё пеиХЁетз = [. . .зЁаЁе.Иетз] СОПЗЁ СО1 = РО31Ё1ОП % 3
И (рО31Е1ОП < 6) { ЕгуЗыар(пемТЕетз, розтЕхоп, роз1Е1оп + 3) } И (роз1Е1.оп >2) { ЕгуЗиар(пеиТЕетз, роз1Е1оп, розхЕЕоп - 3) } И (со1 < 2) { Егу5ыар(пеи1Еетз, роз1Е1оп, роз1Е1оп + 1) } 1Г (со! > 0) { ЕгуЗмар(пем1Еетз, роз1Е1оп, роз1Е1оп - 1) } геЕигп { ...зЕаЕе, 1Еетз: пеи1Еетз, сотр1еЕе: аггаузЕсра!(пеиТЕетз, СОККЕСТ), } } деЕаи1Е: { Екгсж пем Еггог('Епкпомп асЕ1оп: ' + асЕхоп.Еуре) } } ) ехрогЕ <ЗеЕаи1Е гейисег На данном этапе наш преобразователь распознает единственное действие: шоуе (пе- ремещение). Полный код, который доступен в хранилище СНШиЬ (ЬИр8://огеН.1у/ д85НЗ), также содержит действия для зШНе (перемешивание) и гезеЕ (сброс). В хранилище также доступен более обширный набор тестов (Ы(р$://огеП.1у/уК№уП), которые мы использовали для создания кода в листинге 3.2. Но весь этот код не содержит никаких компонентов КеасЕ, а является чистым кодом ]а\а$спр1, поэтому его можно создать и протестировать в изоляции от внешнего мира. Соблюдайте осторожность, и для представления нового состояния создавайте новый объект в преобразователе. Таким образом обеспечивается полная незави- симость каждого нового состояния от всех предыдущих. Теперь настало время заключить наш преобразователь в оболочку компонента Кеас(, используя для этого хук изеДебисе (листинг 3.3). Листинг 3.3. Заключение преобразователя в оболочкукомпонента Яеас1 шрогЕ { изеКейисег } Егош 'геасЕ* шрогЕ гейисег Егош './гедисег* шрогЕ './Ригг1е.сзз'
сопзХ РиггХе =()=>{ сопзХ: [з^аЁе, сИзра^сЪ] = изеКедисег (гедисег, { 1Летз: ['4', '1', ’2', '7', '6', '3', пи!1, '5', '8'], }) геЬигп ( <дгу с1аззНате="Ри221е"> <с!гу с1аззНате="Ри221е-здиагез"> {зЁаЁе.ХХетз.тар((з, 1) => ( «Цу с1аззЫате={'РиггХе-здиаге ${ з ? '' : 'РиггХе-здиаге-етрЁу' }'} кеу={'зчиаге-${1}'} опСИск={ () => сИзра^сЬ(( Ёуре: 'теме1, рауХоай: 1 })} {3} </с!^> ))} </сИу> <сНу с1аззЫате="Ри221е-сопЁго1з"> <ЬиЁ1:оп сХаззКате=’'Ри2гХе-зИиХХХе" опСИск={() => <ХХзраХсЪ({ Хуре: 'зкиХХХе’ })} ЗкиХХХе </ЬиЁХоп> <ЬиР(:оп с1аззЫате="Ри221е-гезеЁ" опСИск={() => сИзраЁск({ 1уре: 'гезеТ:' })} КезеЁ </Ьи!:Ьоп> {зЁа1:е. сотр1еЁе && ( <<±1у с1аззКате="Ри221е-сотр1еЁе">Сотр1е1:е !</д1У> )} </<±Н7> ) } ехрогЁ деГаи1Ё РиггХе Хотя наш компонент головоломки выполняет довольно сложные операции, сам код КеасХ имеет сравнительно небольшой объем. Хук изеКескдсег принимает в качестве параметров функцию преобразователя и на- чальное состояние и возвращает двухэлементный массив: ♦ первый элемент массива содержит текущее состояние, возвращенное преобразо- вателем;
♦ второй элемент массива содержит функцию сНзрабсЪ (), которая позволяет от- правлять действия преобразователю. Плитки отображаются путем циклической обработки строки в массиве, предостав- ляемом В зкаие.тЁетз. При выборе пользователем плитки в положении 1 преобразователю отправляется следующая команда: опС11ск={() => сЦзра^ск({Ёуре: 'тсл/е', рау1оас1: 1})} Компонент Кеас! не имеет никакого представления о том, как перемещать плитки. Он даже не знает, может ли он вообще перемещать их. Поэтому компонент отправ- ляет действие преобразователю. Если действие тсл/е перемещает плитку, компонент автоматически выполняет по- вторную отрисовку с плитками в новом положении. О завершении игры компонент узнает ПО значению зЁаЁе.сотрТе^е: зТаТе.сотр1е1:е && <с11У с1аззКате='Ри221е-сотр1еке'>Сотр1еЁе!</с11^> Мы также добавили код для двух кнопок для действий зИиГПе (перемешивание) и гезе!: (сброс), который был опущен ранее, но содержится в полном коде в храни- лище СйНиЬ (Ьйр$://огеП.1у АУ тг.18). Создав наш компонент, можно проверить его работу. При первоначальной загрузке компонента, отображается его исходное состояние (рис. 3.2). Щелчок по плитке 7 перемещает ее на свободное место (рис. 3.3). Головоломка 4 1 2 7 6 3 5 8 СмЁшАТ^ИГсБРОС Рис. 3.3. Плитка 7 после перемещения на свободное место Рис. 3.2. Исходное состояние игры При нажатии кнопки Смешать преобразователь располагает плитки в произволь- ном порядке. Результат такого действия показан на рис. 3.4. А нажатие кнопки Сброс упорядочивает плитки по возрастанию номеров и ото- бражает текст "Готово!".
Рис. 3.4. Нажатие кнопки ЗКиТПе перемешивает плитки Головоломка 12 3 I 4 5 6 7 8 СмЁцШ^В^БРОС Готово! Рис. 3.5. При нажатии кнопки Сброс плитки упорядочиваются по возрастанию номеров Все сложные подробности выполнения действий спрятаны в функции преобразова- теля, где их можно легко протестировать, а сам компонент прост и легок в обслу- живании. Обсуждение Преобразователи— это средство для управления сложностью компонентов. Они обычно целесообразны в следующих случаях: ♦ при необходимости управлять большим объемом внутреннего состояния; ♦ при сложной логике управления внутренним состоянием компонента. При наличии любого из этих обстоятельств преобразователь может значительно облегчить управление кодом. Но подходите с осторожностью к использованию преобразователей для компонен- тов очень малого объема. Компоненты с простым состоянием и небольшим объ- емом логики, скорее всего, не нуждаются в дополнительной сложности преобразо- вателя. Кроме того, иногда даже при наличии сложного состояния существуют альтерна- тивные подходы. Например, для захвата данных с формы и проверки их правильно- сти может быть лучше создать компонент формы для проверки правильности дан- ных (см. раздел 3.3). Необходимо обеспечить отсутствие в преобразователе любых побочных эффектов. Например, избегайте сетевых вызовов для обновления сервера. При наличии в пре- образователе побочных эффектов имеется высокая вероятность сбоя в работе. В режиме разработки КеасТ может иногда (подспудно) вызывать ваш преобразова- тель, чтобы убедиться в отсутствии побочных эффектов. Если вы обнаружите, что при отрисовке компонента КеасГ вызывает ваш код, содержащий преобразователь,
дважды, знайте, что это просто Кеас1 проверяет на отсутствие нежелательного по- ведения кода. г С учетом всех этих оговорок преобразователи — это превосходный инструмент ^2 для борьбы со сложностью кода. Они являются неотъемлемой частью таких биб- лиотек, как Редих, легко поддаются повторному использованию и объединению, упрощают компоненты, а также значительно облегчают тестирование кода Реас1. Исходный код данного рецепта можно загрузить на веб-сайте СиШиЬ по адресу Ьйр8://огеП.1у/д85НЗ. 3.2. Создание возможности "Отмена" ЗАДАЧА Часть потенциала насыщенных фреймворков Дауа8спр1, таких как Кеас1, состоит в том, что создаваемые с их помощью веб-приложения могут быть очень похожими на приложения для настольных систем. Одна из распространенных возможностей настольных приложений — способность отменять выполненное действие. Некото- рые нативные компоненты в приложениях Кеас1 автоматически поддерживают функцию отмены. Например, если после редактирования данных в текстовом поле нажать клавиши <Стс1> или <С1г1> + <7>, то выполненные в тексте изменения будут отменены. Но нельзя ли распространить возможность отмены на пользова- тельские компоненты? Каким образом можно отслеживать изменения состояния, не создавая для этого больших объемов кода? РЕШЕНИЕ Если состоянием вашего компонента управляет функция преобразователя, то до- вольно общую возможность отмены можно реализовать при помощи функции пре- образователя-отменщика. Рассмотрим следующий фрагмент кода приложения головоломки из раздела 3.1: сопз(: [зЁа^е, дхзра^ск] = изеКедисег (гедисег, { Нетз: ['4', '1', ’2', '7', '6', '3', пи11, ’5', '8'], }) Этот код управляет размещением плиток в числовой игре-головоломке (рис. 3.6), используя функцию преобразования (называющуюся гейисег) и исходное состоя- ние. При нажатии кнопки Смешать компонент обновляет состояние плиток, отправляя преобразователю действие зЪиГПе: <ЬиЁЁоп с1аззЫате=’ РиггХе-зкЩЛе' опСИск={() => сИ.5раЁск({1уре: 'зЬиГПе'}) }>ЗИиГ^1е</ЬиЁЁоп> Дополнительная информация о том, что собой представляют преобразователи и когда следует их использовать, приводится в разделе 3.1.
Головоломка Головоломка Головоломка 4 1 2 7 6 3 5 8 I СБРОС1 4 1 2 6 3 7 5 8 ТЁшАГьИсБРОС 1 2 3 4 5 6 7 8 ЯсБРОС Готово! Рис. 3.6. Простая числовая игра-головоломка Мы создадим новый хук, называющийся изеипс1окес1исег, который будет прямой заменой хуку изеКедисег: сопзТ [зТаТе, сИзраТсЪ] = изеОпдоВебисег(гедисег, { 1Тетз: ['4', '1', '2', '7’, '6', '3', пиИ, '5', '8'], }) Этот хук снабдит наш компонент возможностью возвращаться назад во времени при нажатии кнопки Отменить (листинг 3.4). Листинг 3.4. Код кнопки отмены ---------------------—______.______________________________________________ <ЬиТТоп с1азз^аIпе="Риxx1е-ипс^о,, опС11ск={() => сПзраТсИЦ Туре: 'ипдо' })} Цпдо </ЬиТТоп> Добавление этого кода в компонент создает кнопку Отменить (рис. 3.7), нажатие которой отменяет последнее выполненное пользователем действие. Но каким образом выполняется эта отмена? Хотя хук изеипдокебисег сравнительно легко использовать, понять, как он работает, несколько труднее. Но в этом следует разобраться, чтобы можно было приспособить данный рецепт под свои требования. Воспользуемся тем обстоятельством, что все преобразователи работают одинаково: ♦ действие определяет, что нужно сделать; ♦ после каждого действия преобразователь возвращает обновленное состояние; ♦ при вызове преобразователя не допускается никаких побочных эффектов. Кроме того, преобразователи — это просто функции 1ауа8спр1:, которые принима- ют в качестве параметров объект состояния и объект действия.
Головоломка Головоломка Головоломка 1 2 4 6 3 7 5 8 СМЕШАТЬ ИсБРОС 4 6 3 7 5 8 ИсБРОС 1 2 4 6 3 7 5 8 [сброс ОТМЕНА ПОВТОР ОТМЕНА ПОВТОР ОТМЕНА ПОВТОР Рис. 3.7. Слева направо: исходное состояние, перемещение плитки, отмена перемещения плитки Поскольку способ работы преобразователя четко задан, можно создать новый пре- образователь (преобразователь-отменщик), который облекает другую функцию преобразования. Этот преобразователь-отменщик будет служить посредником, пе- редавая большинство действий внутреннему преобразователю, ведя историю всех предыдущих состояний. Если пользователь хочет отменить последнее действие, преобразователь-отменщик находит предшествующее этому действию состояние в своей истории и возвращает его, не вызывая для этого внутренний преобразова- тель. Начнем с создания функции преобразователя высшего порядка, которая принимает в качестве параметра один преобразователь и возвращают другой (листинг 3.5). Листинг 3.5. Код функции преобразователя высшего порядка нпрогЁ 1ос1аз11 Г г от ’1ос1азЪ' сопзё ипс!о = (гедисег) => (зЁаРе, асЁЁоп) => { 1еЁ { ипс1оН15Ёогу = [], ипбоАсЫопз = [], ...1ппег5ЁаЁе } = 1ос1азЬ.с1опе0еер (зЁаЁе) зю-ёсЪ (асЁ1оп.Ёуре) { сазе 'иМо': { (ипбоАсЁхопз.ТепдЁк > 0) { ипбоАсС1Опз.рор() 1ппег5ЁаЁе = ипдоШзЁогу.рорО } Ьгеак }
сазе 'гескэ': { 16 (ипбоАссгопзЛепдбИ > 0) { ипс1оН18Ёогу = [...ипдоНбзСогу, { ... бппегЗСаСе }] ипбоАсбхолз = [ . . .ипскэАсСбопз, ипйоАсСхопз [ипдоАсЛопз. 1епдбк - 1 ], ] 1ппег5Сабе - гейисег( гппегЗСаСе, ипйоАсЫопз [ипдоАсЫопз.ТепдСЬ - 1] ) } Ьгеак } бебаиИ: { ипбоШзСогу = [.. .ипс1оН1зСогу, { ... бппегбСаСе }] ипбоАсМопз = [. . .ипбоАсЬа.опз, асЁгоп] 1ппег5СаСе = гебисег(1ппег5СаСе, асс1оп) } } гебигп { ...1ппег56аСе, ипдоНхзбогу, ипбоАсСхолз } } ехрогЕ йеГаи1Ё ипсЗо Это довольно сложная функция, и будет полезным уделить некоторое время, чтобы разобраться, что она делает. Она создает функцию преобразователя, которая отслеживает передаваемые ей дей- ствия и состояния. Предположим, что компонент игры посылает действия смеши- вания плиток. Наш преобразователь сначала проверит, не имеет ли действие тип ипс1о (отменить) или гебо (повторить). Если нет, то он передает действие зШПе внутреннему преобразователю, который управляет плитками в игре (рис. 3.8). При передаче внутреннему преобразователю действия зЬий1е код отмены отслежи- вает существующее состояние и действие зЬиН1е, добавляя их к ипаонбзсогу (исто- Преобразователь Компонент Преобразователь-отменщик Посылаем действие смешивания _ Записываем и посылаем действие смешивания Возвращает обновленное состояние плиток Записываем и возвращаем обновленное состояние плиток Компонент Преобразователь-отменщик Рис. 3.8. Преобразователь-отменщик передает большинство действий внутреннему преобразователю
рия отмен) и ипдоАсМопз (отмена действий). Затем он возвращает состояние внут- реннего преобразователя, ипбоШзСогу и ипдоАсЫопз. Если компонент игры передает действие ипбо (отмена), то преобразователь- отменщик возвращает из истории для отмен ипйоШзгогу предыдущее состояние, полностью обходя собственный преобразователь игры (рис. 3.9). Компонент Компонент Преобразователь-отменщик Посылается действие отмены илбо Извлекается из истории и возвращает предыдущее состояние плиток Преобразователь-отменщик Рис. 3.9. Для действий отмены преобразователь-отменщик возвращает последнее состояние из истории Теперь рассмотрим код самого хука изеипдокедисег (листинг 3.6). Листинг 3.6. Код хука иаецгкк>ке<±1сег шрогС { изеЕедисег } Г г от 'геасЁ' нпрогЁ ипдо 1гот './ипбо' сопзР изеИпс1оКес1исег = (гедисег, йиЛхаТЗ^аСе) => изеКедисег(ипдо(гейисег), тптТгаХЗбаСе) ехрогС 6е^аи1С изеипбоВесШсег Код этого хука очень мал. В нем просто вызывается встроенный хук изеКебисег, но вместо прямой передачи этого преобразователя передается ип<1о(гес1исег). В резуль- тате наш компонент использует улучшенную версию предоставляемого нами пре- образователя, которая может отменять и повторять действия. В листинге 3.7 приведена обновленная версия компонента РиггРе с учетом этих модификаций. (Исходная версия приведена в разделе 3.1.) Листинг 3.7. Модифицированная версия кода компонента Рих21е нпрогЛ гебисег Г гот './гескдсег' ипрогР изеипсЗоКедисег Ггот '. /изеИпс1оКес1исег' нпрогр './РиггРе.сзз' сопзЬ РиггХе =()=>{ сопзр [зЪаЬе, сДзра^сЬ] = изеСпбоНедисег(гедисег, { Изетз: ['4', 'Г, '2', '7', '6', '3', пи!1, '5', '8'], }) ге1:игп ( <сИу с1аззМате="Ригг1е"> <сИу с1аззКате="Ри221е-здиагез">
{зЬаЬе.зЛетз.тар!(з, 1) => ( <сИу с1аззЫате={'РиггХе-здиаге $( з ? " : ' Ри221е-здиаге-етр1:у’ }'} кеу={'здиаге-${1}'} опСИск={() => сНзра^сЪ({ Ьуре: ’тоуе’, рау1оад: 1 })} {з} </сПу> ))) ссНл/ с1аззЫате="Ри221е-сопГго1з"> <ЬиМюп с1аз5Ыате="Ри221е-5ки^Г1е" опСИск={() => д1зра!:сЪ({ Ъуре: 'зЪиШе' })} > ЗЪи^Ле </ЬиЁ^оп> <ЬиСЪоп сЗ.аззКате^'РиггХе-гезеЬ" опСИск={() => <±1зра1:сЬ({ Ъуре: 'гезе^' })} НезеЪ </ЬШ±оп> </с!1У> <сИу с1аззЫате="Ри221е-сопЁго1з"> <Ьи1:1:оп сХаззЫагпе^'РиггХе-ипдо" опСИск={() => сНзраЁск({ 1:уре: ’ипбо’ })} Опдо </ЬиМ:оп> <ЬиГГоп с1аззКате=,'Ри221е-гедо" опСИск={() => сНзра^сИ({ 1:уре: 'гедо' })} > Реде </Ьи«оп> </д1У> {зЁаЁе. сотр1е1:е && ( <д1У с1аззЫате="Ри221е-со1пр1е^е,,>Сотр1е^е!</сНу> )} </с1гу> ) }
Всё, чем отличается модифицированная версия компонента игры от исходной, — это заменой хука изевебисег на хук изеипбоКедисег, а также парой дополнительных кнопок для вызова действий отмены и повтора. Если теперь запустить приложение на исполнение и передвинуть некоторые плит- ки, то эти перемещения можно отменить по одному за раз, как показано на рис. 3.10. Головоломка Головоломка Головоломка 4 1 2 7 6 3 Ж 5 8 ^МЕШАТьИсБ^ОС 4 1 2 6 3 7 5 8 СМЕШМъИсБРОС 1 2 3 4 5 6 7 8 СМЕШАГьЖсБРОс! ОТМЕНА ПОВТОР ОТМЕНА ПОВТОР ОТМЕНА ПОВТОР Рис. 3.10. Использование хука изе11пс1оКес1исег позволяет посылать действия отмены и повтора ходов Обсуждение Рассмотренная здесь функция преобразователя-отменщика будет работать с преоб- разователями, которые принимают и возвращают состояния в виде объектов. Для преобразователей, использующих массивы для управления состояниями, функцию отмены ипйо нужно переделать должным образом. Поскольку наш преобразователь-отменщик сохраняет историю всех предыдущих состояний, то, скорее всего, не следует применять его для работы с состояниями с большими объемами данных или когда ему может потребоваться выполнить огромное количество изменений. Если же все-таки подобная необходимость воз- никнет, то полезно ограничить максимальный объем истории. Кроме того, имейте в виду, что история сохраняется в памяти, и если пользователь перезагрузит страницу, то вся история пропадет. Эту проблему, однако, можно решить, сохраняя глобальное состояние в локальном хранилище при любых его изменениях. Исходный код данного рецепта можно загрузить на веб-сайте СлШиЬ по адресу ЬНр8://огеП.1у/Ог27А.
3.3. Создание форм и проверка действительности их данных ЗАДАЧА В большинстве приложений ВеасЕ используются формы в той или иной степени, и во многих приложениях для их создания применяется импровизированный под- ход. Если ваше приложение разрабатывается командой, то может обнаружиться, что одни члены команды используют отдельные переменные состояния для управ- ления полями форм. Другие же разработчики могут сохранять состояние формы в однозначном объекте. Такие объекты легко передавать в форму и извлекать из нее, но обновление каждого их поля может быть сопряжено с трудностями. Анализ достоверности данных поля часто ведет к образованию так называемого спагетти- кода, когда проверка данных с одних форм выполняется при их представлении, а данные с других форм проверяются динамически, как пользовательские типы. В одних формах сообщения проверки могут выводиться при первоначальной за- грузке формы, в других — только после изменения полей пользователем. Результатом такого разнообразия в дизайне может быть отрицательное восприятие пользователем приложения или отсутствие последовательного подхода к разработ- ке кода. Исходя из нашего опыта работы с командами разработчиков Кеас1, формы и проверка достоверности их данных являются распространенными камнями пре- ткновения для разработчиков. РЕШЕНИЕ Придать разработке форм определенный уровень целостности можно, создав ком- понент 51тр1еГогт, который будет служить оболочкой для одного или нескольких компонентов поля ввода 1приЕЕЕе1с1. В листинге 3.8 приведен пример использования компонентов ЗЕтрЕеЕогт И ЕприЕЕЕеЕс). Листинг 3.8. Использование компонентов ЗЕтрЕеЕогт и ЕприЕЕЕеЕс! ЕтрогЕ { изеЕЕЕесЕ, изеЗЕаЕе } Егот 'геасЕ' ЕтрогЕ './Арр.сзз' ЕтрогЕ ЗЕтрЕеЕогт Егот './ЗЕтрЕеЕогт' ЕтрогЕ ЕприЕЕЕеЕс) Егот ' ./ЕприЕЕЕеЕс)' сопзЕ ЕогтЕхатрЕеО = ({ опЗиЬтЕЕ, опСИапде, ЕпЕЕЕаЕУаЕие ={}})=>{ сопзЕ [ЕогтЕЕеЕдз, зеЕЕогтГЕеЕдз] = изеЗЕаЕе(ЕпЕЕЕаЕУаЕие) сопзЕ [уа1Ес1, зеЕУаЕЕй] = изеЗЕаЕе(Егие) сопзЕ [еггогз, зеЕЕггогз] = изеЗЕаЕе({}) изеЕЕЕесЕ) () => { ЕЕ (опСкапде) ( опСЬапде (ЕогтЕЕеЕс)з, уаЕЕб, еггогз) } }, [опСЬапде, ЕогтЕЕеЕсЕз, уаЕЕсЕ, еггогз])
геЬигп ( <с!^ с1а55Ыате="ТЪеЕогш"> <Ы>51пд1е Е1е1с1</к1> <81тр1еЕогтп уа1ие= {ЕоипЕ1е1с1з} опСкапде={зеЁГоппЕхеХдз} оп7аИс1={ (V, еггз) => { зеЁУаИс! (V) зе^Еггогз(еггз) }} > <1при1;Г1е1с1 пате=" Не1б1" опУа11да^е={(V) => IV || у.ХепдЁк < 3 ? 'Тоо зИогё!' : пи11 } /> <ЬиЁЁоп опСИск={ () => оп8иЬт1Л && опЗиЬггиЛ (ЕогтПеЫз) } сИзаЫес!={ !уаИс1} > ЗиЬпйЛ! </ЬиМ1оп> </81тр1еЕопп> </сНу> ) } ехрогЬ деЕаи1Ё ГоппЕхатр1еО Для отслеживания состояния формы используется один объект Гоппг±е1<1з. При из- менении любого поля формы происходит вызов метода опсъапде компонента 51тр1еГопп. Поле ±1е1с11 проверяется методом огМаИдасе, и при любом изменении состояния проверки вызывается метод опУаИа компонента 51тр1еЕогп1. Проверка выполняется только в случае взаимодействия пользователя с полем, в результате которого устанавливается флаг изменения поля. На рис. 3.11 показана форма, выводимая в результате исполнения приложения. Отслеживать значения отдельных полей нет надобности. Значения отдельных по- лей вместе с атрибутами, получаемыми из названий полей, сохраняются в объекте- значении формы. Всеми подробностями проверки занимается компонент 1при^Е1е1с1: он обновляет значение формы и решает, когда выводить сообщение об ошибке. На рис. 3.12 показан пример несколько более сложной формы, в которой компонент 51шр1егогт применяется для проверки нескольких полей. Прежде чем приступать к созданию компонентов 51тр1еЕопп и тпри^ПеЗД необходи- мо разобраться, как они будут взаимодействовать друг с другом. Компонент ТприЗзПеМ должен будет сообщить компоненту 81тр1егопп об изменении своего зна-
5!пд1е Яе1с1 РюИ 1: 1*4 ~| тоо вЬоги ЗиЬпиг* Сытел! уакл: { "Не1д1": "АВ" } Уа1к1? Га1зе Еггоге? { "Г1е1<П": "Тоо зЬогг!" } Рис. 3.11. Простая форма с проверкой действительности данных поля Ми№р1еГ|е!<18 АсМгезд 1'.__________ I*8 ~ 1 ТосвЪогИ АМгем2: I...... " ’ I ВДкйгм) АсШгева 3: I I Я»ои1геО АсИгвмД: I I Йедигвс! Рпсе:________________ |1И I Мив* Ье в* 1ем( 102 Неди*г«1 Ву:_________ ^а/м/уууу Йеци)гвс1 ЗиОпУС' Сиггеп! так»: I "абдгезз!": "АВ", "рг1се": "101" ) Уа1к1? Га1зе Еггогв? ( "асМгеаз!**: "Тоо зЬогС!", "асИгезвЭ": "КефНгвс!", "авбгезаЗ": "Кеди1гей", "ад<3геза4": "Веди1ге0", "рг1се": "Низ С Ье аТ 1еазГ 102", "геди!ге<1Ву": "Веди1гед" Рис. 3.12. Более сложная форма чения, а также действительно ли это значение или нет. Все это компонент делает при помощи контекста. Контекст представляет собой хранилище с областью видимости. Значения, сохра- ненные компонентом в контексте, видимы всем его подкомпонентам. Компонент 51тр1егопп создает контекст гоппсопьех!: и хранит в нем набор функций обратного вызова, при помощи которых любой дочерний компонент может взаимодейство- вать с формой:
йпрог! { сгеаХеСопГехС } Егот 'геасХ' сопзЕ ГоппСопЕехЕ = сгеаЕеСопЕехЕ ({}) ехрогЕ деЕаиХЕ ГогтСопЕехЕ Чтобы разобраться с работой компонента ЗХтрХеЕогт, рассмотрим его упрощенную версию, которая только отслеживает значения своих подкомпонентов, не заботясь на данном этапе об их проверке (листинг 3.9). Листинг 3.9. Упрощенная версия компонента зхирХеГохт ХтрогЕ КеасЕ, { изеСаХХЬаск, изеЕЕЕесЕ, изеЗЕаЕе } Егот 'геасЕ' ХтрогЕ '. /ЗХтрХеЕогт. сзз ’ ХтрогЕ ЕогтСопЕехЕ Егот '. /ЕогтСопЕехЕ' ЕипсЕХоп ирс!аЕеИХЕ11(оХс1УаХие, ЕХеХд, уаХие) { сопзЕ пеиУаХие = { ...оХдУаХие } пемУаХие [ПеХй] = уаХие геЕигп пенУаХие } сопзЕ ЗХтрХеЕогт = ({ сЬХХдгеп, уаХие, опСкапде, опУаХХд }) => { сопзЕ [уаХиез, зеЕУаХиез] = изеЗЕаЕе(уаХие || {}) изеЕЕЕесЕ(() => { зеЕУаХиез(уаХие || {}) }, [уаХие]) изеЕГГесЁ(() => { 1Х (опСЬапде) { опСЬапде ^аХиез) } ), [опСЬапде, уаХиез]) ХеЬ зеЁУаХие = изеСаХХЬаск( (ГхеХд, V) => зе^УаХиез((уз) => ирда^еИоЛЬ(уз, ГгеХд, у)), [зеХУаХиез] ) ХеХ: де^УаХие = изеСаХХЬаск( (ПеХб) => уаХиез [ПеХб], [уаХиез]) ХеХ ^огт = ( зеТУаХие: зеЪУаХие, уаХие: деХУаХие, } ге^игп ( <<±1У сХаззЫате="ЗХтрХеГопп-сопЪаХпег"> сГогтСоп^ехС. РгоуХсЗег уаХие={Гогт}> {сЫХдгеп} </ГогтСопЁех!:. Ргоу1дег> </сНу> ) }
В конечную версию компонента зйпрХеГопп будет добавлен код для проверки отсле- живания и вывода сообщений об ошибках, но пока проще разобраться с происхо- дящим на примере такой урезанной версии компонента. Отслеживание значений всех полей формы осуществляется в объекте состояния Vа1ие5. Форма создает две функции обратного вызова, деРУаХие И зеХУаХие, и сохраня- ет их в контексте (в виде объекта Хогт), где подкомпоненты смогут найти их. Чтобы сохранить форму Гопп в контексте, дочерние компоненты помещаются в оболочку <ЕогтСоп1:ех11. Ргсгу1с1ег>. Обратите внимание на то, что мы обернули функции обратного вызова дехУаХие и зеЪУаХие в изеСаНЬаск, что предотвращает создание компонентом новых версий этих функций при каждой отрисовке компонента зхтрХеЕогт. При каждом вызове дочерним компонентом функции Хопп.уаХиеО он получает те- кущее значение указанного поля. При вызове дочерним компонентом функции Гогт.зеС7а1ие о он обновляет это значение. Теперь рассмотрим упрощенную версию компонента хприХЕХеХд, опять же без кода проверки, чтобы было легче разобраться с его работой (листинг 3.10). Листинг 3.10. Упрощенная версия компонента тприРГХеХа ХтрогХ КеасХ, { изеСопХехХ } Хгош 'геасХ' ХтрогХ ЕогтСопХехХ Хгот 1./ЕогтСопХехХ1 ХтрогХ ' ./ХприХЕХеХсксзз' сопзХ ХприРЕХеХс! = (ргорз) => { сопзр Хопп = изеСопХехХ(ЕогтСопХехХ) 1Е (!Хогт.уаХие) { геХигп ' ХприХЕХеХс! зХюиХс! Ье ыгарреб 1п а Еогт' } сопзХ { пате, 1аЬе1, ...оХЬегРгорз } = ргорз сопзХ уаХие = Хогт.уаХие(пате) геХигп ( <сНу сХаззЫате="1приХЕХеХсГ> <1аЬе1 ИХтХЕог={пате}>{ХаЬеХ || пате):</ХаЬеХ> <ХприХ 1с1={пате} уаХие=^аХие | | 11} опСЬапде={(еуепХ) => { Еогт. зеХ\7аХие (пате, еуепХ.ХагдеХ.уаХие) }} (...оХЬегРгорз} />{' '} {} </сИу> ) }
Компонент 1приьг1е1д извлекает объект Еогт из контекста ЕогтСопЕехЕ. Если он не может найти объект Еогт, следовательно, этот объект не был помещен в оболочку компонента 81тр1еЕопп. Затем компонент отрисовывает поле хприЕ, устанавливая его значение равным значению, возвращенному функцией Еогш.уа1ие(имя_поля). При изменении значения поля компонент 1приЕГ1е1а отправляет новое значение функции Еопп.зеЕ\7а1ие (паше, еуепЕ.ЕагдеЕ.уаТие). Если требуется поле формы иного типа, нежели поле ввода, его можно заключить в оболочку какого-либо компонента наподобие рассмотренного здесь компонента 1приЕПе1с1. Код для проверки действительности вводимых значений во многом такой же, как и уже рассмотренный. Форма должна отслеживать, какие поля были изменены, а также значения каких полей недействительны, таким же образом, каким она отсле- живает свое текущее значение в объекте состояния уа1иез. Затем форма должна передать обратные вызовы для зеЕЭЕгЕу, хзОЕгЕу и зеътпуаНд. Эти обратные вызовы используются дочерними ПОЛЯМИ При исполнении своего кода опУаИдаЕе. Окончательная версия кода компонента ЗЕтрЕеЕопп, включая код проверки действи- тельности значений полей, приведена в листинге 3.11. Листинг 3.11. Окончательная версия компонента зхл^Хегогт ЕтрогЕ { изеСаИЬаск, изеЕЕЕесЕ, изеЗЕаЕе } Егот 'геасЕ' ЕтрогЕ ЕогтСопЕехЕ Егот './ЕогтСопЕехЕ' ЕтрогЕ './ЗЕтрЕеЕогт.сзз' сопзЕ ЗЕтрЕеЕогт = ({ сЬЕЕсЕгеп, уаЕие, опСкапде, оп7а!Ес! }) => { сопзЕ [уаЕиез, зеЕУаЕиез] = изеЗЕаЕе(уаЕие || {}) сопзЕ [с!ЕгЕуЕЕе1с1з, зеЕБЕгЕуЕЕеЕсЕз] = изеЗЕаЕе ({}) сопзЕ [ЕпуаЕЕдЕЕеЕбз, зеЕЕпуаЕЕдЕЕеЕдз] = изеЗЕаЕе ({}) изеЕЕЕесЕЦ) => { зеЕЕ/аЕиез (уаЕие || {}) }, ОаЕие]) изеЕЕЕесЕЦ) => { ЕЕ (опСкапде) { опСЬапде(уаЕиез) } }, [опСкапде, уаЕиез]) изеЕЕЕесЕЦ) => { 1Е (опУаЕЕй) { опУаИс) ( ОЬд есЕ. кеуз (ЕпуаИйЕхеЕйз) . еуегу ((1) => ! Епуа1Ес1ЕЕе1с1з [ 1 ]), ^пVа1^с^Е^е1с^з ) } }, [опУаЕхс!, Ет/аЕЕсПГЕеЕсЕз]) сопзЕ зеЕУаЕие = изеСаИЬаск ( (ЕЕеЕб, V) => зеЕУа1иез (^з) => (( .. .чз, [ЕЕеЕсЕ] : V })),
[зеХУаТиез] сопзЬ деЬУаХие = изеСаИЬаск ((Ые1с1) => Vа1иез [ Ле1б], Оа1иез]) сопзё зе!1В1гЬу = изеСаИЬаск ( (Не1с1) => зеХЬ1г<:уЕ1е1с1з ((дЕ) => ({ .. .сИ, [11е1сЗ] : Хгие })), [ зеЁОтгЁуПеХдз ] сопзё деЬВхгЬу = изеСаИЬаск ( (Г1е1сЗ) => СЬдесХ.кеуз (с31г1уЕ1е1с1з) ИпсХисЗез (Пе1с1), [с31гЬуГ1е1с1з] ) сопзё зеЫпуаИд = изеСаИЬаск ( (Не3.с1, еггог) => { зеЁ1пуаИсЗГ1е1с1з ((3.) => ({ .. .1, [Не3.с3]: еггог ? еггог : ипсЗеНпесЗ, })) }, [зеХТпуаИдЕХеХсЗз] сопзЬ Еопп = { зеЁУаХие: зе^УаХие, Vа1ие: деЗЛ/аХие, зеЬЬХгЬу: зе^ОхгЁу, ХзЬХг^у: деЪС1ГЁу, зеЫпуаИд: зеЫпуаИд, ге^игп ( <<±1у сХаззЫате="8ХтрХеЕопп-соп<:аХпег"> <ЕогтСопРехХ. РгоуХсЗег уаХие={Хогт) > {сЫХсЗгеп} с/ЕоппСопЁехХ. ₽^ОV^с^е^> </61У> } ехрогЕ деЕаиЗХ 81тр1еГопп А в листинге 3.12 приведена окончательная версия кода компонента 1приЬГ1е1ё. Обратите внимание на то, что поле считается измененным (сНгбед) при изменении его значения или при потере фокуса. Листинг 3.12. Окончательная версия компонента 1при#Чек} нпроН { изеСоп^ехЬ, изеЕГГесЁ, изеЗХаГе } Ггош 'геасХ' ипрогХ ЕоппСопХехЁ Ггот '. /ЕогтСопХех!;1 хтрогЬ ' ./1при1:Г1е1с1.сзз'
сопзё зр11ЁСате1Сазе = (з) => 5 .гер1асе(/([а-гО-9])([А-20-9])/д, '$1 $2') .гер1асе(/л([а-г])/, (х) => х.ЁоЦррегСазе()) сопзё 1приЁГ1е16 = (ргорз) => { сопзё Ёогт = изеСопЁехЁ(ГогтСопЁехЁ) сопзё [еггог, зеЁЕггог] = изеБЁаЁе('') сопзё ( опУаИбаЁе, пате, 1аЬе1, ...оЁкегРгорз } = ргорз 1еЁ уа1ие = Ёогт.уа1ие && Ёогт.уа1ие(пате) изеЕЁГесЁ(() => { И (опУаИбаЁе) { зеЁЕггог (опУаИбаЁе (уа1ие)) } }, [опУаИбаЁе, уа1ие]) сопзё зеЁХпуаИд = Ёогт.зеЁ1пуаН6 изеЕЁЁесЁ(() => { И (зеЁТпуаИб) { зеЁТтгаИсЦпате, еггог) ) ), [зеЁ1пуа116, пате, еггог]) И (!Ёогт.уа1ие) { геЁигп *1приЁГ1е16 зЪои16 Ье ыгарреб 1п а Ёогт’ ) геЁигп ( <61V с1азз^ате="IприЁЕ^е1сГ> <1аЬе1 ИЁт1Гог=(пате}>{1аЬе1 II зрИЁСате1Сазе(пате)} :</1аЬе1> <1приЁ 16=(пате) опВ1иг=(() => Ёогт.зеЁВ1гЁу(пате)} уа1ие=(уа1ие || ••} опСЬапде={(еуепЁ) => { Гогт.зеЁ01ГЁу(пате) Ёогт.зеЁУа1ие(пате, еуепЁ.ЁагдеЁ.уа1ие) )} (...оЁкегРгорз} />{' ') { <61у с1аз зНате="1приЁГ1е16-еггог"> {Ёогт.хзОхгЁу(пате) && еггог ? еггог : <>&пЬзр;</>) </61У> ) </61У> ) )
Обсуждение Но основе этого рецепта можно создавать много разных простых форм. Его также можно расширить для использования с любым компонентом Кеас1. Например, что- бы в компоненте ЗЕтрЕеЕогт реализовать календарь или поле выбора даты, весь ком- понент нужно всего лишь заключить в оболочку наподобие оболочки компонента ЕприЕЕЕеЕс). Но данный рецепт не дает возможности создавать формы в формах или в массивах форм. Чтобы поместить одну форму в другую, компонент зйпр1еГогт можно моди- фицировать, ЧТОбы ОН Вел Себя ПОДОбнО КОМПОНеНТу ЕприЕЕЕеЕд. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу Ы1р8://оге11.1у/2ЕГ03Г. 3.4. Часы для измерения времени ЗАДАЧА Иногда приложение Кеасг должно выполнять определенное действие в назначенное время. Действием может быть простое отображение текущего времени, опрос сер- вера через регулярные интервалы времени или смена интерфейса при наступлении вечера. Но как заставить код выполнить отрисовку в результате изменения време- ни? Как избежать слишком частой отрисовки компонентов? И как реализовать все это без излишнего усложнения кода? РЕШЕНИЕ Для решения данной задачи создадим хук изеСЕоск, который предоставит нам дос- туп к отформатированной версии значения текущей даты и времени и автоматиче- ски обновит интерфейс при изменении времени. В листинге 3.13 приведен пример использования этого хука в приложении, а на рис. 3.13 показан результат исполне- ния этого приложения. Листинг 3.13. Приложение с использованием хука изеСЕоск ЕтрогЕ { изеЕЕЕесЕ, изеЗЕаЕе } Егот 'геасЕ' ЕтрогЕ изеС1оск Егот './изеСЕоск' ЕтрогЕ СЕоскГасе Егот 1./СЕоскЕасе' ЕтрогЕ ’./ТЕскег.сзз' сопзЕ ЗЕтрЕеТЕскег = () => { сопзЕ [ЕзТЕск, зеЕТЕск] = изеЗЕаЕе(ЕаЕзе) сопзЕ ЕЕте = изеСЕоск('НН:тт:зз') изеЕЕЕесЕ(() => { зеЕТ1ск((Е) => ’Е) }, [ЕЕте])
геХигп ( <сИу с1аззМате=”Т1скег"> <<±Нл с1аз5Кате="Т1скег-с1оск"> <М>Типе ЦзИск ? 'Иск!' : 'Тоск1 ' }</Ь1> <Ьг /> <С1оскЕасе Ыте=(111те} /> </с!1у> </сИу> ) } ехрогТ (ЗеТаиИ 51тр1еТ1скег Т|те Иск! Типе Тоск! Т1те Иск! 234912 2349:13 23:49’14 Рис. 3.13. Отрисовка компонента ВипрТеТТскех в течение периода длительностью в 3 секунды Переменная 11ше содержит значение текущего времени в формате чч:мм:сс. При из- менении времени значение объекта состояния 1зТ1ск меняется между булевыми значениями истина и ложь, и в зависимости от его конечного значения на экране отображается слово Т1ск! или Тоск\. Также отображается текущее время в цифро- вом формате в виде обычного циферблата со стрелками (посредством компонента С1оскЕасе). Кроме формата даты и времени хук изеС1оск также может принимать в качестве параметра число, задающее длительность интервала обновления в миллисекундах. В листинге 3.14 приведен соответствующий код, а на рис. 3.14 — результат его ис- полнения. Листинг 3.14. Использование хука изесхоск для отображения длительности интервала хтрогХ { изеЕХХесХ, изеЗХаХе } Хгот 1геасХ' ХтрогЕ изеС1оск Егот './изеСХоск' ХтрогЕ './ТХскег.сзз'
сопзё Тикера 1Т1ске г = () => { сопзё [1зТ1скЗ, зеЁНскЗ] = изеЗЁаЁе(Га1зе) сопзё Ё1скТЬгееЗесопс1з = изеС1оск(3000) изеЕГГесЁ(() => { зеЁНскЗ ((Ё) => !ё) }, [ЁхскТкгееЗесопбз]) геЁигп ( <сНу с1аззМате="Т1скег"> <сНу с1азз№те="Т1скег-с1оск"> <Ы>{1зТ1скЗ ? '3 Зесопд Иск! ' : '3 Зесопб Тоск! ' }</Ы> {ЁЁскТИгееЗесопйз} </сНу> </61у> ) } ехрогЁ с!еГаи1Ё 1пЁегуа1Т1скег 3 Зесопд Т!ск! 2021-05-15Т23:49;12.472 3 Зесопс! Тоск! 2021-05-15123:49:15 472 ------------- Рис. 3.14. Объект 1пЁегуа1Т1скег отрисовывает компонент каждые три секунды Эта версия полезна в тех случаях, когда требуется выполнять какое-либо действие через регулярные интервалы времени, например опрашивать сетевую службу. Рассмотрите возможность опроса сетевой службы с помощью проекта часов из Г— раздела 5.1. Если хуку, осуществляющему сетевые вызовы, передать в качестве зависимости текущее значение часов, то опрос сети будет повторяться при каждом изменении этого значения. Если хуку изеС1оск передать числовой параметр, то он возвратит строку значения времени в формате 180, например 2021-0б-11Т14:50:34.70б. Для создания этого хука задействуем стороннюю библиотеку Мотеп1.]з (ЬНрз:// тотепф.сот) для форматирования даты и времени: $ прт 1пзЁа11 тотепЁ Можно также выбрать какую-либо другую библиотеку, например Оау.]з (ЬМрв:// йау.р.ог^). В таком случае можно легко переделать приведенный в листинге 3.15 код. Листинг 3.15. Код для использования хука изеСГоск ЁшрогЁ { изеЕГГесЁ, изеЗЁаЁе } кг от 'геасЁ' 1трогЁ тотепЁ к г от 'тотепЁ'
сопзё изеСХоск = (ХоппаХОгХпХе^аХ) => { сопзё ^оппаХ = ХуреоХ ГоппаХОгХпХегуаХ === 'зХгХпд' ? ХоппаХОгХпХегуаХ : 'УУУУ-ММ-ОСТНН:гот: 33.885' сопзХ ХпХегуаХ = ХуреоХ ХоппаХОгХпХегуаХ === 'питЬег1 ? Гоппа'ХОгХпХегуаХ : 500 сопзХ [гезропзе, зеХНезропзе] = изеЗХаХе ( тотепХ (пен ОаХе ()) . Хоппа X (ХоппаХ) ) изеЕХХесХ(() => { сопзХ пеыТипег = зеХХпХеп/аХ (() => { зеХКезропзе (тотепХ (пем ОаХе ()) . ХоппаХ (ХоппаХ)) }, ХпХегуаХ) геХигп () => сХеагХпХегуаХ (пеюТХтег) }, [ХоппаХ, ХпХегуаХ]) геХигп гезропзе } ехрогЕ деХаиХХ изеСХоск Из передаваемого хуку параметра хоппахогхпхегуах берутся формат даты и времени и требуемый интервал времени ХпХегуаХ, после чего создается таймер посредством метода зеххпхегуахо. В результате значение переменной гезропзе будет устанавли- ваться на каждом интервале времени, задаваемом переменной ХпхегуаХ (в миллисе- кундах). Когда переменной гезропзе задается новое значение времени, выполняется перерисовка любого компонента, взаимодействующего с хуком изесхоск. Нам необходимо обязательно аннулировать все таймеры, которые больше не задей- ствованы. Это можно сделать при помощи одной из возможностей хука изеЕХХесх. Если в конце кода хука изеЕХХесх возвратить функцию, то эта функция будет вызы- ваться при следующем исполнении хука изеЕХХесх. Таким образом, мы можем с ее помощью аннулировать старый таймер, прежде чем создавать новый. Если хуку изесхоск передать новый формат или интервал, то он аннулирует свой старый таймер и ответит, используя новый таймер. Обсуждение Этот рецепт— пример того, как посредством хуков можно решить несложную задачу простым способом. Код Кеас! (соответственно своему названию) реагирует на изменения в зависимостях. Хук изесхоск позволяет не беспокоиться о том, как исполнять код, например, каждую секунду, а просто разрабатывать код, который зависит от текущего времени и скрывает все трудные подробности создания тайме- ров, обновления состояния и обнуления таймеров. Если хук изесхоск используется в компоненте несколько раз, тогда при изменении времени может быть выполнено несколько отрисовок. Например, если одни часы форматируют время в 12-часовом формате (04:45), а другие — в 24-часовом форма-
те (16:45), тогда при наступлении новой минуты будут выполнены две отрисовки вашего компонента. Но одна лишняя отрисовка раз в минуту вряд ли значительно ухудшит производительность. Хук изеСХоск можно также вставлять внутрь других хуков. Например, если создать хук иземеззадез для извлечения сообщений из сервера, то опрос сервера через регу- лярные интервалы времени можно выполнять, вызывая внутри этого хука хук изеСТоск. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬЛр8://оге11.1у/1юЬКК. 3.5. Мониторинг состояния сетевого подключения ЗАДАЧА Предположим, что ваше приложение исполняется на сотовом телефоне, который может иногда терять подключение к сети, например при входе пользователя в мет- ро. Каким образом можно проверить потерю подключения к сети? Существует ли несложный способ посредством К.еас1 обновить интерфейс приложения, чтобы со- общить пользователю о проблеме или отключить возможности, требующие доступа к сети? РЕШЕНИЕ Эта задача решается созданием хука изеОпИпе, который будет информировать нас о состоянии подключения к сети. Нам нужен код, который будет исполняться при потере и восстановлении браузером подключения к сети. К счастью, Кеас1 содер- жит события опИпе и оЕЕИпе уровня м1пдок и Ьо<1у, которые в точности реализуют требуемое нам действие. При срабатывании этих событий текущее состояние под- ключения к сети указывается соответствующим значением Егие или Еа1зе свойства паутдаЕог. опЫпе (ЛИСТИНГ 3.16) Листинг 3.16. Использование событий опИпе и оШхпе ЕтрогЕ { изеЕЕЕесЕ, изеЗЕаЕе } Егот 'геасЕ' сопзЕ изеОпИпе =()=>{ сопзЕ [опИпе, зеЕОпИпе] = изеЗЕаЕе (пауЕдаЕог.опЫпе) изеЕЕЕесЕЦ) => { 1Е (ы1пдоы.ас1с1ЕуепЕЬ1зЕепег) { м1пс!ои.ас1с1ЕуепЕЫзЕепег ('опИпе', 0=>зеЕ0пИпе (Егие), Еа1зе) и1пскж.ас1с1ЕуепЕЫзЕепег ( 'оЕЕИпе', () => зеЕОпИпе (Еа1зе), Еа1зе
) е1зе ( доситепНЬоду.опопИпе = 0 => зеЪОпИпе(Ггие) с1оситеп^.Ьоду.опоГН1пе = 0 => зе!:0пИпе(^а15е) } Ь []) ге^игп опИпе ехроН йе^аиИ изеОпИпе Состояние подключения хука определяется значением переменной опИпе. При пер- вом исполнении хука (обратите внимание на пустой массив зависимостей) регист- рируются прослушиватели (Нз^епегз) событий браузера подключения к сети и от- ключения от нее. Когда происходит какое-либо из этих событий, переменной опИпе можно присвоить значение пие или ^а1зе. Если при этом изменяется текущее зна- чение данной переменной, то выполняется отрисовка любого подключенного к данному хуку компонента. В листинге 3.17 приведен код примера использования этого хука. < Листинг 3.17. Код с использованием хука изеОпИпе йпрогЪ изеОпИпе Нот '. /изеОпИпе ’ йпрогЁ './Арр.сзз' ^ипсМоп Арр () { сопз!: опИпе = изеОпИпе () ге1игп ( <сНу с1аззПате="Арр"> <Ы>Ые1:могк СЪескег</М> <зрап> Уои аге пом.... (опИпе ? ( <сНу с1аззЫате="Арр-1пд1са^ог-опИпе">0ЫЫЫЕ</с11у> ) : ( <сИу с1а5з^ате=’'Арр-^пс^^саЬо^-о^{Ипе,,>0ГЕ^I^Е</с^^V> Н </зрап> </с11у> ) } ехроН де^аиИ Арр При исполнении приложения на его странице отобразится наличие подключения к сети (опИпе). Если отключиться, а затем снова подключиться к сети, то сообще- ния о состоянии подключения будут ОЕГЬПЧЕ, а затем ОЫЬПЧЕ, как показано на рис. 3.15.
№№огк СЬескег ЫеЪлюгк СЬескег №№огк СЬескег Уои аге ПОЖ....0ШЫЕ Уои аге пон....ОРЕШЕ Уои аге гчм....ОЫИ№ Рис. 3.15. Код исполняется при каждом отключении и подключении устройства к сети Обсуждение Важно отметить, что данный хук проверяет наличие подключения браузера к ло- кальной сети, а не к сети Интернет или серверу. Чтобы проверить наличие подклю- чения к серверу, потребуется дополнительный код. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬНр$://огеН.1у/9Ьк8А. 3.6. Управление глобальным состоянием посредством библиотеки Кес1их ЗАДАЧА В других рецептах этой главы мы рассмотрели, как управлять сложным состоянием компонентов при помощи 1ауа8спр1 функции, называющейся преобразователем (гедисег). Преобразователи упрощают компоненты и облегчают тестирование логи- ки программы. Но что делать при необходимости повсеместного доступа к каким-либо данным, например к корзине покупателя? РЕШЕНИЕ Эту задачу можно решить, управляя глобальным состоянием приложения при по- мощи библиотеки Кедих. В ней используются те же самые преобразователи, кото- рые можно передавать функции изекедисег платформы Кеас1, но только для управ- ления одним объектом состояния для всего приложения. Кроме того, существует большое количество расширений Кебих для решения распространенных задач про- граммирования и для ускорения разработки и управления приложениями. Первым делом установим библиотеку Кедих: $ прт 1пз1;а11 гес1их Также нужно установить библиотеку Кеас1-Кедих, которая значительно облегчает использование Кедих с Кеас!: $ прт 1ПЗЁЭ11 геас±-гес1их Теперь все готово для создания приложения с корзиной покупателя (рис. 3.16). При нажатии клиентом кнопки АДй <о Ьазке! (Положить в корзину) приложение добавляет данный продукт в корзину. При повторном нажатии этой кнопки количе-
ство данного товара в корзине обновляется. Корзина используется в нескольких местах приложения, следовательно, это хороший кандидат для реализации посред- ством Кебих. В листинге 3.18 приведен код функции преобразователя для управле- ния корзиной. • Ноте ВООК Воо1$ $1а Ьоо1з МопНо 265 УЛйе. $698.62 | АсМ Ь Ье»кЫ । ЗгктЬоагй Ьоо1к Мопбо 27.5. В1ие. $825.59 | А<И >о Ьавкв! | Моип1а1пееппд Ьоой Моп<1о 27.3. Вгсшл. $634.98 [~А<М <о Ьа»км1 Вазке! ЗпоиЬоагй ЬооГ$1 х$825.59 рсбзтома I С1ааг I Рис. 3.16. Клиент добавляет товары в корзину через приложение Листинг 3.18. Функция преобразователя для управления корзиной сопзТ гедисег = (зТаТе = {}, асСгоп = {}) => { 5Ы1ТсИ (асТ1оп.Ёуре) { сазе 'Ьиу': { сопзТ Ьазке!: = з^аЬе.ЬазкеЁ ? [...зЁаТе.ЬазкеТ] : [] сопзё ех13Ё1пд = ЬазкеТ. ЛпсПпбех ( (1Лет) => 1Тет.ргос1исТ1с1 === асТ1оп.рау1оаб.ргос1исЁ1с1 ) 1Г (ех1з111пд !== -1) ( ЬазкеТ [ех1зТ1пд] .диапТтТу = ЬазкеЁ [ехгзМпд] .диапМТу + 1 } е1зе { ЬазкеЪ.ризЬЦ диапТ1Ту: 1, ...асМоп.рау1оас1 }) } геТигп { ...зЁаЁе, ЬазкеТ, } }
сазе ’сХеагВазкеЁ': { геЁигп { .. .ЗЁаЁе, ЬазкеЁ: [], } } деХаиХЁ: геЁигп { ... ЗЁаЁе } } } ехрогЁ деХаиХЁ гедисег а В данном случае мы создаем только один преобразователь. Для приложений большего размера целесообразно разбить преобразователь на несколько меньших преобразователей, которые можно объединить посредством функции Редих сошЫпеВедисегз () (Г|«рз://оге|1.1у/1УН7х). Функция преобразователя реагирует на действия покупки Ьиу и очистки корзины сХеагВазкее. Действие Ьиу или добавляет новый товар в корзину, или обновляет ко- личество товара с совпадающим номером ргодисхтд, уже находящегося в корзине. Действие сХеагвазкеЁ обнуляет массив корзины. Имея в своем распоряжении функцию преобразователя, создадим с ее помощью хранилище (зХоге) Кедих, которое будет центральным репозиторием для общего со- стояния приложения. Для этого добавим следующий фрагмент кода в какой-либо компонент высшего уровня, например файл Арр]з: ХтрогЁ { сгеаЁеЗЁоге } Ггош 'гедих' ХтрогЁ гедисег Хгош './гедисег' сопзё зЁоге = сгеаЁеЗЁоге(гедисег) В приложении хранилище должно быть доступным глобально, поэтому нужно до- бавить его в контекст компонентов, которым оно может потребоваться. Это можно сделать посредством компонента РгоуХдег библиотеки КеасХ Кедих: <РгоуХдег 5Ёоге={ЗЁоге}> Все перечисленные здесь компоненты имеют доступ к хранилищу </РгоуХдег> В листинге 3.19 приведен фрагмент файла гедисег.]з из примера приложения, пол- ный код которого доступен в репозитории СйНиЬ для этой книги (ЬНрз:// огеН.1у/]90х1). Листинг 3.19. Фрагмент файла гедисег.]* сопзё гедисег = (зЁаЁе = {), асЁХоп = {}) => ( зиХёсИ (асЁХоп.Ёуре) { сазе 'Ьиу': ( сопзё ЬазкеЁ = ЗЁаЁе.ЬазкеЁ ? [...зЁаЁе.ЬазкеЁ] : []
сопзё ехгзМпд = ЬазкеЁ. ЛпсИпдех ( (1Ёего) => 1Ёет.ргос1исЁ1с1 = асЁ1оп.рау1оас!.ргос1исЁ1с1 ) 1Г (ехгзЁтпд !== -1) { ЬазкеЁ[ех1зМпд] .диапЁаЛу = ЬазкеЁ [ехгзЁтпд] .диапЁхЁу + 1 } е1зе { ЬазкеЁ.ризк({ диапЁхЁу: 1, __асЁ1оп.рау1оас1 }) } геЁигп { ________зЁаЁе, ЬазкеЁ, } } сазе ’с1еагВазкеЁ': { геЁигп { ...ЗЁаЁе, ЬазкеЁ: [], } } де^аи1Ё: геЁигп { _____ЗЁаЁе } } } ехрогЁ с!еГаи1Ё гедисег Теперь хранилище доступно для наших компонентов, но как же им пользоваться? Библиотека Кеас! Кедих обеспечивает доступ к хранилищу через хуки. Например, считать содержимое глобального состояния можно при помощи хука изеЗе1ес±ог: сопзё ЬазкеЁ = изе2е1есЁог((зЁаЁе) => ЗЁаЁе.ЬазкеЁ) Хук изе8е1есЁог принимает в качестве параметра функцию, из которой затем можно извлечь требуемую часть общего состояния. Преобразователи работают довольно эффективно и вызывают повторную отрисовку компонентов только при изменении интересующего нас состояния. Отправить действие в центральное хранилище можно посредством хука изеОгзраЁсь: сопзё сИзраЁсЬ = изеСхзраЁсЪ() Итогом будет возврат функции сИзрагсЬ, при помощи которой можно отправлять действия в хранилище: сИзраЁсЬ ({ Ёуре: 'с1еагВазкеЁ' }) Принцип работы этих хуков основан на извлечении хранилища из текущего кон- текста. При попытке исполнить приложение, не содержащее компонент Ргоухдег, или исполнить хук изеЗе1есЁог или изеО1зраЁсЬ вне его контекста, будет выдано со- общение об ошибке, как показано на рис. 3.17.
Еггог: сои1б по! йпб геас!*гес!их сопТех! уа!ие; р1еазе х епзиге 1Ье сотропеп! !& шгаррес! 1п а <Рго71дег> ► 2 51аск Ттшпех иеге со11ар5е<1. ./5 гс/Ва 5ке1.]5/ иеЬвас к_ехро г!5__.йеТаиИ 5гс/Ва:ке1.|5:7 4 ] 1мрог1 "./ВазкеТ.с»"; 5 I 6 | ехрог! йе^аиН О => { > 7 | соп51 Ьазке! = и5е5е1ес1ог($1аТе зТа1е.Ьазке(); 8 | сопзТ <1Х5ра1сЬ = и$е01$ра1сН(); 9 I 1в | геТигп <сНу сТвззМалег'ВазкеТ'» У1еы сошрИед 16 з!аск Тгавез меге соИарзей. ./5гс/1пйех.^5 згс/1пйех.]з:7 4 | 1прог1 Арр Тгоя './Арр*; 5 I 1^*1 » а5 5е?у}.сеИогкег Г г за \/$ргуХсеМ(?гкрг‘I Рис. 3.17. Сообщение об ошибке, возвращаемое при попытке исполнить приложение без компонента Ргоухйег В листинге 3.20 приведен полный код компонента вазке!:, который считывает и очищает корзину покупателя на уровне всего приложения. Листинг 3.20. Полный код компонента ВаякеЪ -'т- * V ипрогЁ { изеБгзра^сЪ, изеЗе1есЁог } Ггот ' геас±-гес1их’ 1трог1: '. /ВазкеЁ. сзз' сопзТ Вазке!: = () => { сопз1 Ьазке<: = изеЗе1есТог ((з^а^е) => зЬаТе.ЬазкеТ) сопз1: сИзраЁсИ = изеВ1зра<:сЬ () ге1:игп ( <<И\7 с1аззКате="Вазке!;"> <к2>Ваз ке1:</Ъ2 > {Ьазке1: && Ьазке1:.1епдТ11 ? ( {Ьазке1:.тар( (зХет) => ( <с11у с1аззКате=’'ВазкеЁ-1Ёе1п"> <д1У с1аззМате="Вазке€-11:етКате">{11:ет.пате}</д1у> <сНу с1аззЫате="Вазке^-111етРгос1исТ1д"> {11: ет. ргобисЁТб) </д1У>
«Ну с1а5зЫате="ВазкеЁ-1ЛетРг1с1.пд"> с1аззНате="ВазкеЪ-1ЁетОиап€1Лу"> (зЛет.диап^зХу} </сИу> <сПу с1аззИате="Вазке1:-11е1пРг1се,,>{1Ёет.рг1се}</<±1У> </сИу> </с!1у> ))} <ЬиМ:оп опС11ск={() => сПзра^сЪ((1:уре: 'с1еагВазке<:'}) }> С1еаг </ЬиЬ1:оп> </> ) : ( 'Егорку' )} </с!1У> ) } ехрогЁ деГаиИ Вазкей Чтобы продемонстрировать добавление товаров в корзину, в листинге 3.21 при- веден код компонента воо^з, позволяющий клиенту купить товары определенного типа. хтрог!: { изеВтзраГсЪ } Г г от 'геасЁ-гедих' ХтрогЁ './ВООЁЗ.С85' сопзГ: ргодисЪз = [ { ргодис±1с1: 'ВЕ8290004', пате: '5к1 Ьоо€з1, с1езсг1рГ1оп: 'Мопскэ 26.5. №йСе.', ргхсе: 698.62, Ь { ргойисМй: 'РС6310098*, пате: ' ЗпомЬоагс! ЬооЪз', дезсггрЫоп: ’ Мопдо 27.5. В1ие.', рггсе: 825.59, }, { ргобисИс!: 'ВН5430103', пате: ’Моип^агпееггпд Ьоо^з', дезсггрЫоп: ' Мопдо 27.3. Вгоып.', рггсе: 634.98, }, ]
сопзХ ВооХз =()=>{ сопзХ сНзраХск = изеВ1зраХск() геЪигп ( <с!ги с1аззНаше="ВооЁз"> <Ы>Воо1з</Ъ1> <61 с1аззНате="Воо1з-ргодисХз,'> {ргобис!з.тар((ргоёисЪ) => ( <ск> {ргобисХ. пате} </ск> <ск!> <р> {ргобисХ. безсг ХрЫоп} < /р> <р>${ргобис!.рг1се}</р> <ЬиИоп опСИск={() => 61зра^сЬ({ Хуре: 'Ьиу1, рауХоаск ргобис! }) } > Аск! Хо Ьазке! </ЬиМоп> </ск6> </> ))} </61> </61У> ) } ехрогХ 6еХаи1Р ВооХз Эти два компонента могут появляться в совершенно разных местах дерева компо- нентов, но используют одно и то же хранилище Кедих. При добавлении продукта в корзину компонент вазкех автоматически обновляет ее состояние, отражая это изменение (рис. 3.18). Обсуждение Разработчики часто применяют библиотеку Кедих совместно с фреймворком КеасТ. Долгое время казалось, что почти каждое приложение Кеас1 содержало Кедих по умолчанию. Но, скорее всего, такое использование Кедих было избыточным и час- то неуместным. Нам приходилось видеть проекты, в которых было полностью за- прещено локальное состояние и общее состояние определялось через Кедих. Мы считаем такой подход ошибочным. Средства Кедих предназначены для управления центральным состоянием приложения, а не состоянием отдельных компонентов. Если данные представляют интерес только для одного компонента или его подком- понентов, то, скорее всего, их не следует сохранять в Кедих. Но если необходимо управление каким-либо глобальным состоянием приложения, тогда Кедих будет предпочтительным инструментом.
: Я воогз Ва8км ЗкЛххЙЬ ЗгюнЪсжй Ь<хИз1 х$825.59 РС03100М мопоотле. „ „„„„„ эдь<ю№ 2хмеа.в2 $698.62 вЕвгэоои Омг ЗтжЬоагд Ьоо*$ Мопдо 27.5. В1ие. $826.59 | Моип(апмппд Ьтйз Могкк>27.3. Вго*п $634.98 Гюиюм*1ЙГ| Рис. 3.18. Хуки Ребих-Неас! обеспечивают обновление отрисовки при добавлении товара в корзину Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЪ по адресу ЬКр$://оге11.1у/]90х1. 3.7. Сохранение состояния при обновлении страниц посредством Пебих Рег818( ЗАДАЧА Библиотека Кедих предоставляет отличные средства для централизованного управ- ления состоянием приложения. Но она имеет один небольшой недостаток: пере- загрузка страницы вызывает обнуление всего состояния (рис. 3.19). • ш ВОО18 Ваакв* «и ь«1Н 5ло»Ъо«гй ЬмЫ >382559 ядооои МотЮ265.ИЬЯв. Ио ЬООК 2 >$698.62 $698.62 «вИООЫ 1ЛШЫММ] | сю» 1 ' Воои Вавке! _... Етр*у 8к1бооМ Михк>2вБ.ИМе. $69862 ;м<цы*й1 ЗпомЛоап! ЬооЬ МооЮ 27.5. Ии*. $82559 ЗпсмЬоагй Ьоок Мотк> 27Б Нив. $8255» МоипСаПМАпд Ьоо11 МопсЮ273.Влжп $634.98 МоилЫпежтлв Ьой» Моп<к> 27.3. Впмт. 1634.98 МОЪНйж Рис. 3.19. Состояние Ребих (слева) теряется при перезагрузке страницы (справа)
Такое поведение объясняется тем, что состояние Кедих хранится в памяти. Можно ли каким-либо образом предотвратить потерю состояния? РЕШЕНИЕ Эта задача решается путем сохранения в локальном хранилище копии состояния Кедих при помощи библиотеки Кедих Рег8181. Первым делом установим эту биб- лиотеку: $ прт 1пзЁа11 гедих-регзЁзЁ Затем нужно создать постоянный преобразователь, заключив в него наш сущест- вующий преобразователь (листинг 3.22). Листинг 3.22. Постоянный преобразователь ЁгпрогЁ зЁогаде Ёгот ' гедих-регзЁзЁ/ИЬ/зЁогаде' сопзё регзЁзЁСопПд = { кеу: 1гооё', ЗЁогаде, } сопзё регз1зЁес1Вес1исег = регзЁзЁКедисег(регзЁзЁСопРЁд, гедисег) В переменной зЁогаде указывается, где будет храниться состояние Кес1их: по умол- чанию в локальном хранилище 1оса13Ёогаде. А константа регзхзЁСопЛд уточняет, где именно в локальном хранилище 1оса15Ёогаде будет храниться наше состояние: в элементе регзЁзЁ: гооё. При изменении состояния КеНих постоянный преобразова- тель регз1зЁедКедисег будет сохранять копию состояния посредством метода 1оса18Ёогаде.зеЁ1тет(' регзхзТ: гооё', ...). Теперь нам нужно создать хранилище Ведих, содержащее преобразователь регзгзЁедВедисег: сопзё ЗЁоге = сгеаЁеЗЁоге(регзЁзЁедКебисег) Нам необходимо вставить код В.ес1их Рег818( между хранилищем В.ес!их и кодом, ко- торый обращается к нему. Эта задача решается посредством компонента РегзЁзЁСаЁе (листинг 3.23). Листинг 3.23. Код компонента зрегвхзъбаЬэ йпрогЁ { РегзЁзЁСаЁе } Ёгот 'гедих-регзЁзЁ/ЁпЁедгаЁЁоп/геасЁ' ЁтрогЁ { регзЁзЁЗЁоге } Ёгот 'гедих-регзЁзЁ' сопзё регзЁзЁог = регзЁзЁЗЁоге(зЁоге) <Ргоу1дег зЁоге={зЁоге}> <РегзЁзЁСаЁе 1оад1пд={<д1У>Ьоад1пд..регз13Ёог=(регз1зЁог}> СотропепЁз Цуе Ёп Ьеге </Регз1зЁСаЁе> </РгоуЁс1ег>
Компонент РегзЕзЕСасе должен находиться внутри метода Кедих РгоуЕйег и снаружи компонентов, которые будут использовать Кедих. Этот компонент будет отслежи- вать состояние Кедих и при его потере перезагружать его из локального хранилища 1оса15Ёогаде. Загрузка данных состояния может занимать некоторое время, поэтому для индикации кратковременной занятости пользовательского интерфейса компо- ненту РегзЕзЕСаЕе можно передать компонент ЕоабЕпд, например какой-либо аними- рованный элемент типа вращающегося значка занятости компьютера. Компонент 1оасИпд будет отображаться вместо его дочерних элементов, пока выполняется за- грузка состояния К.едих. При ненадобности компонент ХоасНпд можно отключить, присвоив ему значение пин. В листинге 3.24 приведена окончательная версия кода приложения из файла Арр.] 8. Листинг 3.24. Окончательная версия кода приложения ЕтрогЕ { ВгокзегКоиЕег, НоиЕе, ЗмЕЕсЪ } Г г от ' геасЕ-гоиЕег-дот' ЕтрогЕ { РгоуЕдег } Егот ’геасЕ-гедих' ЕтрогЕ { сгеаЕеЗЕоге } Егот ’гедих' ЕтрогЕ Мепи Егот './Мели' ЕтрогЕ Ноте Егот 1./Ноте' ЕтрогЕ ВооЕз Егот './ВооЕз’ ЕтрогЕ ВазкеЕ Егот './ВазкеЕ' ЕтрогЕ './Арр.сз з' ЕтрогЕ гедисег Егот './гедисег' ЕтрогЕ { регзЕзЕЗЕоге, регзЕзЕНебисег } Егот 'гейих-регзЕзЕ' ЕтрогЕ { РегзЕзЕСаЕе } Егот 'гедих-регзЕзЕ/ЕпЕедгаЕЕоп/геасЕ' ЕтрогЕ зЕогаде Егот ’гедих-регзЕзЕ/ЕЕЬ/зЕогаде' сопзЕ регзЕзЕСопЕЕд = { кеу: ’гооЕ’, зЕогаде, } сопзЕ регзЕзЕедКедисег = регзЕзЕКедисег(регзЕзЕСопЕЕд, гебисег) сопзЕ зЕоге = сгеаЕеЗЕоге (регзЕзЕесЕКебисег) сопзЕ регзЕзЕог = регзЕзЕЗЕоге(зЕоге) ЕипсЕЕоп Арр() { геЕигп ( <с!Еу с1аз8Ыате="Арр"> <Рп?уЕс1ег зЕоге={ зЕоге }> <РегзЕзЕСаЕе 1оа<ЕЕпд={<(ЕЕу>Ьоас1Епд... </<ЕЕу>} регзЕзЕог={регзЕзЕог} > <ВгомзегКоиЕег> <Мепи /> <5ыЕЕсЪ> <КоиЕе ехасЕ раЕк="/"> <Ноте /> </КоиЕе>
<Кои1:е ра1к="/ЬооСз"> <Воо^з /> </КоиСе> </Зи1ЬсЬ> <ВазкеС /> </ВгокзегКоиСег> </Регз1зССаЪе> </РгоУ1дег> </сНу> ) } ехрогС де^аиИ Арр вил* Воо<$ 51а Ьо№ Мопдо28-5 Ми1 $696.62 |~А<М,Ы>м1мГ| ЗлошЬоаг^ Ьоо15 Мопйо 27.5. В1ие. $82559 |А<ИЬ М»Ы | Моил1впеетд ЬооЬ Мопбо 27.3. Впжп. $63498 ВаакеС 8п<ж1юагс1 ЬосШ *1825.59 РСвУЦЮ : БИЬоои 2x1693,62 вэдэдюм 1 о—« ) Ноте Воо1з Ваакас зиьоои ЗпдмЬомД ЬооШ х$825ДО рсвтом Мопдо265.1М1Ке. ЗИЬМВ 2х$69й62 169В.В2 мвгздстм (МеКоЬыШ] к_р-' _] ЗгкмЬоак! ЬооЬ Мопс1с 275. В1ие. $625.59 | Ц<ЗЬЬш'111 МоипСаг вНпд Ьоо1з Мопдо 273. Впмп. $634.98 | А<Ме>Ьм1|Ы |
Теперь при перезагрузке страницы состояние Яеёих сохраняется, как показано на рис. 3.20. Обсуждение Библиотека Кедих Рег81з1 предоставляет простой способ сохранения состояния Кедих при перезагрузке страниц. При этом следует быть осторожным, чтобы не превысить максимальный предел хранилища 1оса1зг:огаде большими объемами дан- ных Кедих. Этот предел зависит от конкретного браузера, но обычно составляет около 10 МБайт. В случаях, когда объем данных Кедих превышает этот размер, следует рассмотреть возможность сохранения некоторой части данных на сервере. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ьир$://огеН.1у/К81151. 3.8. Вычисление производного состояния посредством Пеее1ес1 ЗАДАЧА Во многих случаях, прежде чем отображать данные состояния приложения, извле- ченные во внешний объект при помощи Кебих, их нужно подвергнуть той или иной обработке. Вернемся к приложению, которое мы рассматривали в нескольких пре- дыдущих рецептах этой главы (рис. 3.21). • Ноте • Воой ВОО1$ 5ИЬоо15 Мопбо 26.5. ИЪйе. $698.62 [ Ю ЬчкмЗ 5гкн*Ьазп1 Ьоо!5 Мопдо 27.5. В1ие $825.59 I Л&Ло Ь*»к«~| Мошйа1пмппд ЬооТз Мопдо 273. Вгожп. $634.98 МйьЬмМ' Вавке: $поиЬоап1 Ьоо(з1 х$82559 РС6310088 ЗИЬоо» 2х$698.62 ВЕ8260004 I -- I Рис. 3.21. Как наилучшим образом вычислить общую стоимость товаров в корзине и налог на них?
Предположим, что нам нужно вычислить общую стоимость товаров в корзине, а затем рассчитать налог с продаж на эту сумму. Данную задачу можно было бы решить при помощи функции ЛауаБспр!, обрабатывающей все товары в корзине и вычисляющей требуемую информацию. Но такая функция должна будет повторно выполнять все вычисления при каждой отрисовке корзины. Есть ли какой-либо способ вычислить производные значения по состоянию и обновлять вычисления только при изменении состояния? РЕШЕНИЕ Данную задачу можно решить при помощи библиотеки Кезс1ес1, специально соз- данной разработчиками Кедих для эффективного вычисления значений по объектам состояний. Библиотека Ке$с1ес1 создает селекторные функции. Селекторная функция (или про- сто селектор) принимает объект состояния в качестве параметра и возвращает обработанную версию этого объекта. В разделе 3. б мы уже один раз встречались с селектором, при возврате текущего состояния корзины из центрального состояния Кедих: сопзё ЬазкеЁ = изеЗе1есЁог((зЁаЁе) => зЁаЁе.ЬазкеЁ) Здесь селекторная функция зЁаЁе =>зРаЁе.ЬазкеЁ извлекает некое значение из объек- та состояния. Библиотека В.езе1ес1: создает высокоэффективные селекторные функ- ции, которые могут кешировать свои результаты при отсутствии изменений в со- стоянии, от которого они зависят. Первым делом установим эту библиотеку: $ прт 1пзЁа11 гезе1есЁ Теперь мы можем создать селекторную функцию, которая будет выполнять сле- дующие операции: ♦ подсчитывать общее количество товаров в корзине; ♦ вычислять общую стоимость всех товаров. Назовем эту функцию зиштагххег. Прежде чем рассматривать подробности написа- ния этой функции, создадим тест, который покажет, что она должна делать (лис- тинг 3.25). Листинг 3.25. Тест для демонстрации требований к функции зиштагхгег ' зЬои1с1 Ье аЫе ёо кап<31е ти1Ё1р!е ргодисЁз', () => { сопзё асЁиа! = зиттагггегЦ ЬазкеЁ: [ { ргодисМд: '1234', диапЁ1Ёу: 2, рггсе: 1.23 ), { ргобисЁТс!: '5678', диапЁ1Ёу: 1, ргхсе: 1.5 ), ], }) ехресЁ(асЁиа!).ЁоЕдиа!({ ЁЁетСоипЁ: 3, созё: 3.96 }) })
Таким образом, при передаче этой функции объекта состояния она суммирует ко- личество и стоимость товаров и возвращает объект, содержащий соответствующие переменные гьетСоипь и созё. Посредством библиотеки Кезе1ес1: селекторную функцию зитшагггег можно создать, как показано в листинге 3.26. Листинг 3.26. Селекторная функция ятпптпаг! УАГ гтпрогЁ { сгеаЁе8е!есЁог } Ёгот 'гезеЬесЁ' сопзё зшпгоагггег = сгеаЁе8е!есЁог ( (зЁаЁе) => ЗЁаЁе.ЬазкеЁ || [], (ЬазкеЁ) => ({ гЁешСоипЁ: ЬазкеЁ.гедисе((г, ]) => 1 + ].диапЁгЁу, 0), созё: ЬазкеЁ.гедисе((г, ]) => г + д.диапЁгЁу * д.рггсе, 0), }) ) ехрогЁ беГаиЁЁ зиттагггег Селекторная функция создается с помощью функции сгеаЁе8е1есЁог на основе дру- гих селекторных функций. Все параметры, передаваемые этой функции, за исклю- чением последнего, должны быть селекторными функциями. В данном случае мы передаем только один параметр: (зЁаЁе) => ЗЁаЁе.ЬазкеЁ || [] Этот код извлекает компонент корзины из объекта состояния. Последний параметр, передаваемый функции сгеаЁе8е1есЁог, называется объедини- телем (сотЫпег). Это функция, которая вычисляет новое значение на основании результатов предшествующих селекторов (листинг 3.27). Листинг 3.27. Код функции-объединителя (ЬазкеЁ) => ({ ЁЁешСоипЁ: ЬазкеЁ.гедисе((г, д) => г + д.диапЁгЁу, 0), созё: ЬазкеЁ.геЬисе((г, ]) => 1 + д.диапЁгЁу * д.рггсе, 0), )) Значение ЬазкеЁ получается в результате обработки состояния первым селектором. Но чем объяснить такой необычный способ создания функций? Разве это ненамно- го сложнее, чем просто создать функцию .1ауа$спр1 вручную, не передавая все эти функции другим функциям? Ответить на эти вопросы можно одним словом — эффективность. Селекторы пере- вычисляют свои значения только тогда, когда это требуется. Объекты состояния могут иметь сложную структуру и десятки атрибутов. Но нас интересует только содержимое атрибута ЬазкеЁ, и мы не хотим перевычислять стоимость при измене- нии значения каких-либо других атрибутов.
Функция зыптагхгег решает, когда возвращаемое ею значение, скорее всего, было изменено. Предположим, что в первом вызове она вычисляет значения ббешСоипб и уа1ие следующим образом: {ббетСоипб: 3, созб: 3.96} Затем пользователь исполняет некоторое число команд, например обновляет на- стройки своих предпочтений, отправляет кому-то сообщение, добавляет несколько товаров в свой список покупок и т. п. Каждое из этих действий может вызвать обновление глобального состояния при- ложения. Но при следующем вызове функции зышпагбхег она возвратит из кеша ранее созданное значение: {ббетСоипб: 3, созб: 3.96} Почему? Потому что она знает, что это значение зависит только от значения Ьазкеб в глобальном состоянии. Если данное значение не изменилось, то нет надобности перевычислять возвращаемое значение. Поскольку библиотека Ке8е1ес1 позволяет создавать селекторные функции из дру- гих селекторных функций, можно создать еще одну селекторную функцию, бахег, для вычисления налога с продаж (листинг 3.28). Листинг 3.28. Функция бахег для вычисления налога с продаж бтрогб { сгеабеЗебесбог } бгот 'гезебесб' бтрогб зиттагбхег бгот ' . /зиттагбгег' сопзб бахег = сгеабеЗебесбог( зиттагбгег, (зиттагу) => зиттагу.созб * 0.07 ) ехрогб бебаибб бахег Селектор бахег обрабатывает значение созб, возвращаемое функцией зиттагбгег, умножая его на 0,07, т. е. вычисляет 7% от общей стоимости товаров в корзине. Если общая стоимость товаров в корзине не изменилась, то нет надобности обнов- лять результат фуНКЦИИ бахег. Функции зиттагбгег и бахег можно использовать внутри компонентов, точно так же, как и любую другую селекторную функцию (листинг 3.29). Листинг 3.29. Использование функций зипппагбхег и бахег в компоненте бтрогб { изеБбзрабск, изеЗебесбог } бгот 'геасб-гедих' бтрогб './Вазкеб.сзз' бтрогб зиттагбгег бгот './зиттагбгег' бтрогб бахег бгот './бахег' сопзб Вазкеб = () => { сопзб Ьазкеб = изеЗебесбог((збабе) => збабе.Ьазкеб)
сопзё { 1ЁетСоипЁ, созё } = изе5е!есЁог(зиштагЁгег) сопзё Ёах = изеЗеХесЁог(Ёахег) сопзё сИзраЁск = изеБхзраЁсЬ() геЁигп ( <сИу с1аззНате="ВазкеЁ"> <Ь2>ВазкеЁ</И2> {ЬазкеЁ && ЬазкеЁ.1епдЁЬ ? ( {ЬазкеЁ.тар((1Ёет) => ( <с1гу с1аззЫате="ВазкеЁ-1Ёет"> <сИу с1аззЫате="ВазкеЁ-1ЁетМате">{1Ёет. пате}</<±И7> <61У с1аззЫате="ВазкеЁ-1ЁетРгос1исЁ1с1"> {ЁЁет.ргобисЁТб} </сИу> <сИу с1аззПате="ВазкеЁ-1ЁетРг1с1пд"> <Ыу с1аззЫате="ВазкеЁ-1Ёет0иапЁ1Ёу"> {1Ёет.диапЁ1Ёу} </Ыу> <сНу с1аз5Нате="ВазкеЁ-1ЁетРг1се">{1Ёет.рг1се}</с1гу> </с11у> </б1У> ))) <р>{1ЁетСоипЁ} 1Ёетз</р> <р>ТоЁа1: ${созЁ.ЁоГ1хес1 (2) }</р> <р>5а!ез Ёах: ${Ёах.ЁоПхес1{2) }</р> <ЬиЁЁоп опС11ск={() => сИзраЁсЬ({ Ёуре: 'сРеагВазкеЁ' })}> С1еаг </ЬиЁЁоп> ) : ( 'ЕтрЁу1 )} </с!гу> } ехрогЁ с!еЁаи1Ё ВазкеЁ При исполнении кода теперь внизу корзины покупателя отображается общая стои- мость находящихся в ней товаров, которая будет обновляться при добавлении в корзину новых покупок (рис. 3.22). Обсуждение На первый взгляд селекторные функции могут казаться сложными и трудными для понимания. Но, уделив некоторое время, чтобы разобраться с ними, можно понять, что ничего сложного в них нет. В них нет ничего, присущего только Кес1их, и их
• вЖ ВОО1$ 5к| Ьоо1б Могкк) 26.6. №Ы№. $698.62 БпоиЬоап! Ьоо1б Мопйо 27.6. В1ие. $825.59 |ЛсИ1о Ьчк»(~[ Моигйвкпееппд Ьоо(з Мопдо 27.3. Вгоюп. $634.98 | АхМЬЬажж | Вавке! ЗшжЪоагй Ьоо&1 х$825 59 рсвз ооев ЗИ Ьоо»з 2 х$69862 №8290004 3№гл$ То1а1: $2222.83 5а1ез 1ах: $155.60 2^ I Рис. 3.22. Селекторные функции пересчитывают общую стоимость товаров в корзине и сумму налога только при изменении количества товаров в корзине вполне можно использовать совместно с функциями-преобразователями, не отно- сящимися к Кедих. Поскольку у них нет зависимостей вне пределов самой библио- теки К.е§е1ес1, они легко поддаются модульному тестированию. Примеры тестов включены в код этой главы. Исходный код данного рецепта можно загрузить на веб-сайте бкНиЬ по адресу 1Шр8://огеП.1у/1178Ьг.
Проектирование для обеспечения интерактивности Рассматриваемые в этой главе рецепты фокусируются на решении целого ряда за- дач проектирования пользовательского интерфейса. В частности, обработка оши- бок, предоставление помощи пользователям в применении разрабатываемой вами системы, создание сложных входных последовательностей, избегая при этом напи- сания спагетти-кода и т. п. Далее приведено множество советов, которые мы неоднократно находили полез- ными. В конце главы исследуются разные способы добавления анимации в прило- жения. Всюду, где это возможно, мы предпринимаем низкотехнологичный подход, и рассматриваемые здесь рецепты в идеале повысят эффективность ваших разрабо- ток интерфейса, требуя для этого минимальных усилий. 4.1. Создание центрального обработчика ошибок ЗАДАЧА Дать точное определение, что именно делает программное обеспечение хоро- шим, — задача не из легких. Но большинство превосходных программ имеют один общий аспект: их подход к реагированию на ошибки и исключения. При исполне- нии ваших приложений всегда будут возникать исключительные, неожиданные си- туации, например, потеря подключения к сети, сбой на сервере, искажение данных в хранилище и т. п. Поэтому важно рассмотреть, каким образом действовать при возникновении таких ситуаций. Один из вариантов, который почти наверняка будет провальным, — это игнориро- вать факт возникновения сбойных ситуаций и прятать вызывающие их непривлека- тельные подробности. Но правильным подходом будет каким-либо образом орга- низовать сохранение где-то подробностей обстоятельств, вызвавших сбой, которые затем можно будет использовать для предотвращения возникновения этой ошибки в будущем. При разработке серверного кода подробности ошибки можно записывать в журнал и по запросу возвращать соответствующее сообщение. Но при написании клиент- ского кода требуется план, как действовать в случае возникновения локальных ошибок. Можно, например, отображать подробности ошибки пользователю и пред- ложить отправить отчет об ошибке. Или можно задействовать стороннюю службу,
например Зепйу.ю (ЬПр8://8епСгуло), чтобы сохранять подробности ошибки уда- ленно. Но какой бы подход ни был избран, он должен быть единообразен. Как же добиться единообразной обработки ошибок в приложениях Кеас1? РЕШЕНИЕ В этом рецепте мы рассмотрим, как создать центральный обработчик ошибок. Сра- зу же уточним, этот код не будет автоматически улавливать все исключения. Его еще нужно явно добавить в блоки перехвата ошибок (са1сН Ыоск) Зауа8спр1. Это также не заменитель для обработки ошибок, после которых можно восстановить исполнение приложения каким-либо иным образом. Например, если при обработке заказа возникает ошибка по причине снятия сервера с линии для регламентных ра- бот, будет намного лучше предложить пользователю повторить попытку позже. Но этот подход поможет выявить любые ошибки, которые не были предусмотрены в нашем плане обработки ошибок. Как правило, когда что-то идет не так, пользователю нужно сообщить следующие три вещи: ♦ Что случилось? ♦ Почему это случилось? ♦ Как реагировать на произошедшее? В приводимом здесь примере мы будем обрабатывать ошибки, отображая диалого- вое окно, содержащее подробности объекта Еггог ЗауаЗспр! и предлагающее поль- зователю отправить электронной почтой его содержимое команде поддержки сис- темы. При возникновении ошибки мы хотим вызывать простую функцию для ее обработки: зеЁУ1з!ЫеЕггог ('Этого делать нельзя', еггогОЬ^есХ) Обычно обеспечить доступ к этой функции глобально по всему приложению мож- но посредством контекста. Контекст представляет собой нечто наподобие области видимости, в которую можно заключить набор компонентов В.еас1. Все, помещен- ное в этот контекст, становится доступным для всех дочерних элементов. В таком контексте мы сохраним нашу функцию обработки ошибок, которую сможем ис- полнять всякий раз, когда возникает ошибка. Назовем наш контекст ЕггогНапЩегСопсехс (листинг 4.1). Листинг 4.1. Контекст для хранения функции обработки ошибок гтрогХ КеасЁ Ёгот ' геасЁ' сопзё ЕггогНапсЛегСопЁехЁ = КеасЁ.сгеаЁеСопЁехЁ(() => {}) ехрогЁ беЁаиХЁ ЕггогНапс1ХегСопЁехЁ Чтобы сделать контекст доступным набору компонентов, создадим компонент ЕггогНапЩегРгоухдег, который, в свою очередь, создаст экземпляр контекста и сдела- ет его доступным для всех передаваемых ему дочерних компонентов (листинг 4.2).
• Листинг 4.2. Компонент ЕггогНапс11егРгоу±с1ег ХтрогЕ ЕггогНапсЧегСопЬехГ Нот '. /ЕггогНапсЧегСопЬехЬ1 1еЬ зеЬЕггог = () => {} сопзТ: ЕггогНапд1егРгоу1с1ег = (ргорз) => { 1Г (ргорз.саНЬаск) { зеЁЕггог = ргорз. саНЬаск } геЁигп ( <ЕггогНапс11егСопСехЁ. РгоуЫег уа1ие= {зеСЕггог} > (ргорз. сЬНЬгеп) </ЕггогНапс11егСопЬехЁ. Ргоу1с1ег> ) } ехрогЁ ЬеГаиИ ЕггогНапс11егРгоу1с1ег Теперь нам нужно написать немного кода с инструкциями, что делать при вызове функции обработки ошибок. В данном случае такой код должен реагировать на сообщение об ошибке, отображая диалоговое окно, содержащее все подробности об ошибке. Соответствующий код приведен в листинге 4.3. Если вы хотите обраба- тывать ошибки по-другому, модифицируйте этот код должным образом. Листинг 4.3. Код, исполняющийся при сообщении об ошибке йпрогЬ { изеСаИЬаск, изеЗЬаЬе } Нот 'геасГ' йпроН ЕггогНапсПегРгоухдег Егот '. /ЕггогНапсЛегРгоуЬйег1 ЕтрогЕ ЕггогЬхаХод Егот './ЕггогЕ1а1од' сопзЕ ЕггогСопЬахпег = (ргорз) => { сопзЕ [еггог, зеЕЕггог] = изеЗЕаЕе() сопзЕ [еггогПЫе, зеЕЕггогТ1Е1е] = изеЗЕаЕе() сопзЕ [асЫоп, зеЕАсЕЕоп] = изеЗЕаЕе() И (еггог) ( сопзоЬе.еггог( 'Ап еггог Ьаз Ьееп ЕИгомп', еггогТ1Е1е, ЛЗОЫ.зЕгЕпдИу (еггог) ) } сопзЕ саНЬаск = изеСаИЬаск ((Е1Е1е, егг, асЫоп) => { сопзо1е.еггог('ЕККОК КА18ЕБ ') сопзоЬе.еггог('Еггог Е1Е1е: ', Ш1е) сопзо1е.еггог('Еггог сопЕепЕ', ЗЫЖ.зЕНпдИу (егг) ) зеЕЕггог(егг) зеЕЕггогТ1Е1е (1Ше) зеЕАсЕгоп (асЕ1оп) ), [])
геЁигп ( <ЕггогНапсИегРгоУ1дег са11Ьаск= {саИЬаск}> {ргорз. сЪПдгеп} {еггог && ( <ЕггогВ1а1од Ё1Ё1е= {еггогТШе} опС1озе={() => { зеЁЕггог(пи11) зеЁЕггогТ1Ё1е{'1) }} асЁ1оп={асЁ1оп} еггог={еггог} /> )} </ЕггогНапд1егРгоУ1с1ег> ) } ехрогЁ йе!аи1Ё ЕггогСопЁаЁпег Компонент ЕггогСопЁгагпег отображает подробности ошибки посредством компо- нента ЕггогОхаХод. Здесь мы не будем вдаваться в подробности кода компонента ЕггогШаХод, поскольку этот фрагмент нужно написать самостоятельно при реализа- ции своей версии кода обработки ошибок1. В компонент ЕггогСопЁахпег необходимо поместить большую часть нашего прило- жения. Вызывать обработчик ошибок смогут все компоненты внутри компонента ЕггогСопЁаЁпег (ЛИСТИНГ 4.4). Листинг 4.4. Помещение приложения в компонент ЕггогСопЪахпег йпрогЁ './Арр.сзз' ЙпрогЁ ЕггогСопЁатпег Ёгот './ЕггогСопЁагпег' ЙпрогЁ С1оск1п Ёгот './С1оск1п' ГипсЁЁоп Арр() { геЁигп ( <сИ\7 с1аззМате="Арр"> <ЕггогСопЁа1пег> <С1оск1п /> </ЕггогСопЁа1пег> </с1гу> } ехрогЁ деЁаи1Ё Арр 1 Весь исходный код для данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу к«р$:// огеН.1уЛуИМ7О.
Компонент использует обработчик ошибок посредством хука изеЕггогНапсЦег () (листинг 4.5), который извлекает функцию обработки ошибок из контекста и воз- вращает ее. Листинг 4.5. Создание мука ивеЕххогНапаРлг шрогр ЕггогНапсИегСопРехР Ггот 1. /ЕггогНапсИегСопРехР' 1гарогР { изеСопРехР } Ргош 'геасР' сопзР изеЕггогНапсЦег = О => изеСопРехР(ЕггогНапсИегСопРехр) ехрогр беРаиХР изеЕггогНапсЦег Это был довольно сложный код, но теперь мы подошли к собственно использова- нию обработчика ошибок. И здесь уже нет ничего сложного. Например, код, приве- денный в листинге 4.6, отправляет сетевой запрос, когда пользователь нажимает кнопку. Если исполнение запроса не завершается успешно, подробности возникшей ошибки передаются обработчику ошибок. Листинг4.6.Пример использованияобработчика ошибоквкоде > т, шрогР изеЕггогНапсЦег Ргош './изеЕггогНапсЦег' йпрогР ах1оз Ргот 'ахгоз' сопзР С1оск1п =()=>{ сопзР зеР\Цз!ЫеЕггог = изеЕггогНапсЦег () сопзР с!оС1оск1п = азупс () => { Ргу { амахР ахРоз.риР('/с1оскТ1ше') } саРск (егг) { зеРУРзхЫеЕггог ('ЦпаЫе Ро гесогб могк зРагР Р1те', егг) ) } геРигп ( <Ы>С11ск ВиРРоп Ро Кесогс! ЗРагР Т1те</Ь1> <ЬиРРоп опС11ск={с1оС1оск1п}>5РагР могк</ЬиРРоп> ) ) ехрогР 6еРаи1Р С1оск1п На рис. 4.1 показано окно приложения. Нажатие кнопки вызывает неуспешное завершение исполнения сетевого запроса из-за отсутствия серверного кода. Срабатывает обработчик ошибок и выводится диалоговое окно с описанием ошибки (рис. 4.2). Обратите внимание, что данное окно содержит информацию о том, какая произошла ошибка, почему она произош- ла и что пользователю следует предпринять относительно нее.
СПск ВиПоп (о Несогй 81аИ Т1те | 5(аН«огк | Рис. 4.1. Приложение записи времени начала работы ИпаЫе 1о гесогй ууогк з(аг1 Игле 5оте1К!пд Ьай Каррелей. ТКе йе^аВз 1Ке еггог аге Ье1ои. Р1еа$е сору (Кет апб зепй {Кет 1о зу&етз зиррогГ "теззаде": "РедиезЬ ГаЫей зЪасиз соде 404", "пате": "Еггог", "вЬаск": "Еггог: ЯедиевЪ 1а11ед ихеЬ зЬаГие соде 404\п ас сгеаЬеЕггох (НСЕр://1оса1ЪояС:ЗООО/яСаСхс/Зя^епдогя-гаахп.сНилк:10902:15)\п ас зесс1е (НССр://1оса1ЬоясгЗООО/зсаЕХс^зЛгапдогя-тахп.сНипк.3»; 11136:12) \п а с ХМЬНсСрНециезС.НалдХеЬоад (ЪССр: /ЛосаХНозс: 3000/зсасХс/Эз^епдогз-таХп. сНипк.1»:10376:7) \п аС ХМЬНССрКедиезС,еуа! (еVа1 аС такеСолСепСШпдснЬхзСепег (ЪсСр://1оса1ЬовС:3000/__сургевв/гиппег/сургевв_гиппег.Эе:154555:10) , <апопутоиз>: 4 :29) ", "сопЛд": 1 "иг!": "/с1оскТ1те", ”п сНод": "рис", "Ьеадегв": ( "АссерС": "аррХхсас1оп/)1оп, СекС/р1а1п, */*" ), "СгапзГоттРециезс": ( ли11 _____к.___________________________.___________________ _______________4 СОРУ ЕННОК Рис. 4.2. Когда сетевой запрос вызывает исключение, оно передается обработчику ошибок Обсуждение Изо всех рецептов, созданных нами в течение многих лет, описанный здесь сэко- номил нам больше всего времени. В процессе разработки в исполнении кода часто возникают сбои, и если единственным указателем на причину ошибки является трассировка стека, упрятанная глубоко в консоли 5ауа8спр1, то его очень легко и не заметить. Особенно важно, что в случае сбоя какой-либо части инфраструктуры (сети, шлю- за, сервера, базы данных), этот небольшой фрагмент кода может сэкономить вам огромное количество часов, которые потребовалось бы потратить на поиски при- чины ошибки. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу ЬНрз ://огеП.1уЛу11М7().
4.2. Создаем интерактивное справочное руководство ЗАДАЧА Тим Бернерс-Ли (Т1т Вегпегз-Ьее) преднамеренно оснастил Всемирную паутину очень небольшим количеством возможностей. Она работает по простому протоколу (НТТР), и первоначально в ней использовался простой язык разметки (НТМЬ). Та- кое отсутствие сложности означало, что новые пользователи веб-сайтов сразу же понимали, как работать с ними. Например, если что-то выглядело как гиперссылка, было ясно, что щелчок по ней переведет тебя на другую страницу. Но все это изменилось с приходом насыщенных приложений ДауаБспрТ Веб- приложения больше не являются набором веб-страниц, связанных между собой ги- перссылками. Теперь они больше похожи на старые компьютерные приложения, более мощные и с большим количеством возможностей. Но за это приходится рас- плачиваться намного большей сложностью работы с ними, зачастую требуя специ- ального руководства, объясняющего, что и как. Итак, как можно добавить интерактивное руководство в наше приложение? РЕШЕНИЕ Мы создадим простую справочную систему, которую можно будет наложить по- верх существующего приложения. При открытии страницы справки отображается последовательность всплывающих окон, описывающих, как работать с приложени- ем (рис. 4.3). Рис. 4.3. При нажатии кнопки 5Ьом/ Ке1р выводится первое из последовательности всплывающих окон с инструкциями по работе с приложением
Мы хотим, чтобы нашу справочную систему было легко обслуживать и чтобы она предоставляла информацию только по видимым компонентам страницы. Поскольку это довольно объемная задача, начнем с создания компонента, который выводит всплывающее окно со справочной информацией (листинг 4.7). Листинг 4.7. Компонент для отображения всплывающего окна с подсказками ЁтрогЁ { Роррег } Ёгот '@таЁег1а1-и1/соге' ЁтрогЁ 1 . /НеЁрВиЬЫе. сзз' сопзё Не1рВиЬЫе = (ргорз) => { сопзё еРетепЁ = ргорз.ГогЕРетепЁ ? йоситепЁ.диегу5е1есЁог(ргорз.ЕогЕРетепЁ) : пиИ геЁигп еРетепЁ ? ( <Роррег с1аззМате="Не1рВиЬЫе-сопЁа1пег" ореп={ргорз.орел} апсЪогЕ1={еРетепЁ} р1асетепЁ={ргорз.рХасетепЁ II 'ЬоЁЁот-зЁагЁ'} <с1гу с1аззМате="Не1рВиЬЫе-с1озе" опСИск= (ргорз.опС1озе}> С1озе [X] </с!гу> {ргорз.сопЁепЁ} <с!з^ с1аз5Ыате=,,Не1рВиЬЫе-сопЁго1з"> {ргорз.ргеуЁоизЪаЬе! ? ( <сИу с1аззМате="Не1рВиЬЫе-сопЁго1 Не1рВиЬЫе-р^еV^оиз" опСИск= (ргорз. опРгеуЁоиз} &1Ё; {ргорз.ргеуЁоизЪаЬе!) </с!гу> ) : ( <с!^>&пЬзр;</с1гу> )} {ргорз.пехЁЬаЬе! ? ( <сИу с1аззЫате=,,Не1рВиЬЫе-сопЁго1 Не1рВиЬЫе-пехЁ" опСИск= {ргорз. опИехЁ} > (ргорз.пехЁЬаЬе!} &дЁ; </сИу> ) : ( <с1гу>&пЬзр; </с)1у> )}
</с11у> </Роррег> ) : пи11 } ехрогЕ деЕаиХЕ Не1рВиЬЫе В листинге 4.7 используется компонент Роррег из библиотеки @гпа1епа1-ш. Этот компонент можно закрепить на странице рядом с каким-либо другим компонентом. Компонент не1рВиЬЫе принимает строку ЕогЕХешепЕ, которая будет представлять ка- кой-либо селектор С88, например .с1азз-пате или #зоте-1<1. Селекторы будут слу- жить для ассоциирования элементов на экране со всплывающими сообщениями. Теперь, когда у нас есть компонент для всплывающих сообщений, нам нужен ком- понент для координации отображения таких всплывающих сообщений. Код такого компонента, называющегося неХрЗедиепсе, приведен в листинге 4.8. Листинг 4.8. Кед компонента Не1р5едиепсе шрогЕ { изеЕЕЕесЕ, изеЗЕаЕе } Егот 'геасЕ' шрогЕ НеХрВиЬЫе Егот './НеХрВиЬЫе' ЕипсЕЕоп 1з7131Ые(е) { геЕигп !!( е.оЕЕзеЕИЕдЕк || е.оЕЕзеЕНехдкЕ || е. деЕСИепЕВесЕз () . ХепдЕЪ ) } сопзЕ НеХрЗедиепсе = (ргорз) => { сопзЕ [роз1Е1оп, зеЕРоз1Е1оп] = изеЗЕаЕе(0) сопзЕ [зедиепсе, зеЕЗедиепсе] = изеЗЕаЕе() изеЕЕЕесЕ(() => { 1Е (ргорз.зедиепсе) { сопзЕ ЕИЕег = ргорз.зедиепсе.ЕИЕег ( (1) => { 1Е (!1.ЕогЕ1етепЕ) { геЕигп Еа1зе ) сопзЕ еХетепЕ = доситепЕ.диегуЗеХесЕог(Х.ЕогЕХетепЕ) 1Е (!еХетепЕ) { геЕигп Еа1зе } геЕигп ХзУХзХЬХе(еХетепЕ) )) зеЕЗедиепсе (ЕИЕег) } еХзе { зеЕЗедиепсе (пиИ) ) }, [ргорз.зедиепсе, ргорз.ореп])
сопзё с!аЁа = зедиепсе && зедиепсе [розШоп] изеЕХХесЁ (() => { зеЁРозХЁХоп (0) }, [ргорз.ореп]) сопзё опЫехЁ = () => ЗеЁР051Ё10П((р) => { XX (р -== зедиепсе.ХепдЁЪ - 1) { ргорз.опС1озе && ргорз.опСХозе() } геХигп р + 1 }) сопзё опРгеуЁоиз = О => зеЁРозЁЁЁоп((р) -> { 1Ё (р === 0) { ргорз.опСХозе && ргорз.опСХозе() } геЁигп р - X }) геЁигп ( <<±И7 сХаззНаше="НеХрЗедиепсе-сопЁаХпег"> {баЁа && ( <НеХрВиЬЬХе ореп={ргорз.ореп} ХогЕХетепЁ={с1аЁа. ХогЕХетепЁ} рХасетепЁ= {<ХаЁа. рХасетепЁ} опСХозе={ргорз.опСХозе} ргеуХоизЬаЬеХ={роз1ЁХоп > 0 && 'РгеуХоиз'} пехЁЬаЬеХ={ розХЁХоп < зедиепсе.ХепдЁН - X ? 'МехЁ' : ’ЕХпХзЬ' } опРгеуХоиз={опРгеуХоиз} опМехЁ={опЫехЁ} сопЁепЁ= {(ХаЁа. ЁехЁ} /> )} </<±И7> ) } ехрогЁ (ХеХаиХЁ НеХрЗедиепсе Компонент НеХрЗедиепсе принимает массив объектов .1а\а8спр1, пример которого показан в листинге 4.9. Листинг 4.9. Пример объектов Зауа5спр1, принимаемых компонентом НеХрЗедиепсе [ {ХогЕХешепЁ: "р", ЁехЁ: "ТЫз Хз зоше ХпЁгодисЁогу ЁехЁ ЁеХХХпд уои Ном ёо зЁагЁ”},
{когЕкетепк: ".Арр-кхпк", кехк: "ТЫз иШ зкюи уои кон ко изе Кеаск"}, {когЕкетепк: ".Арр-понкеге", кехк: "Ткхз Ъекр кехк нхкк пеуег арреаг"}, ] В компоненте НекрЗедиепсе эти объекты преобразуются в динамическую последова- тельность компонентов некрВиЬЬкез. Компоненты НекрВиЬЬкез отображаются только при обнаружении элемента, соответствующего селектору когЕкетепк. В таком случае возле этого элемента отображается всплывающее окно некрВиЬЬке, содержащее текст соответствующей справки. Добавим теперь компонент НекрЗедиепсе в стандартный код файла Арр.) 8, созданный Средством сгеаке-геаск-арр (ЛИСТИНГ 4.10). Листинг 4.10. Вставка компонента НеХрВедиепсе в стандартный код Арр.|а хтрогк { изеЗкаке } кгот 'геаск' хтрогк 1одо кгот ,./1одо.зVд, хтрогк НекрЗедиепсе кгот './Не1р8едиепсе1 хтрогк './Арр.сзз' кипскхоп Арр() { сопзк [зкоиНекр, зекЗкюиНекр] = изеЗкаке(какзе) гекигп ( <6ху с1аззМате="Арр"> <кеас1ег сказзМате="Арр-кеабег"> <хтд згс={кодо} с1аззЫате="Арр-1одо" а!к="1одо" /> <Р> ЕсИк <сос1е>5гс/Арр.дз</со<1е> апс! заVе ко гекоай. </р> <а сказзЫате="Арр-кхпк" кгек="кккрз: //геаскд з. огд" кагдек="_Ькапк" гек="поорепег погекеггег" Ьеагп Веаск </а> </Ъеас1ег> <Ьиккоп опСкхск={() => зекЗкюиНекр(кгие)}>3кои 1лекр</Ьиккоп> <НекрЗедиепсе зедиепсе={[ { когЕкетепк: 1р', кехк: 'Ткхз хз зоте хпкгодискогу кехк кеккхпд уои кюн ко зкагк1, }, { когЕкетепк: '.Арр-кхпк', кехк: "ГИхз ихкк зкюи уои кои ко изе Веаск', },
^огЕТетеп!:: ' . Арр-поиИеге', Ьех!:: 'ТК1з Ье1р <;ех<: иШ пеуег арреаг', ореп={зИонНеТр} опС1озе={() => зеЁЗУюмНеТр(^а1зе)} </сПу> ехрогГ <1еГаи11: Арр Сразу после запуска внешний вид модифицированного приложения практически ничем не отличается от обычного стандартного приложения, но добавлена кнопка Ье1р (рис. 4.4). Рис. 4.4. Внешний вид приложения сразу после загрузки При нажатии этой кнопки отображается диалоговое окно со справочной информа- цией для первого элемента, как показано на рис. 4.5. А при нажатии в этом окне ссылки 1\ех( открывается справочное диалоговое окно для следующего элемента, как показано на рис. 4.6. Нажатие ссылки №ех! в каждом справочном диалоговом окне будет последовательно отображать справочное окно для следующего элемента, пока не останется совпадающих элементов.
Рис. 4.5. При нажатии кнопки Ье1р открывается справочное диалоговое окно для первого элемента Рис. 4.6. В последнем справочном окне вместо ссылки Ыех1 используется ссылка НтвЬ
Обсуждение Присутствие в приложении интерактивной справки делает возможным для пользо- вателя узнавать о наличии, назначении и способе использования элементов его пользовательского интерфейса. Разработчики уделяют много времени, добавляя в приложения возможности, которые, вероятно, никогда не будут задействованы просто по той причине, что пользователи не будут знать о них. В нашей реализации справки информация предоставляется в виде простого текста. Но может быть полезным рассмотреть использование для этой цели языка разметки МагЫо\уп, чтобы обеспечить более насыщенную справочную среду, а также иметь возможность вставлять ссылки на другие, более обширные справочные страницы2. Справочные темы автоматически ограничиваются элементами, видимыми на стра- нице. Можно создать или отдельную справочную последовательность для каждой страницы, или одну большую справочную последовательность, которая будет автоматически адаптироваться к текущему представлению пользовательского ин- терфейса. Наконец, справочная система наподобие рассмотренной здесь идеально подходит для хранения в "безголовых" системах СМ8, что позволит обновлять справку дина- мически, без необходимости создавать новое развертывание при каждом обновле- нии. Исходный код данного рецепта можно загрузить на веб-сайте СшНиЬ по адресу ЬНр8://огеП.1у/С51М1Ч. 4.3. Сложные взаимодействия посредством преобразователей ЗАДАЧА Часто для получения желаемого результата работы приложения пользователям нужно выполнить определенную последовательность действий. Например, проде- лать последовательность шагов в каком-либо мастере или осуществить вход в сис- тему и подтвердить намерение выполнить какую-либо опасную операцию (рис. 4.7). Кроме того, пользователю может потребоваться не только проделать последова- тельность шагов, но и выполнение самих шагов может быть обусловленным. На- пример, если пользователь недавно вошел в систему, то повторный вход может не требоваться. Или же пользователь может решить отменить операцию посередине последовательности шагов. При моделировании сложных последовательностей в компонентах можно вскоре обнаружить, что приложение битком набито спагетти-кодом. 2 Дополнительная информация по использованию редактора Магкйоууп в приложениях приведена в раз- деле 4.5.
Рис. 4.7. Процесс удаления виджета требует входа в систему по паролю и последующего подтверждения удаления РЕШЕНИЕ Для управления сложной последовательностью операций мы рекомендуем прибег- нуть к преобразователю. Мы познакомились с использованием преобразователей для управления состоянием в главе 3. Преобразователь — это функция 1ауа8спр(, которая принимает в качестве параметров объект состояния и объект действия. На основе действия принимается решение, как изменить состояние, и оно не должно иметь побочных эффектов. Поскольку преобразователи не имеют кода пользовательского интерфейса, они идеально подходят для управления запутанными частями взаимосвязанного со- стояния, без необходимости заботиться о внешнем виде. Они особенно хорошо поддаются модульному тестированию.
Например, предположим, что мы реализуем последовательность удаления виджета, упомянутую в начале этого раздела. Начнем классическим образом с создания мо- дульного теста (листинг 4.11). Листинг 4.11. Код модульного теста приложения ЕтрогЕ де1еЕ1опВедисег Егот './с1е1еЕ1опВес1исег' йезсгЕЬе (1 деЕеЕЕопВебисег1, () ~> { И (' з1юи1с1 зЪоы 1одд.п сИа1од И ые аге поЕ 1оддес1 1п', () => { сопзЕ асЕиа! = Е1е1еЕ1опВес1исег ({}, { Еуре: ' ЗТАВТ_СЕЕЕТ1ОЫ' }) ехресЕ (асЕиа!.зкоиЬодхп) .ЕоВе (Егие) ехресЕ (асЕиа! .теззаде). ЕоВе ('') ехресЕ (асЕиа!. <1е1еЕеВиЕЕопО!заЫес1) . ЕоВе (Егие) ехресЕ(асЕиа!.1од!пЕггог).ЕоВе('') ехресЕ (асЕиа!.зИоиСопЕЕгтаЕЕоп).ЕоВе(Еа!зе) }) )) Здесь мы создали функцию преобразования, называющуюся с1е1еМопВес1исег. Пере- даем ей пустой объект ({}) и действие ({Еуре: 'зтакт веьеттоы' }), указывающее, что мы хотим начать процесс удаления. Затем указываем, что мы хотим, чтобы в но- вой версии состояния значение переменной зЬсиЛодхп было Егие, переменной зЕюиСопЕЕгтаЕЕоп — Еа!зе И Т. Д. Затем мы можем реализовать код преобразователя для выполнения этих операций (листинг 4.12). Листинг4.12. Подфункции преобразования ЕипсЕЕоп деЕеЕхопВейисег(зЕаЕе, асЕЕоп) { зиЕЕсИ (асЕЕоп.Еуре) { сазе 'ЗТАВТ_ОЕЬЕТ1ОК': геЕигп { ...зЕаЕе, зЪомЪодхп: Егие, теззаде: 11, с!е1еЕеВиЕЕопВ!5аЫес1: Егие, 1од!пЕггог: '', зЪоиСопЕЕгтаМоп: Еа!зе, } йеЕаиЕЕ: геЕигп пи!1 // Ог апуЕЫпд } ) Сначала мы просто присваиваем атрибутам состояния значения, которые успешно проходят тестирование. По мере добавления дополнительных тестов качество на-
шего преобразователя повышается, т. к. он может обрабатывать большее количест- во ситуаций. В конечном итоге наш преобразователь будет выглядеть так, как показано в лис- тинге 4.133 *. | Листинг 4.13. конечная версия функции преобразователя йтсМоп сЗеТеЁЁопВедисег (зЁаЁе, асЁ1оп) { зиёёсЪ (асЁтоп.Ёуре) { сазе '8ТАКТ_0ЕЬЕТ10Н': геЁигп { ...ЗЁаЁе, зЪоиЬодЁп: !ЗЁаЁе.ХоддесПп, теззаде: '', с1е1еЁеВиЁЁоп01заЫес1: Ёгие, 1од1пЕггог: зЪомСопПгтаЁгоп: ! !ЗЁаЁе. 1оддес11п, } сазе 'САМСЕЬ_ВЕЬЕТ1ОМ': геЁигп ( ...ЗЁаЁе, зЪоиЬодЁп: Ёа1зе, зЬоиСопЁЁгтаЁЁоп: Ёа1зе, зЬомНези1Ё: Га1зе, теззаде: 'Ве1еЁ1оп сапсеТеб', с!е1еЁеВиЁЁопС1заЫес1: Ёа1зе, } сазе ’ЬОСТЫ': сопзё раззиогйСоггесЁ = асЁ1оп.рау1оас! == 'змогсИгзк' геЁигп ( ...зЁаЁе, зкоиЬодЁп: !раззмогдСоггесЁ, зЪомСопПгтаЁхоп: раззмогйСоггесЁ, 1од1пЕггог: раззмогбСоггесЁ ? 11 : 11пуа116 раззмогсГ, 1оддесПп: Ёгие, } сазе 1 С0МЕ1ВМ_ВЕЬЕТЮМ': геЁигп { ...зЁаЁе, зкомСопИгтаЁЁоп: Га1зе, зЬомВезиЁЁ: Ёгие, теззаде: 'ИЁбдеЁ деХеЁей', } 3 Тесты для проверки работы этого кода можно загрузить из репозитория ОИНиЬ по адресу 1Шр$:// огеП.1у/1)СС1у.
сазе 'Е1Ы13Н': гееигп { ...зеаее, зкомЬодёп: еа!зе, зИоиСопИгтаЫоп: еа!зе, зкоиКезиХе: еа1зе, с!е1еееВиееопВ1заЫед: Га1зе, } деГаиЗЛ: екгои пен Еггог('Бпкпоип асе1оп: ’ + асе1оп.еуре) } } ехрогр деГаиХе де!ее1опВес1исег Несмотря на сложность приведенного кода, его можно написать довольно быстро, если начать с разработки тестов. Имея в своем распоряжении преобразователь, используем его в нашем приложении (листинг 4.14). Листинг 4.14. Применение функцммпр»ео6раз<эвиатезпяв приложении пороге { изеКедисег, изеЗеаее } егот ’геасЁ' пороге './Арр.сзз' пороге с!е1ее1опКес1исег егот './<1е1ее1опВес1исег' еипсеЗ-Оп Арр () { сопзе [зеаее, сИзраеск] = изеКедисег(с!е1ее1опВедисег, {}) сопзе [раззмогсЗ, зееРаззыогс!] = изеЗеаееО гееигп ( <с1гу с1аззЫате="Арр"> <Ьиееоп опСИск={() => { сИзраесЬ({ еуре: '8ТАВТ_ОЕЬЕТ1ОЫ' }) }} с11заЫес1= {зеаее. с1е1еееВиееопВ1заЫес1} Ве1еее И1ддее! </Ьиееоп> <6гу с1аззЫате="Арр-теззаде">{зеаее.теззаде}</с11у> {зеаее.зкоиЬодёп && ( <с^ с1аззНате=,’Арр-с11а1од',> <р>Епеег уоиг раззиогд</р> <1прие еуре="раззыогсГ уа1ие={раз змогд} опСЪапде={ (еуе) => зееРаззмогс1^е.еагдее^а1ие)} />
сЬиГЁоп опС11ск={() => сИзраРск({ 1:уре: 'Ь0С1К', рау!оас1: раззмогд }) } Ьод1п </Ьи1:Гоп> сЬи^Ьоп опСИск={ () => сИзраЁск({ Ёуре: 'САМСЕЬ_ОЕЬЕТ1СЖ' })} Сапсе! </Ьи!1Ёоп> <д1У с1аззЫате="Арр-еггог">{з^аЬе.1од1пЕггог}</с11у> </д1У> )) {зЬа!е. зИоиСопГ1ппа1;1оп && ( <сНу с1аззЫате=’’Арр-сИа1од"> <р>Аге уои зиге уои мап1: 110 де1е1:е 1±е м!с1де1;?</р> <Ьи<1Ёоп опСИск={() => сИзраЁск({ Ёуре: ' СОНПВМ_ОЕЪЕТЮЫ', }) } > Уез </Ьи111:оп> <Ьи!:Ьоп опСИск={() => сИзра^сИ({ Руре: ' САЫСЕЪ_ОЕЪЕТЮЫ', }) } > Ыо </Ьи111:оп> </д^> )} {зЁа!:е.зЪомКезиИ && ( <сИу с1аззКате="Арр~с11а1од"> <р>Тке «1дде^ ыаз <1е1е1:ес1</р> <ЬиЬ1:оп опСИск={() => сИзра^ск ({ 1:уре: 'Е1Ы15Н', }) } >
Боне </Ьи1Лоп> </сИу> )} </с!1У> ) } ехрог!; де^аиИ: Арр Большая часть кода этого компонента просто создает пользовательский интерфейс для каждого диалогового окна последовательности и не содержит практически никакой логики. Просто реализуется то, что указывает преобразователь. В итоге пользователь получит так называемый беспроблемный путь (Парру раШ) входа в систему и подтверждения удаления виджета (рис. 4.8). Рис. 4.8. Конечный результат исполнения приложения
Но, как можно видеть из рис. 4.9, программа также обрабатывает и случаи ухода с обычного пути (ед§е сазе), такие как ввод неправильного пароля и отмена опера- ции удаления. ОеМа Мйдв!1 ЕМебоп Сапсе1е<1 ЕпЕег уоиг разкшогс! Сапсе! Рис. 4.9. Обработка нештатных ситуаций преобразователем Обсуждение В некоторых случаях наличие преобразователей может сделать код запутанным. В ситуациях, когда нужно обрабатывать только малое число состояний с неболь- шим количеством взаимодействий между ними, преобразователь, скорее всего, не требуется. Но если для описания последовательности взаимодействий пользователя не обойтись без блок-схемы или диаграммы состояний, значит, может потребовать- ся прибегнуть к помощи преобразователя. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬПр8://оге11.1у/ЬГдЬ11. 4.4. Взаимодействие с клавиатурой ЗАДАЧА Опытные пользователи предпочитают исполнять часто встречающиеся операции при помощи клавиатуры. Компоненты Кеас1 могут реагировать на события клавиа- туры, но только тогда, когда они (или их потомки) имеют фокус. Каким образом можно реализовать реагирование компонента на события на уровне документа? РЕШЕНИЕ Для решения этой задачи мы создадим хук, который будет отслеживать события нажатия клавиши клавиатуры кеубомп на уровне документа. При этом его можно легко переделать для отслеживания любого другого события Лауа8спр1 в иерархии ПОМ. Код этого хука приведен в листинге 4.15.
Листинг 4.15. Хук для отслеживания событий клавиатуры кеуйоип I бтрогб { изеЕИесб } бгот 1геасб' сопзб изеКеуЬбзбепег = (саНЬаск) => { изеЕббесб(() => { сопзб Избепег = (е) => { е = е || нбпбом.еуепб сопзб бадЫате = е.багдеб.1оса1Ыате || е.багдеб.бадИате // Принимаем только события клавиш, возникающие на уровне элемента Ьобу, чтобы избежать включения нажатий клавиш, например, в текстовых полях 16 (бадЫате.боПррегСазе() === 1 ВОСУ') { саНЬаск (е) } } доситепб. адсЗЕуепбЫзбепег('кеускжп', Избепег, бгие) гебигп О => { скэситепб. гетоуеЕуепбЫзбепег (' кеудомп', Избепег, бгие) } }, [саНЬаск]) } ехрогб дебаибб изеКеуЫзбепег Хук принимает функцию обратного вызова и регистрирует ее для событий нажатия клавиш кеудомп по объекту боситепб. В конце изеЕИесь он возвращает функцию, ко- торая отменяет регистрацию функции обратного вызова. При изменении переда- ваемой функции обратного вызова, прежде чем регистрировать новую функцию, выполняется отмена регистрации предыдущей. В листинге 4.16 приведен пример использования нашего хука. Подумайте, все ли здесь правильно, сможете ли вы обнаружить небольшую проблему с написанием данного кода, которую нужно было решить. Листинг 4.16. Пример использования хука для отслеживания событий Одфят 1трогб { изеСаИЬаск, изеЗбабе } бгот 'геасб' бтрогб './Арр.сзз' йпрогб изеКеуЫзбепег бгот './изеКеуЫзбепег' сопзб В1СНТ_АВВОИ = 39 сопзб ЪЕЕТ_АВКОИ = 37 сопзб ЕЗСАРЕ = 27 бипсбтоп Арр() { сопзб [апд1е, зебАпдбе] = изеЗбабе(О) сопзб [1азбКеу, зебЬазбКеу] = изеЗбабе('') 1еб опКеуОомп = изеСаИЬаск ( (еуб) => { 16 ^б.кеуСобе === ЬЕГТ_АКРОИ) { зебАпд!е((с) => Мабк.тах(-360, с - 10)) зебЬазбКеу('Ьебб')
} е1зе И (е^.кеуСоде === К1СНТ_АКК0Н) ( зе1:Апд1е ((с) => Ма1±.1П1п (360, с + 10)) зеЬЬазЬКеу (’КхдГЛ') } е1зе 1Г (е^.кеуСойе === ЕЗСАРЕ) { зе!:Апд1е (0) зе1:Ьаз1:Кеу (' Езсаре ’) ) ), [зеГАпд1е] ) изеКеуЪ131епег(опКеуВоып) ге1:игп ( <с!^ с1аззЫате="Арр"> <р> Апд1е: {апд1е) ЬазЁ кеу: {1аз1:Кеу} </р> <зуд ю.сН±="400рх" Ъе1дкИ="400рх" ЫЛ1е="аггоы" Ш1="попе" з 1: г о кеИ 1сН±= " 10 " зЁгоке="Ыаск" зЬу1е={ { ^гапз^огт: 'гоЁаЁе (${апд1е)с1ед) ', )) > <ро1уИпе ро1пЬз="100,200 200,0 300,200" /> <ро1уИпе ро1пЬз="200, 0 200,400" /> </зуд> </сНу> ) ) ехрогр де^аи!!: Арр Программа ожидает нажатия пользователем клавиши правой или левой стрелки. Обработка этих событий осуществляется функцией опКеуОоип, но обратите внимание на то, что мы заключили ее в изеСаИЬаск. Если этого не сделать, то браузер будет воссоздавать функцию опКеуОоып всякий раз при отрисовке компонента Арр. Новая функция делает то же самое, что и старая, но она будет находиться в другом месте в памяти, и изекеуЫз^епег будет постоянно отменять ее регистрацию и повторно регистрировать ее. Если функцию обратного вызова не заключить в изеСаИЬаск, это может вызвать поток вызовов для отрисовки, что может замедлить работу приложения.
Благодаря изесаПЬаск мы можем обеспечить создание функции только при измене- нии зегАпд1е. При запуске приложения на исполнение на экран выводится стрелка, которую можно вращать, нажимая клавиши левой и правой стрелки на клавиатуре (рис. 4.10). Нажатие клавиши <Е§саре> возвращает стрелку в исходное вертикаль- ное положение. Угол 100 Нажата клавиша «Левая стрелка> Нажата клавиша «Правая стрелка> Нажата клавиша <Езсаре> Рис. 4.10. Управление стрелкой на экране посредством нажатия клавиш клавиатуры Обсуждение Мы предприняли должные меры предосторожности, чтобы функция изеКеуЫзьепег отслеживала только события нажатия клавиш, возникающие лишь на уровне эле- мента Ьос1у. Браузер не передает программе события нажатия клавиш стрелок, про- исходящие, когда фокус находится в текстовом поле. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬПр$://оге11.1у/У1У10. 4.5. Создание насыщенного содержимого посредством редактора Магкйошп ЗАДАЧА В приложениях, предоставляющих пользователям возможность работы с большими блоками текста, полезно сделать так, чтобы этот текст содержал должное формати- рование, ссылки и т. п. Но если передавать пользователю такие "ужасы", как необ- работанный код НТМЬ, то могут возникнуть проблемы безопасности и несказан- ные страдания разработчиков. Как же обеспечить пользователям ввод высокоинформативного содержимого, не подрывая безопасность приложения? РЕШЕНИЕ Задача предоставления пользователям возможности безопасно передавать в прило- жение объемное содержимое прекрасно решается посредством редактора Магк-
склуп. Чтобы увидеть, как работать с редактором Магкс1о\уп, рассмотрим следую- щее простое приложение (листинг 4.17), которое позволяет пользователю вставлять в список ряд сообщений с отметкой времени. Листинг 4.17. Приложение для ввода простых текстовых сообщений I с отметкой времени йпрогЁ { изеЗЁаЁе } Ггот 'геасЁ' хтрогЁ './Гогит.сзз' сопзё Гогит =()=>( сопзё [ЁехЁ, зеЁТехЁ] = изеЗЁаЁеС') сопзё [теззадез, зеЁМеззадез] = изе5ЁаЁе([]) геЁигп ( <зесЁ1оп с1а5зМате="Гогит"> <ЁехЁагеа со1з={80] гомз={20} уа!ие={ЁехЁ} опСЬапде={(еуЁ) => зеЁТехЁ(еуЁ.ЁагдеЁ.уа!ие)} /> <ЬиЁЁоп опСИск={() => { зеЁМеззадез((тздз) => [ ( Ьоду: ЁехЁ, ЁйпезЁатр: пем БаЁе().Ёо1808Ёг1пд(), }, .. .тздз, ]) зеЁТехЁ('') Н РОЗЁ </Ьоёёоп> {теззадез.тар((тзд) => { геЁигп ( <б!> <бЁ>{тзд. Ё1тезЁатр}</с1Ё> <с!с1> {тзд. Ьоду} < / с!с1> </с!1> ) })} </зесЁ1оп> ) } ехрогЁ деГаи!Ё Гогит
При исполнении приложения отображается большое текстовое поле (рис. 4.11). Когда пользователь вводит в поле простой текст, приложение сохраняет пробелы и переходы на новую строку. I Та1к оиЫ1пе тЬе Со11ош1пд 1» цъе •йгагс" оисИпе 1ог гобау'з Са1к. * 1пЬгодисЬ1оп мьз аз ап 1пзсгшпепс * СопЬго! ЕесЬгйдиез * ЫЬЗ 1тр1етепЬас1оп * Озаде Роз1 | Ров< | 2021-05-15Т22:49:12.4722 # Та1к оиИше ТЬе Ыкпйпд 151Ье * *(1гаЙ* оиШпе (ог (ойау'з 1а1к. * 1пГгос1ис(юп * М-2 аз ап 1П5(гитеп( • Соп1го1 (есНгйдиез • М.51гпр1етеп1а1юп * Цзаде * АсТмМвз Рис. 4.11. Вводимый в текстовое поле текст отображается на странице приложения Для приложений, использующих текстовые поля, целесообразно разрешить пользо- вателю вводить содержимое с помощью редактора Магкдолуп. Имеется множество библиотек Магкйохуп, но большинство из них представляют собой просто оболочки для компонента геасг-тагкдомп или выделители синтакси- ческих элементов типа РпзшДЗ (Ьйр8://рп8т]8.сош) или СоНеМ1ггог (Ьйрв:// соЛет1ггог.пе1). Здесь мы рассмотрим библиотеку геас{:-тс1-ес12Лог, которая добавляет дополнитель- ные возможности в компонент геасг -таг№оип, позволяя отображать поле редактора МагЫоип и редактировать текст в нем. Первым делом установим эту библиотеку: $ прт 1П51:а11 @ш.и/геасЁ-тс1-ес11Лог
Теперь мы можем преобразовать поле ввода простого текста в редактор МагЫогуп и форматировать вводимые сообщения языком НТМЬ (листинг 4.18). Листинг 4Л8, Ислолыим^^,С^ :-,,....... ...............?- -.....-$..'....-.......".'................''........ шрогХ { изе51:а1:е } Г гот 'геас±' 1трог1: МСЕсНЛог Ггот ' бигк/геас^-тд-едзЛог1 сопз!: МагкдокпЕогит = ()=>{ сопзё [ЁехЁ, зеЁТехЁ] = изеЗЁаЁе ('') сопзё [теззадез, зеЁМеззадез] = изеЗЁаЁе([]) геЁигп ( ОесЁЁоп с1аззЫате=’Тогит’’> <МОЕс11ёог Ъе1дЪЁ={300) Vа1ие={ЁеxЁ} опСЬапде={зеЁТехЁ} /> <ЬиЁЁоп опСИск={() => { зеЁМеззадез((тздз) => [ { Ьоду: ЁехЁ, ЁгтезЁатр: пем ОаЁе () . Ёо1303Ёг1пд (), }, ...тздз, ]) зеЁТехЁ ('') }} > Розё </ЬиЁЁоп> (теззадез.тар((тзд) => { геЁигп ( <61> <дЁ> {тзд. Ё1тезЁатр)</с1Ё> <дб> <МВЕсНЁог.Магкс1оут зоигсе=(тзд.Ьоду) /> </дс!> </с!1> ) })} </зесЁ1оп> ) } ехрогр деЁаиЁЁ МагкдоыпЕогит Небольшие усилия по преобразованию простого текстового поля в поле с возмож- ностями редактора Магкдолуп дают значительную отдачу. Как можно видеть из рис. 4.12, пользователь может применять сложное форматирование (псЬ Гогшайт^) к вводимому тексту, а также имеет возможность редактировать его в полноэкран- ном режиме перед сохранением.
В 15*5 И О> а = Е Е л так оиН пе ТЪе (оИслИпд № 1Ье йгиП аиШпе Ъг *аёау в 1а1к. Та1к оиШпе Л X ' кпгобисИоп вз ел 1П8Гпяпеп1 Сол1го1 (есПгадиев ИЬЗ 1тр*е<пвт1йк>п кзад* АсИуШм ТЬе 1о1к№нпд 151Ье дгаП оиШое Тог кх1а/51а1к. • 1пггосккЛ1оп М15 аз ап 1П51гытел1 • Соп1го11есЬп1диез • И 1.5 !тр1етеп1в1юп • Кваде АсГплЬеь Рой | В 15*0 <р „ <л ы -ее Л X Е X » ТакоиЦгя тпе гоио*1пд >в (Не 'Огал оишпе (&г «хЗау'в ИЖ. Та1к оиНте ' 1гЯтойис№п НЬ5 лч ип 1гк1плпвп1 Сошгй (еслгйдим НЬЗ 1тр<ел№пгшюп Цзаде ' Ас№Н1ш| ТЬе ГоИожтд 1$ 1Ье <1га/1 оиКпе №г Ьэйау’в 1а1к * 1п1го4ис1юп • ЫЬ8 аз ап 1пз(гитеп1 • Соп1гсН 1есНп1див5 • МЬ51тр1етеп(а1|оп Кваде * АсЙуйм В 1*»»#Н<АИЕЕЕ 3 I С X Ро»1 | 2021-05-15722:49:124722 Та1кои11те ТКе 1о11о\ллпд 51Ье &аК оиОте <ог кэдауЧ (а!к • 1п1пх1ис1юп • ЫЬБ ан ап 1П5(гитепт • СопТго) 1есЬп1доез • Ы15 )тр1етеп1айоп • Кваде _____ АсЙуМея___________________________________________________________________ Рис. 4.12. Редактор Магкс1оууп обеспечивает предварительный просмотр вводимого текста и редактирование в полноэкранном режиме
Обсуждение Добавление в приложение возможностей редактора Магкс1о\\п не занимает много времени и требует минимальных усилий, но значительно улучшает восприятие приложения пользователем. Подробная информация по редактору Магкдохуп пре- доставляется в оригинальном руководстве его разработчика Джона Грубера (ЗоИп СшЬег), доступном по адресу Ьйр8://огей.1у/2ЕЕ9л. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ьйр8://огеП.1у/80п7х. 4.6. Анимация посредством классов С58 ЗАДАЧА В разрабатываемое приложение требуется добавить небольшую простую анима- цию, не прибегая при этом к установке сторонних библиотек, чтобы не увеличивать размер приложения. РЕШЕНИЕ Для большинства анимационных эффектов, которые вам когда-либо потребуются в приложении Кеас1, установка сторонних библиотек, скорее всего, не будет обяза- тельной. В настоящее время средства С88 дают браузерам нативную возможность анимировать свойства С88 с минимальными усилиями. Анимация получается плавная, не требуя большого объема кода, поскольку она генерируется аппаратны- ми графическими средствами. Анимация посредством графического процессора менее энергозатратная, что делает ее применение более подходящим для мобиль- ных устройств. । Если вы хотите добавить анимационные эффекты в свои приложения Кеас1, нач- | ните с возможностей С88, прежде чем рассматривать другие подходы. Анимация средствами С88 основана на свойстве С88, называющемся переходом (1гап8Йюп). Предположим, что мы хотим создать расширяющуюся информацион- ную панель. При нажатии пользователем кнопки на панели она должна раскрыться, а при повторном нажатии кнопки — так же плавно закрыться (рис. 4.13). Такой анимационный эффект можно создать при помощи свойства тгапзШоп С88, показанного в листинге 4.19. Листинг 4.19. Свойство Ьгапз1Ъ±оп С83 . 1пЕоРапе1-<ЗеТа11з { ИегдЬТ: 350рх; Ёгап51Т1оп: НетдИР 0.5з; }
ТЫ® !$ ап ехрапбаЫе рапе! V ТИ15 1з ап ехрапбаЫе рапе! ТЫэ ю а сотропегй тЬа( изет СЗЗ агигпаЬап 1о схрапй апй соп(гас1 Йи5 6е1ай рапе! Рис. 4.13. Простая С88-анимация плавно раскрывает и закрывает информационную панель В данном правиле С88 указываются свойства ЬехдЫ: и хгапзШоп. Эта комбинация задает плавное изменение текущей высоты (кехдит) элемента к требуемой в течение следующей пол секунды. Анимационный эффект запускается при изменении высоты элемента, например когда становится действительным другое правило С88. Если имеется другое имя класса С88, в котором указана иная высота (листинг 4.20), то свойство игапзШоп выполнит изменение высоты с анимационным эффектом при переключении эле- мента на другой класс. Г Листинг 4.20. Определение другого имени класса С58 с другой высотой элемента I__.________________________________________________ ____ ______ ___________________ . ТпГоРапеХ-беСаИз { ЬегдЫ:: 350рх; Ргап51Р1оп: ИехдЫ: 0.5з; } . 1пГоРапе1-с1е{:а1.15. ТпГоРапе1-с1е1:а11з-с1озес1 { ЪехдкЛ: 0; Данная структура именования классов— это пример именования ВЕМ (В1оск Е1етеп1 МобЖег— модификатор блочных элементов). Блоком в данном случае является компонент (1пХоРаг.е1), элементом— содержимое блока (бе^аИз), а мо- дификатор сообщает текущее состояние элемента (с1озес1). Схема именования ВЕМ понижает вероятность конфликта имен в программе. Если элемент тпГоРапеХз-бесаИз внезапно получит дополнительный класс . Ш^оРапеХ-беСаИз-сХозеб, ТО его высота изменится С 350рх ДО 0 И СВОЙСТВО Ёгапз1Ё1оп плавно свернет элемент. И наоборот, при потере класса . хпгоРапеХ-беСаХХз-сХозеб элемент снова будет развернут. Все это означает, что мы можем оставить всю тяжелую работу С88, а в коде Кеас1 нам нужно будет только добавлять класс к элементу или удалять его, как показано в листинге 4.21.
: Листинг 4.21. Манипулирование классами элемента в коде Кеас1 нпрогЬ { изе51:аЁе } ^гот 1 геас±' 1трогЁ './1п^оРапе!. сзз' сопз11 1пГоРапе1 - ({ ЫЁ1е, сИИдгеп }) => { СОП31: [ореп, зе(;Ореп] = изеЗ^аЁе (Ра1зе) ге1:игп ( <зесЁ1оп с1аззМа1пе="1пРоРапе1"> <Ъ1> {} <ЬиМ;оп опС11ск={ () => зе1;0реп(^) => !у) }> {ореп ? 'л' : 'V'} </Ьи1:1:оп> </Ы> с1аззМате={' 1пРоРапе1-с1е1;а11з ${ ореп ? 11 : ' 1пРоРапе1-с1е1:а11з-с1озеб' }'} > {сЪИдгеп} </сНу> </зес11оп> ) } ехрогЁ бе^аиИ; Тп^оРапе! Обсуждение Нам часто приходилось видеть включение в проекты сторонних библиотек компо- нентов для использования какого-либо небольшого виджета, который разворачива- ет или сворачивает свое содержимое. Как было продемонстрировано выше, добав- ление такого анимационного эффекта в приложение не представляет никаких сложностей. Исходный код данного рецепта можно загрузить на веб-сайте СгйНиЬ по адресу ЬМр5://огеП.1у/ГКп1с. 4.7. Анимация средствами Кеас! ЗАДАЧА Хотя анимационные средства С88 очень низкотехнологичные, они будут достаточ- ными для большинства задач анимации, которые вам, вероятно, могут потребо- ваться. Но они требуют глубокого понимания различных свойств С88 и эффектов их ани- мации. Например, как можно проиллюстрировать удаление элемента, резко его расширяя и делая прозрачным?
Многие библиотеки, например библиотека АгптаТе.сзз (ЬМр$://ап1та(е.$(у1е), со- держат большое множество готовых анимаций С88, но для них часто требуется применение более продвинутых принципов С88-анимации, таких как опорные кад- ры (кеуГгате). Кроме того, они не особенно заточены под Кеас1. Так как же нам добавить в приложение Кеас1 анимации посредством С88-библиотек? РЕШЕНИЕ Такую задачу можно решить, используя библиотеку В.еас( Атта1юп$ в качестве оболочки Кеас! для библиотеки АштаЩ.сзз. Эта библиотека позволит эффективно добавлять анимационный стиль в разрабатываемые компоненты, не требуя созда- ния дополнительных отрисовщиков и не увеличивая значительно размер генери- руемой БОМ-модели. Эффективность этой библиотеки обеспечивается ее совмещением с какой-либо библиотекой типа С88-1П-.18. Технология библиотек С88-1П-18 позволяет размещать информацию о стилях непосредственно в коде 1ауа8спр1. Средство Кеас1 также по- зволяет добавлять атрибуты стиля в виде компонентов Кеас1, но технология С88-1П- 18 делает это более эффективно, динамически создавая общие элементы стиля в элементе Ьеай страницы. На рынке доступно несколько библиотек С 8 8-1 п-18, но для этого рецепта мы выбе- рем библиотеку КасНит (ЬНр8://огеП.1у/о№ВЕ1). Для начала установим эту библиотеку, а также библиотеку Кеас( АштабопБ: $ прт 1пз1:а11 гасНит $ прт 1пз(:а11 геас!1-ап1таТ1опз Наше приложение будет исполнять анимационный эффект при каждом добавлении изображения в коллекцию (рис. 4.14). 1тадм (га т (.огет Ркзит Рис. 4.14. Нажатие кнопки Ас1с1 загружает новое изображение из хранилища р18ит.рГю1о8
Стадез Ггот Югетп Р»езит Рис. 4.15. Щелчок мышью по пятому изображению запускает анимационный эффект нарастающей прозрачности до полного исчезновения изображения и его удаления из коллекции Аналогично при щелчке мышью по изображению оно удаляется из коллекции с эффектом нарастающей прозрачности (рис. 4.15)4. Начнем с импортирования некоторых анимаций из библиотеки Кеас1 Апппабоп и вспомогательного кода из библиотеки КасНшп (листинг 4.22). Листинг 4.22. Импортирование кода из библиотек Неас1 Аттабоп и Кабшт нпрогЁ { ри1зе, гоотОиТ, зкаке, тегде } Ггот 'геасТ-агшпаТ1опз' нпрогЬ КасЦит, { 5Ёу1еНоо1: } ^гот 'гасНит' сопз1 зЬу1ез = { сгеа(;ес1: { апгтаМоп: ' х 0.5з', ап1таТ1опЫате: КасЦит.кеуГгатез(ри1зе, 'ри1зе'), Ь де1еТес1: { агйтаТхоп: ' х 0.5з', апттаЫопКате: ВасЦит. кеуГгатез (тегде (хоотОи<:, зкаке), ' хоотОиТ'), }, } Из библиотеки Кеас1 Аттабоп импортируются анимационные эффекты ри1зе, хоотОит и зИаке. Анимация ри1зе будет реализована при добавлении изображений в коллекцию. А при удалении изображений будет использоваться объединение 4 Бумажные книги, несомненно, прекрасные вещи, но чтобы полностью оценить анимационный эффект, загрузите код приложения с СИ(НиЬ (КПр8://огеЙ.1у/ОсАцо).
анимаций гоотОие и зкаке. Для объединения анимаций служит функция тегде биб- лиотеки Кеас1 Аштабоп. Функция 5Ёу1ез генерирует все стили С88, необходимые для исполнения всех этих полусекундных анимационных эффектов. Вся работа по исполнению анимаций выполняется функцией Каахшп.кеу^гатез о. Нам нужно знать, когда действие анимации полностью завершится, потому что если мы удалим изображение перед этим, то у нас не будет, к чему применять ани- мационный эффект. Для отслеживания С 8 8-анимаций можно передавать функцию обратного вызова опАпгтаЫопЕпс! любому элементу, который мы намереваемся анимировать. Для каж- дого элемента в нашей коллекции изображений нужно отслеживать три элемента: ♦ адрес ОКЬ представляемого им изображения; ♦ булеву переменную, которая будет иметь значение егие, пока исполняется ани- мация создания изображения; ♦ булеву переменную, которая будет иметь значение ъгие, пока исполняется ани- мация удаления изображения. В листинге 4.23 приведен код для анимации изображений, добавляемых в коллек- цию и удаляемых из нее. Листинг 4.23. Код для анимации добавляемых и удаляемых изображений 1трогЬ { изе51:а1:е } ^гот ' геас<:' ипрогХ { ри1зе, хоотОиЁ, зЪаке, тегде } Егот 1 геасЕ-апЕтаМопз' ХтрогЕ КасИит, { ЗЕуТеКооЕ. } Егот ' гасНит' ЕтрогЕ ' . /Арр. сзз' сопзЕ зЕуЕез = { сгеаЕед: { апЕтаЕЕоп: 'х 0.5з', апЕтаЕЕопЫате: КасИит.кеуЕгатез(ри1зе, 'ри1зе'), }, беЕеЕес!: { апЕтаЕЕоп: 'х 0.5з', апЕтаЕЕопКате: КасИит. кеуЕгатез (тегде (хоотОиЕ, зЬаке), ' хоотОиЕ'), }, } ЕипсЕЕоп деЕЗЕуЕеЕогЕЕетЦЕет) { геЕигп ЕЕет.беЕеЕЕпд ? зЕуЕез. (ЕеЕеЕес! : ЕЕет.сгеаЕЕпд ? зЕуЕез.сгеаЕеб : пиЕЕ }
ТипсЁТоп Арр() { сопзё [йаЁа, зеЁБаЁа] = изеЗЁаЁе ([]) 1еЁ беТеЁеТЁет = Я) => зеЁРаЁа((<1) => { сопзё гези1Ё = [...б] гезиТЁ[1].деТеЁЁпд = Ёгие геЁигп гези1Ё }) 1еЁ сгеаЁеТЁет = () => { зеЁБаЁа((д) => [ .. .а, { иг! : 'ЪЁЁрз://р1С8ит.рЁюЁО5/1с1/${б.1епдЁЁ1 * 3}/200', сгеаЁЁпд: Ёгие, }, ]) } 1еЁ сотрТеЁеАпТтаЁхоп = (6, 1) => { (с1.с1е1еЁ1пд) { зеЁБаЁаЦс!) => { сопзё гези1Ё = [... с1] гези1Ё.зрИсе (1, 1) геЁигп гези1Ё }) } еТзе 1Ё (сксгеаЁЁпд) ( зеЁБаЁа ((с!) => { сопзё гези1Ё = [...с1] гези1Ё[1].сгеаЁгпд = ТаТзе геЁигп гези1Ё }) } } геЁигп ( <с11у сТаззЫате="Арр"> <8Ёу1еКооЁ> <Р> Ттадез Тгот&пЬзр; <а кгеЁ="ЬЁЁрз://р1с8и1п.рЁ1ОЁоз/">Ьогет Р1сзит</а> </р> <ЬиЁЁоп опСИск= {сгеаЁеТЁет}>Ас1с1</ЬиЁЁоп> {с!аЁа.тар( (б, 1) => ( <с1гу 5Ёу1е={деЁЗЁу1еГог1Ёет(с1) } опАп1таЁ1опЕпс1={ О -> сотр1еЁеАп1таЁ1оп (6, 1)}
<1шд 1с1= {' 1шаде$ {1}'} 5гс={6.иг1} и1сН±={200} Ъе1дЫ:={200} а1Г="Капс1от" ММе="С11ск ёо С1е1е1;е" опСИск={() => де1е1:е11:ет(1)} /> </сИ\л> ))} </2Ёу1еКооЁ> </сНу> ) } ехрогр деГаи1Ё Арр Обсуждение При выборе анимационного эффекта сначала нужно дать ответ на вопрос: что будет означать данная анимация? Все анимационные эффекты должны иметь значение. Например, обозначать некое действие, связанное с бытием (создание или удаление), указывать изменение со- стояния (включение или отключение) или увеличивать либо уменьшать изображе- ние, чтобы показать подробности или более общую картину соответственно. Или же иллюстрировать предел или границу (эффект отскакивания в конце длинного списка), или дать возможность пользователю выразить свое предпочтение (смахи- вание по сенсорному экрану влево или вправо). Анимация также должна быть краткой. Большинство анимационных эффектов должны завершиться через полсекунды, чтобы пользователь мог ощутить сущность анимации без осознанного ее восприятия. Анимационный эффект никогда не должен быть просто декоративным. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬНря://огеН.1у/гКК8Г. 4.8. Анимация информационной графики посредством библиотеки ТууеепОпе ЗАДАЧА Анимационные эффекты выполняются средствами С88 плавно и высокоэффектив- но. На стадии компоновки браузеры могут передавать С 8 8-анимацию графиче- скому оборудованию, в результате чего анимационные эффекты будут не только
исполняться на уровне машинного кода, но и сам машинный код не создаст допол- нительной нагрузки на центральный процессор. Но в результате исполнения С88-анимаций на графическом оборудовании код при- ложения "не знает", что происходит во время анимации. Он может отслеживать, когда анимация начинается, завершается или повторяется (события опАпгшаЫопЗ^агЁ, опАпгтаТи опЕпс! И опАп1таЕ1оп11:ега1:1оп соответственно), НО все происходящее между началом и завершением будет загадкой. При анимации, например, столбикового графика вдобавок к изменению высоты самих столбцов может быть желательным также анимировать числа соответствую- щих столбцов. Или в анимированном приложении для отслеживания прохождения велосипедной гонки одновременно с динамическим отображением положения от- дельных велосипедистов на местности может требоваться отображать их текущую высоту над уровнем моря. Но как создать анимации, состояние которых можно отслеживать в процессе ис- полнения? РЕШЕНИЕ Библиотека Ту/еепОпе создает анимационные эффекты при помощи 1ауа8спр1, что означает, что их можно отслеживать кадр за кадром. Начнем с установки этой библиотеки: $ прт 1пз1:а11 гс-Ъмееп-опе Библиотека ТиеепОпе работает со стилями С88, но не использует анимации С88. Вместо этого она генерирует преобразования С88, которые обновляет множество раз в секунду. Элемент, который требуется анимировать, нужно заключить в оболочку элемента <тиеепОпе/>. Например, предположим, что нам нужно анимировать прямоугольник гес1 внутри графики 8УС. Соответствующий код будет выглядеть следующим об- разом: <ТмеепОпе сотропеп1:=' д' ап1та!:1оп= {... деЁаИз Неге} > <гес1: Ы1ЙРЪ="2" Ъе1дЫ=,’6" х="3" у="-3" ГИ1=,,кЫЁе"/> </ТкеепОпе> Элемент тыеепОпе принимает в качестве параметров имя анимируемого элемента и объект, описывающий требуемую анимацию. Мы рассмотрим этот объект более подробно чуть далее. Имя элемента (в данном случае это просто буква д) используется библиотекой ТиеепОпе для создания оболочки вокруг анимируемого объекта. Эта оболочка снабжается атрибутом стиля, содержащим набор преобразований С88 для пере- мещения и вращения содержимого. Таким образом, на определенном этапе анимации в нашем примере ЭОМ-модель может выглядеть следующим образом:
<д зку1е='7гапз?огт: кгапз1аке(881.555рх, 489.614рх) гсЛаРе (136.174с1ед); "> <геск м1сО="2" Ъе1дЬЁ="6" х="3" у="-3" 1И1-"иИИе"/> </д> Хотя посредством библиотеки ТхуеепОпе можно создавать эффекты, похожие на анимации С88, эта библиотека работает по-иному. Вместо того чтобы передавать исполнение анимации аппаратному обеспечению, она создает каждый кадр посред- ством 1ауа8спр(. Это имеет два последствия. Во-первых, она задействует вычис- лительные мощности центрального процессора (плохо), а во-вторых, позволяет отслеживать анимацию в процессе исполнения (хорошо). Если тиеепопе передать функцию обратного вызова опОрйаье, на каждом кадре нам будет передаваться информация о процессе анимации: <ТмеепОпе сотропепС=' д' ап!та1:1оп={.. .деОаИз Неге} опир<ЗаРе={1пГо=>{. . .} }> <геск ы1сШ1="2" Ие1дЪЁ="6" х="3" у="-3" Ш1="нЫ11е'7> </ТмеепОпе> Переменная гаМо передаваемого функции опорке объекта 1п1о может принимать значения от 0 до 1, представляющие пропорцию исполненной части анимации эле- мента тмеепОпе от общей ее длительности. Эту пропорцию можно использовать для анимации текста, связанного с графикой. Например, если мы создадим анимированную панель управления, на которой ото- бражается местонахождение автомобилей на гоночной трассе, то сможем посредст- вом функции опцрйане отображать скорость и пройденное расстояние каждого авто- мобиля в процессе перемещения по трассе самого автомобиля. Для этого примера будем применять графику 8УС. Первым делом создадим строку, содержащую путь 8УС, который представляет гоночную трассу: ехрогк аеГаиИ 'ш 723.72379,404.71306 ... -8.30851,-3.00521 х' В действительности это значительно урезанная версия настоящей трассы, которую мы будем использовать. Строку трассы можно импортировать из файла (гаек.)8 сле- дующим образом: ипрогЬ ра1± Ггот './кгаск' Отобразить трассу в компоненте Кеас! можно, выполнив отрисовку элемента зуд (листинг 4.24). Листинг 4.24. Отрисовка компонента ечд <зуд Ъе1дЪЁ="600" м1с11:11=,,1000" У1еыВох="0 0 1000 600" зку1е={ {Ьаскдгоипс1Со1ог: 'Ыаск'} }> <ракИ зкгоке='#444' з1:гокеК1<Л:Ь={ 10} ГИ1='попе' с!={ра1:к}/> </зуд> К этому коду можно добавить пару прямоугольников для представления автомоби- ля: красный — для кузова и белый — для лобового стекла (листинг 4.25).
Листинг 4.25. Добавление кода для представления автомобиля <зуд Ъе1д1и="600" и1<1кк="1000" V^еиВоx="0 0 1000 600" зку1е= {{Ьаскдгоипс1Со1ог: ' Ыаск'}} > <ракк зкгоке='#444' зкгокеК1с1к11={ 10} 1111='попе' с!={рак11}/> <геск ш.с!к11={24} ке1дкк={16) х={-12} у={-8} Г111='гесГ/> <геск Н1<1к11={2} Ъе1дкк={6} х={3} у—{—3} Ш1--'ыЫке'/> </^д> На рис. 4.16 показано, как будет выглядеть наша гоночная трасса с автомобилем в левом верхнем углу окна. Рис. 4.16. Гоночная трасса с автомобилем в левом верхнем углу Но как нам реализовать анимацию движения автомобиля по трассе? Это легко сде- лать при помощи подключаемого модуля Ра1йР1и§1п библиотеки Ту/еепОпе, предна- значенного для создания анимаций, следующих вдоль строк пути 8УС: нпрогк РакЬР1ид1п Ггот ' гс-кыееп-опе/ИЬ/рЗидкп/РаккРкидхп' ТиеепОпе.р1ид!пз.ризЬ(Ра1±Р1ид1п) В результате мы сконфигурировали Т\\еепОпе для работы с анимациями путей 8У6. Теперь мы можем рассмотреть, как описать анимацию для Тм/еепОпе. Делаем мы это посредством простого объекта 5ауа8сг1р1 (листинг 4.26). Листинг 4.26. Объект с описанием анимации ипрогк ракк 1г от './кгаск' сопзк 1о11оиАп1так1оп = { ракк: { х: ракк, у: ракк, гокаке: ракк }, гереак: -1, }
В этом объекте мы даем библиотеке ТхуеепОпе два указания: 1. Создавать перемещения и вращения, следующие вдоль строки пути, которую мы ранее импортировали из файла 1гаск.]з. 2. Задав счетчику гсреаЕ значение -1, мы сообщаем, что анимация должна выпол- няться в бесконечном цикле. Мы можем использовать этот объект в качестве основы для анимации нашего автомобиля (листинг 4.27). Листинг 4.27. Базовый код для анимации автомобиля <зуд кеЕдкЕ="600" ыЕс1Ек="1000" V^емВоx="0 0 1000 600" зЕу1е={ {Ьаскдгоипс1Со1ог: 'Ыаск'} }> <раЕк зЕгоке='#444' зЕгокеИЕс1Ек={10) ЕЕ11='попе' с!={раЕк}/> сТыеепОпе сотропепЕ=' д' апЕтаЕЕоп={{_ЕоИоыАпЕтаЕЕоп, бигаЕЕоп: 16000} }> <гесЕ ыЕс!Ек={24} кеЕдкЕ={16} х={-12} у={-8} ЕЕИ^гесГ/> СгесЕ ыЕс1Ек={2} кеЕдкЕ={6} х={3} у={-3} ЕЕ11='ыкЕЕе' /> </ТыеепОпе> </^д> Обратите внимание на наличие оператора расширения (троеточие) для предостав- ления дополнительного параметра йигаЕЕоп. Его величина задана в миллисекундах, означая, что анимация должна длиться 16 секунд. Мы можем добавить второй автомобиль и назначить функцию обратного вызова опЕ)рс1аЕе, чтобы создать для каждого из них очень базовый набор имитированных телеметрических данных, когда они перемещаются по трассе. В листинге 4.28 при- веден полный код приложения. Листинг 4.28. Полный код анимации гонок и отображение телеметрических данных ЕтрогЕ { изеЗЕаЕе } Егот 'геасЕ' ЕтрогЕ ТыеепОпе Егот ’гс-Еыееп-опе' ЕтрогЕ 0еЕаЕ1з Егот ’./ОеЕаЕ1з’ ЕтрогЕ раЕк Егот './Егаск' ЕтрогЕ РаЕкР1идЕп Егот 1гс-Еыееп-опе/1ЕЬ/р1идЕп/РаЕкР1идЕп' ЕтрогЕ дгЕс! Егот ' ./дгЕскзуд' ЕтрогЕ './Арр.сзз' ТыеепОпе.р1идЕпз.ризк(РаЕкРЕидЕп) сопзЕ ЕоИоыАпЕтаЕЕоп = { раЕк: { х: раЕк, у: раЕк, гоЕаЕе: раЕк }, гереаЕ: -1, } ЕипсЕЕоп Арр() { сопзЕ [гес1Те1етеЕгу, зеЕКедТеЕетеЕгу] = изеЗЕаЕе({ <ЕЕзЕ: 0,
зрееск О, 1ар: О, }) сопз!: [ЫиеТе1етеЬгу, зе1:В1иеТе1е1пеЁгу] = изе31:аЁе({ сПзё: О, зрееск О, 1ар: О, }) сопз!: Ьгаск7еЫс1е = (1пГо, ^е1ете11гу) => ({ сНзё: 1п1о.гаЫо, зрееск 1п?о.га1:1о - ^е1етеЁгу.сНзЁ, 1ар: 1п^о.гаГ:1о < ТгеХете^гу.сИзЁ ? Ёе1етеЁгу. 1ар + 1 : Ёе1етеЁгу.1ар, }) ге^игп ( <сИу с1аззЫате="Арр"> <Ь1 >Ь1игЬигдг 1пд</Ы> <0еРаНз гесГГе1ете1:гу= {гес1Те1ете1:гу} ЫиеТе1етеЁгу={ЫиеТе1етеЁгу} /> <ЗVд Ье1дкР="600" И1СН±="1ООО" У1емВох="0 О 1ООО 600" з!:у1е={{ Ьаскдгоипс1Со1ог: 'Ыаск' }} > <1таде ЬгеГ={дг1д} ы1с11±={1000} Ь.е1дГ1к= {600} /> <раРк зЁгоке="#444" з1:гокеИ1сН±={10} 1И1="попе" с!={ра11к} /> <раЬк зЁгоке=''#с0с0с0" з^гокеКЫГ:11={2} зЪгокеСаз11аггау=''3 4" Ш1="попе" б={раМ1} /> <ТиеепОпе сотропепЁ="д" ап1та111оп={ { ... Го11оыАп1таЫоп, йигаЫоп: 16000, опОрс!а1:е: (1пГо) => зеЪВесПЫете^гу ((1:е1ете1:гу) => ЪгаскУеЫЫе (1п1о, РеХетеЪгу) ), }}
<гес± Ы1СН:Ь= {24} Ие1дЫ;={1б) х={-12} у={-8} Ш1-"гес1" /> <гес!1 ыд.с11111= {2} Не1дЫз={6} х={3} у={-3} ИИ^’иЫХе" /> </ТыеепОпе> сТыеепОпе сотропеп!:="д" ап1таЁ1оп={( ... Го11оиАп11па1;1оп, бе1ау: 3000, дигаМоп: 15500, опОрбаге: (1пГо) => 8еОВ1иеТе1етеТгу((Те1етеТгу) => ТгаскЛ/еЫсХе(хп^о, Те1етеТгу) ), }} > <гесИ ю.скк={24} Ие1д1Ю={ 16} х={-12} у={-8} Ш1="Ыие" /> <гес6 и1<Згк-{2} ке1дЬР={6} х={3} у={-3} ГИ1="иЬ1Те" /> </ТиеепОпе> </8Vд> </с31У> ) } ехрог11 <ЗеГаи11: Арр На рис. 4.17 показано, как выглядит исполняющееся приложение. Автомобили перемещаются вдоль пути гоночной трассы, поворачиваясь, чтобы передняя часть автомобиля была ориентирована по направлению движения. Рис. 4.17. Внешний вид конечной версии анимации с телеметрическими данными, генерируемыми из текущего состояния анимации
Обсуждение Для реализации большинства анимационных эффектов пользовательского интер- фейса следует использовать средства анимации С88. Но в случае информационной графики часто требуется синхронизировать текст с графикой. Это можно реализо- вать при помощи средств библиотеки ТиеепОпе, хотя за счет повышенной загрузки вычислительных мощностей центрального процессора. Исходный код данного рецепта можно загрузить на веб-сайте СгйНиЬ по адресу Ш(р$://огеП.1у/817 Ур.
Подключение к службам В отличие от фреймворков, например таких, как Ап^Шаг, библиотека Кеас( не со- держит всех возможностей, которые могут потребоваться для приложения. В част- ности, она не предоставляет стандартного способа получения приложениями дан- ных от сетевых служб. Такая свобода действий является, в принципе, положитель- ным аспектом, поскольку это означает, что приложения Кеас1 могут использовать самые последние технологии. Но есть и недостаток — начинающим разработчикам КеасТ придется решать задачу выбора подходящего средства своими собственными силами. В данной главе мы рассмотрим несколько способов подключения сетевых служб к разрабатываемым приложениям. Во всех этих рецептах мы увидим несколько общих идей и будем пытаться отделять код для работы с сетевыми возможностями от кода, использующего его компоненты. Таким образом, при появлении новой технологии сетевой службы мы сможем переключиться на нее без необходимости переделывать большой объем кода. 5.1. Преобразование сетевых вызовов в хуки ЗАДАЧА Одно из преимуществ разработки на основе компонентов заключается в том, что таким образом код разбивается на небольшие легко управляемые фрагменты, каж- дый из которых выполняет четко определенное, распознаваемое действие. В неко- тором смысле наилучший компонент — это компонент, который можно просмат- ривать на большом экране без необходимости выполнять прокрутку. Одно из наи- лучших достоинств Кеас1 состоит в том, что с течением времени эта библиотека стала проще во многих отношениях. Хуки Кеас1 и отказ от компонентов на основе классов позволили избавиться от шаблонов и уменьшили объем кода. Но добавление в компонент кода для сетевого обмена — один из верных способов увеличить размер компонента. Если вы стремитесь создавать простой код, следует стараться не включать сетевой код в разрабатываемые компоненты. Таким образом компоненты будут небольшого размера, а сетевой код будет больше поддаваться повторному использованию. Так как же можно отделить сетевой код от остального кода компонента?
РЕШЕНИЕ В этом рецепте мы рассмотрим способ, как переместить сетевые запросы в хуки Кеас1 для отслеживания, продолжает ли исполняться сетевой запрос или же про- изошла какая-либо ошибка, не допустившая его успешного выполнения. Прежде чем вникать в детали, рассмотрим, следующие три важных аспекта, кото- рые нужно отслеживать при реализации асинхронного сетевого запроса: ♦ данные, возвращаемые запросом; ♦ ход загрузки с сервера запрошенных данных; ♦ любые ошибки, которые могли произойти при исполнении запроса. Мы увидим все три аспекта присутствующими в каждом из рецептов этой главы. Не важно, исполняем ли мы запрос посредством команды или ах1оз, через свя- зующее программное обеспечение Кедих или при помощи промежуточного языка запросов для АР1, например СгарИрЬ, для запроса всегда будут иметь важность данные, состояние их загрузки и ошибки процесса загрузки. Для примера создадим простую доску сообщений, состоящую из нескольких фору- мов. Сообщения форумов состоят из поля автора сообщения и поля текста сообще- ния. На рис. 5.1 приведен снимок экрана этого приложения, которое можно загру- зить на веб-сайте СиШиЬ. | НАЗА [ ЯсММАЗА ] 8С Ао11з согпр!е1е апд а рИсЬ 18 ргодгат. Опе ВААУО. РАО Опе ВРА\/О «5 ап аЬог1 соп!го1 гподе!. АН Лиде 1$ 2 тПез. САРСОМ АН 18 иеП а! НоизАоп. Уои аге доод а11 тти1е. Рис. 5.1. Кнопки позволяют выбрать форум МАЗА или форумы, иные, чем МАЗА Кнопки в верхней части окна приложения позволяют выбрать форум КА8А (КА8А) или форумы, иные, чем КА8А (Ко( КА8А). Бэкенд обработка для нашего приложения обеспечивается сервером Коде, а в само приложение было загружено несколько сообщений для форума КА8А. Загрузите исходный код приложения и активизируйте сервер бэкенда, запустив на исполнение файл сценария $егуег.]з в корневой папке приложения: $ поде ./зегуег.]5 Сервер бэкенда будет исполняться по адресу ЬНр://1оса11ю8<:5000. Теперь можно запустить на исполнение само приложение Кеас1: $ прт гип зЁаг1: Приложение, как обычно, исполняется на порту 3000.
В режиме разработки все запросы к серверу бэкенда проходят через прокси- сервер Кеас1. При создании приложения посредством команды сгеаЁе-геасЁ-арр эту возможность можно реализовать, добавив свойство ргоху в файле раскаде.]5оп и присвоив ему значение Ъёёр://1оса1ЬозЁ:5000. В результате сервер Реас! будет передавать запросы АР1 нашему серверу бэкенда 8еп/ег.|$. Например, запрос кЁЁр://1осаИюзЁ:3000/тез5аде5/паза (который возвращает массив сообщений из форума НАЗА) будет перенаправлен на адрес прокси-сервера Кйр://1оса1Иоз1:5000/ теззадез/пава. Сетевой запрос для чтения сообщений подается посредством простой команды ЁеЁсЪ (листинг 5.1). Листинг 5.1, Сетевой запрос для чтения сообщений 1 сопзё гезропзе = ака1Ё ЁеЁск('/теззадез/${Ёогит)') 1Г (!гезропзе.ок) { сопзё ЁехЁ = аиаЁЁ гезропзе.ЁехЁ() ёЬгои пен Еггог ('ипаЫе ёо геад теззадез Ёог ${Ёогит): ${ЁехЁ}') ) сопзё Ьоду = аиахЁ гезропзезоп() Переменной Гогит в этом коде будет присвоено строковое значение идентификатора форума. Команда ЁеЁсЬ — асинхронного типа и возвращает обещание (ргогшзе), по- этому будем ожидать его получения командой аиагЁ. Затем мы можем проверить, не завершился ли вызов сбоем с каким-либо кодом ошибки НТТР, и если да, то гене- рируем исключение. Из ответа мы извлечем объект .18ОК и сохраним его в пере- менной Ьоду. В случае отсутствия в теле ответа объекта правильного формата также генерируется исключение. Нам нужно отслеживать три вещи: данные, состояние загрузки и наличие любых ошибок. Обработку всего этого мы заключим в специальный хук, поэтому созда- дим три состояния: ааЁа, 1оасНпп и еггог (ЛИСТИНГ 5.2). Листинг 5.2. Кед хука изеМеззадез сопзё изеМеззадез = (Ёогит) => { сопзё [6аЁа, зеЁВаЁа] = изе5ЁаЁе([]) сопзё [1оасИпд, зеЁЬоасЦпд] = изеЗЁаЁе(Ёа1зе) сопзё [еггог, зеЁЕггог] = изеЗЁаЁеО геЁигп { даЁа, 1оасИпд, еггог } В хук изеМеззадез в качестве параметра будем передавать название форума, а он бу- дет возвращать объект, содержащий состояния данных баЁа, загрузки 1оасИпд и ошибки еггог. Значения из объекта можно извлечь и переименовать в любом ком- поненте посредством деструктурирования объекта, как показано в листинге 5.3.
Листинг 5.3. Деструктурирование объекта сопзЕ { даТа: теззадез, 1оасНпд: теззадезЬоасНпд, еггог: теззадезЕггог, } = изеМеззадез('паза') -ф- Переименование переменных в операторе расширения помогает избежать кон- фликтов имен. Например, если нужно прочитать сообщения из нескольких фору- мов, можно выполнить другой вызов хука изеМеззадез и переменную для его ответа назвать не теззадез, а по-другому. Но возвратимся обратно к нашему хуку изеМеззадез. Сетевой запрос зависит от зна- чения передаваемой ему переменной Еогиш, поэтому необходимо обеспечить испол- нение запроса ЕеЕсь внутри хука изеЕЕЕесЕ (листинг 5.4). Листинг 5.4. Помещение запроса в оболочку хука ивввязесс изеЕЕЕесЕ(() => { зеЕЕггог(пи!1) 1Е (Еогит) { } е1зе { зеЕБаЕа ([]) зеЕЬоаё1пд(Еа1зе) ) }, [Еогит]) На данном этапе мы опускаем код, который выполняет сам запрос. Код в хуке изеЕЕЕесЕ будет исполняться при первом вызове этого хука. При повторной отри- совке клиентского компонента и передаче такого же самого значения Еогит хук изеЕЕЕесЕ исполняться не будет, поскольку зависимость [Еогит] не изменится. Хук будет исполняться повторно только при изменении значения переменной Еогит. Теперь рассмотрим, как можно вставить запрос ЕеЕсЪ к этому хуку (листинг 5.5). Листинг 5.5. Добавление запроса зйвЪсй к хуку иаеИЕГвсЕ. йпрогЕ { изеЕЕЕесЕ, изеЗЕаЕе } Егот 'геасЕ' сопзЕ изеМеззадез = (Еогит) => { сопзЕ [даЕа, зеЕСаЕа] = изеЗЕаЕе([]) сопзЕ [ХоасИпд, зеЕЬоасПпд] = изеЗЕаЕе(Еа1зе) сопзЕ [еггог, зеЕЕггог] = изеЗЕаЕе() изеЕЕЕесЕ(() => { 1еЕ сНдСапсе! = Еа1зе зеЕЕггог(пи!1)
1Г (Гогит) { ;(азупс () => { Ёгу { зеЁЬоасИпд(Ггие) сопзё гезропзе = аыаЁЁ ГеЁсИ(ч/теззадез/${Гогит}') 1Г (!гезропзе.ок) { сопзё ЁехЁ = амаЁЁ гезропзе. ЁехЁ () Ёкгом пек Еггог( 'ЦпаЫе ёо геаб теззадез Гог $(Гогит}: ${ГехГ}' } сопзё Ьобу = аиадЛ гезропзе.дзоп() 1Г (! сИсГСапсе!) { зеЁВаЁа(Ьобу) } } саГсИ (егг) { зеЁЕггог(егг) } ПпаПу { зеЁЬоасИпд(Га1зе) } }) 0 } е!зе { зеЁБаЁа([]) зеЁЬоасИпд(ГаЬзе) } геЁигп () => { сНбСапсе! = Ёгие } }, [Гогит]) геЁигп { даЁа, ЬоасНпд, еггог } } ехрогЁ беГаиЁЁ изеМеззадез Поскольку для правильной обработки обещаний мы используем функцию «аЬЁ, нам нужно обернуть код в довольно неприглядный вызов (азупс о => {...}). Внутри этой оболочки мы сможет задать значения для переменных с1аЁа, ГоасНпд и еггог, по- ка запрос исполняется, завершается и (возможно) возвращает недействительные данные. Все это происходит асинхронно после завершения вызова хука. При изме- нении состояния переменных бага, Гоабшд и еггог хук вызовет повторную отрисовку компонента с этими новыми значениями. Поскольку хук содержит асинхронный код, возврат хука происходит до получения ответа на сетевой запрос. Это означает возможный повторный вызов хука до полу- чения ответа на предыдущий сетевой запрос. Во избежание разрешения сетевых —X- л ответов в неправильном порядке код примера отслеживает, не был ли текущий запрос замещен более поздним запросом, используя для этого переменную сНбСапсе!. На основе этой переменной будет приниматься решение, возвращать ли данные из хука. При этом сам сетевой запрос не будет отменяться. Вопрос от- мены запроса рассматривается в разделе 5.3.
Теперь рассмотрим содержимое файла Арр.]з примера приложения, чтобы разо- браться с использованием этого хука (листинг 5.6). ! Листинг 5.6. Содержимое файла Арр.|в примера приложения ипрогЁ ' . /Арр. С5 3 ' лпрогЁ { изеЗЁаЁе } Ёгот 'геасЁ' ЁтрогЁ изеМеззадез Егот 1./изеМеззадез1 ЕипсЁ1оп Арр() { сопзё [Еогит, зеЁЕогит] = изеЗЁаЁе('паза') сопзё { с!аЁа: теззадез, 1оас11пд: теззадезЪоасНпд, еггог: теззадезЕггог, ) = изеМеззадез(Ёогит) геЁигп ( <сНу с1аззЫате="Арр"> <ЬиЁЁоп опСИск={() => зеЁГогит( 'паза') }>ЫАЗА</ЬиЁЁоп> <ЬиЁЁоп опСИск={() => зеЁЕогит( 'поЁЫаза') }>Ыоё МАЗА</ЬиЁЁоп> {теззадезЕггог ? ( <с!гу с1аззКате="еггог"> ЗотеЁМпд ыепЁ нгопд: <сНу с1аззЫате=”еггог-сопЁепЁз"> {теззадезЕггог.теззаде} </с11у> </сНу> ) : теззадезЬоасИпд ? ( <с!гу с1а55Ыате="1оас11пд">Ьоас11пд_</с11у> ) : теззадез && теззадез.1епдЁк ? ( <с!1> {теззадез.тар{(т) => ( <дЁ> {т. аиЁког} </с!ё> <дб> {т. ЁехЁ} </ск1> ))} <М1> ) : ( 'Ыо теззадез' )} </сИ.у> ) } ехрогЁ деЁаиЁЁ Арр Наше приложение при нажатии кнопки ^А8А или 1Чо1 МА8А отображает в своем окне соответствующий форум. При нажатии кнопки Ко! №А8А сервер возвращает
ошибку 404, в результате чего в окне приложения отображается сообщение об ошибке. На рис. 5.2 показано, как наше приложение отображает этап загрузки сообщений для форума М4&4 (вверху), загруженные сообщения (посередине) и со- общение об ошибке при попытке загрузить данные из отсутствующего форума Мл №8А (внизу). ИАЗА | НоИЧАЗА ЬоасЛпд.. [~ЫА$А [МоГыАВА ~] 8С РоНз сотр1е1е апд а рйсЬ <з ргодгат. Опе №АУО. РАО Опе ВРАУО13 ап аЬог! соп1го1 тоде). АНИиде 1з 2 тЛез. САРСОМ АП шеН а( Нои$(оп. Уои аге дооН а( 1 т!пи№ МАЗА | ГйХМАЗА | БотеНипд шел* жгопд: ЦпаЫе То геай кеззадез Тог поЖаза: ("еггог":"ипкпо«п Топи"} Рис. 5.2. Отображение процесса загрузки сообщений, загруженных сообщений и сообщения об ошибке Хук изеМеззадез также обрабатывает и возвращаемые сервером ошибки, как пока- зано на рис. 5.3. | МАЗА [ И АЗА | 5оте№тд шеп( шгопд. МпаЫе То геа<1 пеззадез Тог паза: {"еггог":"0орз1е!"} Рис. 5.3. Компонент может отображать возвращаемые сервером сообщения об ошибках Обсуждение При создании приложения может возникнуть соблазн разрабатывать код без дополнительных функций, неявно предполагая, что все будет работать должным образом без нештатных ситуаций. Но все же полезно уделить некоторое время раз- работке кода для обработки ошибок, а также для отображения процесса загрузки данных. Это сделает ваше приложение более удобным в использовании и облегчит обнаружение медленных служб и ошибок.
Можно также рассмотреть возможность совмещения этого рецепта с рецептом раз- дела 4.1, чтобы пользователям было легче описать, что произошло. Исходный код данного рецепта можно загрузить на веб-сайте бйНиЬ по адресу Ь«р8://огеП.1у/Т6М6д. 5.2. Автоматическое обновление посредством счетчиков состояния ЗАДАЧА Сетевым службам часто требуется взаимодействовать друг с другом. Возьмем, на- пример, приложение форума, рассмотренное в разделе 5.1. Мы хотим, чтобы спи- сок сообщений обновлялся автоматически при каждом добавлении в него нового сообщения. Для этого приложения мы уже создали в том разделе хук иземеззадез, содержащий весь требуемый код для чтения сообщений форума. Теперь мы добавим в это приложение форму для отправки сообщений на сервер (листинг 5.7). Листинг 5.7. Форма дляотправкисообщенийна сервер СОПЗТ ( дата: теззадез, 1оасИпд: теззадезЬоасИпд, еггог: теззадезЕггог, } = изеМеззадез('паза') сопз! [ТехТ, зеТТехТ] = изеЗТаТеО сопз! [аиТИог, зеТАиТИог] = изеЗТаТеО сопзТ [сгеаТеМеззадеЕггог, зеТСгеаТеМеззадеЕггог] = изеЗТаТеО // ОТЪег соде Неге... <1при1 Туре="ТехТ" уа1ие={аиТЪог} р1асеЬо1дег=,,АиТког" опСИапде={(еуТ) => зеТАиТког(еур.ТагдеТ.уа1ие)} /> <Техгагеа уа1ие={ТехТ) р1асеЪо1с1ег="Меззаде" опСИапде={ (еуЁ) => зеТТехк (еуТ. ТагдеТ.уа1ие)} /> <ЬиТТоп опСИск={ азупс () => { кгу { аиахЁ [собе То розТ теззаде Неге]
зебТехб ('') зе^АиЁЪог(11) } са^ск (егг) { зеЁСгеаЁеМеззадеЕггог(егг) } )} > РозЪ </Ьиб1оп> Но эта форма имеет один недостаток: новые сообщения не отображаются в списке, пока страница не будет обновлена вручную (рис. 5.4). РАО гапдвг, 1 м!1»в. 3-4 пси, у<1ос1^у 1в 2,195 Гмг ре г авсопй. И1 аг» ЕЪгоидН ЕЬе гед!ол оГ кдхАвиш 4ула*1с пй«. В сЛоып гайда, 12 ша!» Н1.дК, V в 1 ос 11. у 4,000 рас ласопд. 1±»0 5С Йо11® сотр1е1е апд а ргХсЪ и ргодгат. Опе ВЙАУО. РАО Опе ВЙАУО к ап аЬоП согйго! тойеГ АЙйиде !з 2 тйез САРСОМ АН 15 иеН а* Нои$(оп. Уои аге доод а( 1 т)пШе 5С ЙоНа согерИе агк! а рйсЬ 1а ргодгат Опе ВЙАУО. РАО Опе ВЯАУО15 ап аЬоП соп(гЫ поде). АИШИ 1$ 2 тпНе®. САРСОМ АН и иеН а1 Ноиа(оп. Уои аге доек! а< 1 т1тйе. Л Рис. 5.4. Отправленные сообщения не отображаются в списке сообщений Итак, как нам автоматически загружать сообщения с сервера после сохранения на нем нового сообщения? РЕШЕНИЕ Активирование операции обновления данных будем выполнять при помощи счет- чика состояний. Счетчик состояний — это просто постоянно нарастающее число. Текущее значение счетчика не важно, нужно лишь изменять его каждый раз, когда требуется перезагрузить данные: сопзб [збабеУегзгоп, зебБбабеУегзгоп] = изе81:абе(0) Счетчик состояний можно рассматривать как представление воспринимаемой нами версии данных на сервере. Когда мы делаем что-либо, что может изменить состоя- ние сервера, мы обновляем счетчик состояний, чтобы отображать это изменение: // собе бо розб а пеы теззаде Неге (здесь размещается код для отправки нового сообщения) зебЗбабеУегзгоп((V) => V + 1)
! Обратите внимание на то, что мы инкрементируем значение переменной зТаТеУегзЁоп при помощи функции, а не простым оператором зеЁ5ЁаЁеУегз1оп(зЁаЁеУегз1оп + 1). Если новое значение зависит от старого, то значение состояния всегда нужно об- I —X новлять при помощи функции, поскольку состояния в Реас! устанавливаются асин- хронно. Поэтому если исполнить оператор зеЁЗЁаЁеУегзюп (зЁаЁеУегзюп + 1) дважды в быстрой последовательности, то значение зтатеУегзЁоп может не изме- ниться между этими двумя операциями и мы можем пропустить его инкремента- цию. Код, считывающий текущий набор сообщений, заключен в хук изеЕ^Гест, который мы можем заставить выполнять возврат, сделав его зависимым от значения пере- менной зТаЁе7а1ие (ЛИСТИНГ 5.8). Листинг 5.8. Принуждаем возврат хука ивеЕЕГесЪ изеЕ1ЁесЁ(() => { зеТЕггог(пи11) (Ёогит) { // Сос1е ёо геад /теззадез/:Ёогит } е!зе { зеЁОаГа([]) зеЁЬоасИпд(Ёа1зе) } ), [Гогит, зЁаЁеУегзтоп]) Таким образом, если изменится значение переменной 1огит или переменной зЁаЁе7егзюп, хук автоматически перезагрузит сообщения (рис. 5.5). Вот таким будет наш подход. Теперь нам нужно определить, куда мы вставим этот код. В листинге 5.9 приведена предыдущая версия компонента, которая только счи- тывает сообщения. 1^0 1 А|ЛЬя | 0©нл } нИвз МШий» !!» по*. ии1ос1Ьу 2,1 >5 рвг явсога], аг» (ЪгоидК ЬЬи дугяжАс лс*. 1 гдпдв, 12 11» 4,000 Ем1 рег звсспд. М₽53*дв ГРоч] 8С ЯоИя сотрШе алд а рйсЬ в ргодгат. Опе ВРАУО. РАО Опе ВРАУО15 ап аЬогТ согйго! пкх1е! АПЙиде в 2 тПев САРСОМ АЛ <5 кеК а1 Носв1оп. Уои аге дскх! а!1 ггйгиЛе ~РжГ| 5С Ро11з сотр1е(е алд а рЛсЬ в ргодгат. Оле ВРАУО. РАО Опе ВЙАУО15 ап аЬог! соп(го1 тос1е1. АЖ1и<1е !з 2 тЛе$. САРСОМ АП в неП а! Нои$!оп. Той аге доо<1 а!1 т!пи№. РАО Оожп галде, 1 тйез. АМиде 3-4 т (е по», уе1осг1у в 2,1951ее1 рег зесопд. № аге (ЬгоидК №е гедюп й тах1тит дугмтю ргезэиге поп 8 тйе5 скжп галде, 12 тйек Ь>дК, №е4осЛу 4,000 Гее! рег еесопд. Рис. 5.5. Отправка нового сообщения вызывает обновление списка сообщений
Листинг 5.9. Версия компонента, только считывающая сообщения ППрОГГ './Арр. СЗЗ' хтрогГ { изеЕГаГе } Ггот 'геасГ* ГтрогГ иземеззадез Ггот './изеМеззадез1 ГипсПоп Арр() { сопзГ [Гогит, зеГЕогит] = изеЗГаГе('паза*) сопзГ { баГа: теззадез, ГоасПпд: теззадезЬоасНпд, еггог: теззадезЕггог, } = иземеззадез(Гогит) геГигп ( «Цу с1аззМате="Арр"> <ЬиГГоп опСИск={() => зеГЕогит( 'паза*) }>МА5А</ЬиГГоп> <ЬиГГоп опСПск={() => зеГЕогит( 'поГЫаза*) }>ЫоГ ЫАЗА</ЬиГГоп> {теззадезЕггог ? ( <с!1у с1аззЫате="еггог"> ЗотеГкГпд мепГ ыгопд: <сНу с1аззНате="еггог-сопГепГз"> {теззадезЕггог.теззаде} </(±1У> </<±1у> ) : теззадезЬоасИпд ? ( «Ну с1аззЫате="1оа(11пд',>Ьоад1пд.. .</сИу> ) : теззадез && теззадез.ГепдГк ? ( <б1> {теззадез.тар((т) => ( <дГ >{т.аиГког}</дГ> <с1с1> {т. ГехГ} </с!с1> </> ))} </с!1> ) : ( ’Ыо теззадез* )} </с!1У> ) ) ехрогГ с!еГаи1Г Арр К этому компоненту мы добавим нашу новую форму. В этот же компонент можно было бы также включить код для работы в сети и код счетчика состояний. Но тогда код отправки сообщений оказался бы в этом компоненте, а код для их считыва- ния — в хуке иземеззадез. Но будет лучше, чтобы весь код для работы в сети нахо-
дился в хуке. Таким образом, не только наш компонент будет аккуратнее оформ- лен, но и код для сетевых операций будет легче использовать повторно. В листинге 5.10 приведен код новой версии хука изеМеззадез, переименованного В изеГогиш1. Листинг 5.10. Код хука ивеГогт нпрогЁ { изеСаИЬаск, изеЕЁЁесЁ, изеЗЁаЁе } Ггош 'геасЁ' сопзё изеГогиш = (Ёогит) => { сопзё [даЁа, зеЁБаЁа] = изе8ЁаЁе([]) сопзё [1оа<±шд, зеЁЬоасЛпд] = изеЗЁаЁе(Ёа1зе) сопзё [еггог, зеЁЕггог] = изеЗЁаЁе() сопзё [сгеаЁтпд, зеЁСгеаЁЁпд] = изеЗЁаЁе(Га1зе) сопзё [зЁаЁеУегз1оп, зеЁЗЁаЁеУегзгоп] = изеЗЁаЁе(0) сопзё сгеаЁе = изеСаИЬаск ( азупс (теззаде) => { Ёгу { зеЁСгеаЁЁпд(Ёгие) сопзё гезропзе = амаЁЁ 1еЁсИ('/теззадез/${Согит}', { теЁЬод: 1РОЗТ1, Ьоду: ^ОЫ. зЁгЁпдИу(теззаде), Ъеадегз: { 1 СопЁепЁ-Ёуре1: 1 аррИсаЁ1оп/]зоп; сЬагзеЁ=иТЕ-81, ), }) И (!гезропзе.ок) { сопзё ЁехЁ = аиаЁЁ гезропзе.ЁехЁ() ёЬгом пем Еггог( 'СпаЫе Ёо сгеаЁе а ${€огит) теззаде: ${ЁехЁ}' ) зеЁЗЁаЁеУегзгоп((V) -> V + 1) } ИпаИу ( зеЁСгеаЁхпд(Ёа1зе) ) }, [Ёогит] ) изеЕИесЁ(() => ( 1еЁ с11с1Сапсе1 = Ёа1зе зеЁЕггог (пиИ) 1 Переименование хука объясняется тем, что он теперь используется не только для чтения списка сообще- ний, но для работы с форумом в общем. В дальнейшем к нему также можно добавить функции для удаления и редактирования сообщений, а также для пометки их флагом.
1Г ({гогит) { ;(азупс () => { Ггу ( зеЁЬоасИпд(Ёгие) сопзё гезропзе = амахЁ ГеЁсЬ('/теззадез/?{Гогит}') 1Г ((гезропзе.ок) { сопзё ЁехЁ = аиаЬЁ гезропзе.ЁехЁ() ёЬгок пек Еггог( 'ЬпаЫе ёо геас1 теззадез Гог $(Гогит): ${ГехЁ}' ) } сопзё Ьойу = акаГЁ гезропзе.]зоп() 1Г {!сИс!Сапсе1) { зеЁЬаЁа(Ьоду) ) } саЁск (егг) { зеЁЕггог(егг) } ГЬпаНу { зеЁЬоасИпд(Га1зе) } }) О } е!зе { зеЁЬаЁа([]) зеЁЬоасИпд (Га1зе) ) геЁигп () => { сНдСапсе! = Ёгие ) }, [Гогит, зЁаГеУегзГоп]) геЁигп { даЁа, ЬоасНпд, еггог, сгеаЁе, сгеаЁЁпд } } ехрогЁ деГаи1Ё изеЕогит Теперь в хуке изеЕогит мы создаем функцию сгеаЁе, после чего возвращаем его ком- поненту вместе с разными другими составляющими состояния. Обратите внимание на то, что эту функцию сгеаЁе мы заключаем в хук изеСаНЬаск, что означает, что мы не будем создавать новую версию этой функции, если только это не потребуется для того, чтобы создать данные для другого значения Гогит. 4"^ Будьте осторожны при создании функций внутри хуков и компонентов. Средство Реас! часто инициирует повторную отрисовку при создании нового объекта функ- ции, даже если данная функции делает то же самое, что и предыдущая версия. При вызове функции сгеаЁе она отправляет новое сообщение на форум, а затем обновляет значение переменной згагеУегзЬоп, что автоматически принудит хук счи- тать сообщения с сервера. Обратите также внимание на присутствие булевой пере-
менной сгеаНпд, которая имеет значение Ёгие, пока сетевой код отправляет сообще- ние на сервер. Эта переменная служит для отключения кнопки РО8Т. Но мы не следим за тем, возникают ли какие-либо ошибки в функции сгеаЁе. Поче- му нет? В конце концов, мы делаем это при чтении данных с сервера. Потому что при изменении данных на сервере часто требуется более высокий уровень контроля над обработкой ошибок, чем при простом считывании данных. В примере прило- жения при отправке сообщения на сервер мы очищаем форму сообщений. В случае возникновения ошибки мы хотим, чтобы сообщение не удалялось из формы. Теперь рассмотрим код, вызывающий наш хук (листинг 5.11). Листинг 5.11. Код для вызова хука изеЕогш йпрогЁ './Арр.сзз' нпрогЁ { изеЗЁаЁе } Ёгош 'геасЁ' нпрогЁ изеГогиш Ёгот '. /изеЕогшп' ГипсЁЁоп Арр() { сопзё { с1аЁа: теззадез, 1оасИпд: теззадезЬоасНпд, еггог: теззадезЕггог, сгеаЁе: сгеаЁеМеззаде, сгеаЁЁпд: сгеаЁЁпдМеззаде, } = изеЕогшп ('паза') сопзё [ЁехЁ, зеЁТехЁ] = изе5ЁаЁе() сопзё [аиЁЬог, зеЁАиЁИог] = изе2ЁаЁе() сопзё [сгеаЁеМеззадеЕггог, зеЁСгеаЁеМеззадеЕггог] = изеЗЁаЁеО геЁигп ( <сИу с1аззМате="Арр"> <1приЁ Ёуре="ЁехЁ" Vа1ие={аиЁ^1о^} р1асеИо1дег="АиЁЪог" опСЪапде={ (еУЁ) => зеЁАиЁЪог (еуЁ.ЁагдеЁ^а!ие)} /> <ЁехЁагеа Vа1ие={ЁеxЁ} р1асеЬо!<1ег="Меззаде" опСИапде={(еуЁ) => зеЁТехЁ(еVЁ.Ёа^деЁ.Vа1ие)} /> <ЬиЁЁоп опС11ск={азупс () => { Ёгу { амаЁЁ сгеаЁеМеззаде({ аиЁког, ЁехЁ }) зеЁТехЁ('') зеЁАиЁког(' ')
} са1сЬ (егг) { зе^Сгеа^еМеззадеЕггог(егг) } Н сИзаЫес1= {сгеаЁгпдМеззаде} Роз!: </ЬиЫ:оп> {сгеа^еМеззадеЕггог ? ( ссЦ'у с1аззКате=''еггог"> ОпаЫе сгеаЁе теззаде <<Иу с1аззКате="еггог-соп!:епГз"> {сгеа^еМеззадеЕггог.теззаде} </с11Л7> </сНу> ) : пиИ) {теззадезЕггог ? ( <с!гу с1аззКате="еггог"> ЗотеМппд ыегЛ мгопд: <сПу с1аззКате="еггог-соп!:еп11з"> {теззадезЕггог.теззаде) </с11у> </с11У7> ) : теззадезЪоасктд ? ( с1аззКате=''1оа<11пд">Ьоас11пд.. .</с(гу> ) : теззадез && теззадез.ХепдЁк ? ( <с11> {теззадез.тар((т) => ( <сН> {т. аи!±ог} </сК:> <ск1> {т. 1:ех!:} </скЗ> </> )) ) </Й1> ) : ( 'Ко теззадез' )} </сИл/> ) ехрогЁ бе^аиИ: Арр Подробности считывания и записи сообщений скрыты внутри хука изеГогит. Для присвоения функции сгеа^е переменной сгеа^емеззаде мы используем деструктури- рование объекта. При вызове функции сгеа^еМеззаде она не только отправит сооб-
щение на сервер, но также автоматически прочитает новые сообщения форума и обновит содержимое окна соответствующим образом (рис. 5.6). Теперь наш хук не просто является средством для считывания данных с сервера, а становится службой для управления самим форумом. Опе ВНАУО в ап аЪогС соп*го1 тойв!. АКйибе к 2 тНеэ. Опе ВЯАУО и ап аЬоП соп*го1 тобе!. АННибе к 2 пи1ез. САКОМ А11 гз иеН а* Ноиз*оп. Уои аге дсхх! а( 1 т1пи*е. САРСОМ А11 И неИ а* Ноийоп Уои аге доод а* 1 тинЛе РАО Оошп гапде, 1 тйаз. АКйибе 3-4 тйез псе*, ие)осйу <з 2,195 (ее* рег зесопд. У/е аге *Ьгоид*1 Иге гедюп о* таютит Иупапис ргеззиге гнж. 8 т1ез Исжп гапде, 12 тИез Нчк *е!осйу 4,000 (ее* рег зесопд. Рис. 5.6. Отправление нового сообщения на сервер и автоматическое обновление содержимого окна Обсуждение Будьте осторожны, если вы намереваетесь применять описанный подход для со- хранения данных на сервере посредством одного компонента, а затем считывать их с сервера при помощи другого. Экземпляры отдельных хуков будут использовать отдельные счетчики состояний, и сохранение данных в одном компоненте не вызо- вет автоматического чтения данных в другом компоненте. Если вы хотите разде- лить код для сохранения и чтения данных между отдельными компонентами, вызы- вайте специальный хук в каком-либо общем родительском компоненте, передайте данные и отправьте функции дочерним компонентам, для которых они требуются. Если требуется выполнять регулярные опросы сетевой службы, рассмотрите воз- можность создания часов и сделайте код для сетевых операций зависимым от те- кущего значения часов, подобно тому, как код в этом рецепте зависит от счетчика состояний2. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу Ьир8://огеП.1у/кпуС5. 2 См. раздел 3.4.
5.3. Отмена сетевых запросов посредством маркеров ЗАДАЧА Рассмотрим следующее неверно функционирующее приложение, выполняющее поиск городов. Когда пользователь начинает вводить название города в поле поис- ка, в окне приложения начинает отображаться список совпадающих городов. На- пример, при вводе букв "С... Н... I. О...” в таблице результатов отображаются совпа- дающие города. Но затем, после кратковременной правильной работы, в таблице результатов отображается более длинный список городов, содержащий неправиль- ные результаты, например, ХУюЬйа ГаПз (рис. 5.7). 1^ I СЙу 81Лв ОеЬоК МгсНдап Сгапд Нар№ МюЫдап Наглел МгсЫдап 51егйпд НадЫк М1сН|дап Алл АгЬог МюЫдап Ьап&пд МгсЫдап С11п1ол МгсЬдап СНу 8Ыа СЬюадо 1№по& [>е(гой МюМдап №сМа Капзаз Бгапд НарчЬ М|сЬ|дап 7/аггеп М1с№дап 51егКпд НадМк М!сГйдал Апл АгЬог М<сЫдал Ь п$1пд МшЫдал МсЬЛа Ра11з Техаз СКюо С1«пГоп СаОТоггна М1с№дап Рис. 5.7. На первых порах поиск работает должным образом (слева), но затем выводится список, содержащий неправильные города Проблема состоит в том, что система отправляет новый сетевой запрос при вводе пользователем каждой новой буквы. Но исполнение сетевых запросов занимает разное время. В данном примере выполнение сетевого запроса на поиск строки "СНГ заняло на пару секунд больше, чем на поиске строки "СН16". Вследствие этого результаты поиска строки "СНГ’ были возвращены после результатов поиска строки "СН1О". Как же тогда можно предотвратить возврат результатов асинхронных сетевых запросов в неправильном порядке? РЕШЕНИЕ При отправке сетевому серверу многократных запросов сет старые запросы можно отменить, прежде чем отправлять новые. Таким образом, мы никогда не получим результаты запросов в неправильном порядке, поскольку одновременно к службе будет обращаться только один запрос. Для этого рецепта нам потребуется сетевая библиотека Ахюз. Установим ее как обычно: $ прт 1пзРа11 ах1оз
Библиотека Ахюз служит оболочкой для нативной функции ГеЪсЪ и предоставляет возможность отменять сетевые запросы, используя маркеры. Реализация библиоте- ки Ахюз основана на проектной инициативе отменяемых обещаний, предложенной организацией ЕСМА (Ы1р5://огеН.1у/]Л4ЬБ'). Начнем с исследования нашего проблемного кода (листинг 5.12). Код для сетевых операций заключен в специализированный хук3. Листинг 5.12. Код проблемного приложения ЁтрогЁ { изеЕЁГесЁ, изеЗЁаЁе } Г г от 1геасЁ1 ЁтрогЁ ах1оз Ёгот 'ахЁоз' сопзё изеЗеагск = (Ёегтз) => { сопзё [с1аЪа, зеЁБаЁа] = изе5ЁаЁе([]) сопзё [1оасИпд, зеЁЬоасНпд] = изеЗЁаЁе(Га1зе) сопзё [еггог, зеЁЕггог] = изеЗЁаЁе() изеЕГ1есЁ(() => { зеЁЕггог (пиИ) 1Ё (Ёегтз) { ;(азупс О => { Ёгу { зеЁЬоасНпд (Ёгие) сопзё гезропзе = амаЁЁ ахгоз.деЁ('/зеагск', { рагатз: { Ёегтз }, ]) зеЁОаЁа(гезропзе.баЁа) } саЁск (егг) { зеЁЕггог(егг) ] Г1па11у { зеЁЬоасЦпд(1а1зе) } е1зе { зеЁВаЁа([]) зеЁЬоасЧпд(Га1зе) } ), [Ёегтз]) геЁигп { даЁа, 1оасИпд, еггог } ) ехрогЁ (ЗеЁаиЁЁ изеЗеагсЪ Искомая строка содержится в параметре Ёегтз. Причина проблемы следующая. Сначала код подал сетевой запрос к /зеагсЬ для поиска строки "СНГ. 3 Сравните этот код с кодом в разделе 5.1, в котором используется команда ГеЁсК
Пока этот запрос находился в процессе исполнения, был сделан другой запрос поиска, на этот раз строки "СНЮ". Выполнение первого запроса заняло больше времени, чем второго, что и вызвало ошибку. Для устранения этой проблемы мы воспользуемся маркером отмены библиотеки Ахюз. Если пометить запрос маркером, то в дальнейшем запрос можно отменить при помощи этого маркера. Браузер завершит исполнение запроса, и он пропадет без следа. Чтобы использовать маркер, сначала нужно создать для него источник: сопзё зоигсе = ахХоз.СапсеХТокеп.зоигсе() Источник маркера можно сравнить с пультом дистанционного управления для сетевого запроса. После подключения сетевого запроса к источнику последнему можно дать указание отменить сетевой запрос. Источник ассоциируется с запросом посредством команды зоигсе.хокеп, как показано в листинге 5.13. Листинг 5.13. Ассоциирование источника маркера с запросом сопзё гезропзе = амаХЁ ахХоз.деЁ ('/зеагсЬ', { рагатз: { Ёеппз }, сапсеХТокеп: зоигсе.Ёокеп, Система Ахюз запомнит, какой маркер с каким сетевым запросом связан. Отмена сетевого запроса осуществляется следующим образом: зоигсе.сапсеХ(’ахХоз гедиезЁ сапсеХед’) Но отменять запрос обязательно нужно только тогда, когда мы отправляем новый запрос. К счастью, наш сетевой вызов заключен в оболочку хука изеЕХХесЁ, который обладает одной полезной возможностью. В частности, при возвращении функции, которая отменяет текущий запрос, эта функция будет исполнена непосредственно перед следующим исполнением хука изеЕХХесх. Таким образом, при возврате функ- ции, отменяющей текущий сетевой запрос, мы автоматически отменяем старый сетевой запрос перед каждым исполнением нового запроса4. В листинге 5.14 при- ведена обновленная версия специализированного хука. Листинг 5.14. Обновленная версия специализированного хука ХтрогЁ { изеЕПесЁ, изеЗЁаЁе } Ггот 'геасЁ' ХтрогХ ахХоз Хгот 'ахХоз' сопзё изеСапсеХаЬХеЗеагсЬ = (Ёегтз) => { сопзё [даЁа, зеЁБаЁа] = изеЗЁаЁе([]) сопзё [ХоасНпд, зеЁЬоасИпд] = изеЗЁаЁе(ХаХзе) сопзё [еггог, зеЁЕггог] = изеЗЁаЁеО 4 Если предыдущий сетевой запрос завершен, его отмена не повлечет за собой никаких последствий.
изеЕЁЁесЁЦ) => ( зеЁЕггог (пиХХ) 1Х (Ёегтз) { сопзё зоигсе = ахХоз.СапсеХТокеп.зоигсе() ;(азупс О => { *гу { зеЁЬоасИпд (Ёгие) сопзё гезропзе = амагЁ ахХоз.деЁ('/зеагск', { рагатз: { Ёегтз }, сапсеХТокеп: зоигсе.Ёокеп, }) зеЁБаЁа (гезропзе.сХаЁа) } саЁск (егг) { зеЁЕггог(егг) } ЛпаХХу { зеЁЬоасИпд(ХаХзе) ) }) О геЁигп () => { зоигсе.сапсеХ('ахгоз гедиезЁ сапсеХХесГ ) } } еХзе { зеЁБаЁа([]) зеЁЬоасИпд(ХаХзе) } }, [Ёегтз]) геЁигп { даЁа, ХоасНпд, еггог } ) ехрогЁ (ХеЁаиХЁ изеСапсеХаЬХеБеагсЪ Обсуждение Этот подход к отмене сетевых запросов следует применять только при доступе к идемпотентным службам. На практике это означает, что его следует использовать для запросов сет, где вас интересуют только самые последние результаты. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу Ы1р8://огеИ.1у/аР]5§. 5.4. Сетевые вызовы посредством Ней их ЗАДАЧА Библиотека Кебих предоставляет возможность централизованного управления состоянием приложения5. Изменения состояния приложения осуществляются от- 5 При первом знакомстве эта библиотека может выглядеть довольно сложной. Дополнительные рецепты с использованием библиотеки Кебих приведены в главе 3.
правкой команд (называющихся действиями — асйопх), которые захватываются и обрабатываются функциями 1ауа8спр1, называющимися преобразователями (или редюсерами — гедисега). Библиотека Кедих пользуется популярностью среди про- граммистов Кеас(, поскольку она предоставляет возможность отделить код логики управления состояниям от кода компонента. Команды Кедих исполняются асин- хронно, но в строгом порядке, что позволяет создавать большие и сложные прило- жения с использованием Кедих, которые одновременно и эффективные, и стабиль- ные. Было бы просто замечательно, если бы эти мощные возможности Кедих можно было применить для управления всеми нашими сетевыми запросами. Мы могли бы отправлять действия, указывающие, например "Прочитать результаты последнего поиска", а Кедих мог бы подать соответствующий сетевой запрос, а затем обновить должным образом общее состояние. Но, чтобы обеспечить стабильность работы кода Кедих, преобразующие функции должны удовлетворять нескольким довольно строгим критериям, одним из которых является отсутствие побочных эффектов. Это означает полный запрет на подачу сетевых запросов изнутри преобразователя. Но если нам нельзя осуществлять сетевые запросы изнутри функций-преобразо- вателей, то как можно сконфигурировать Кедих для взаимодействия с сетью? РЕШЕНИЕ В приложениях Кеас1, использующих возможности библиотеки Кес1их, компоненты публикуют (отправляют) действия, а преобразователи реагируют на действия обновлением общего состояния (рис. 5.8). Рис. 5.8. Обновление общего состояния посредством преобразователей Кебих Если требуется создавать действия с дополнительными эффектами, нужно исполь- зовать промежуточное программное обеспечение Кедих. Промежуточное ПО при- нимает действия перед тем, как Кедих отправляет их преобразователям, и может преобразовывать или удалять их либо создавать новые действия. Самое главное, коду промежуточного ПО КеНих разрешается иметь побочные эффекты. Это озна- чает, что если компонент отправляет действие, указывающее "Найти эту строку", мы можем создать промежуточное ПО, которое принимает это действие, генериру- ет сетевой вызов, а затем преобразует ответ в новое действие "Сохранить эти ре-
зультаты поиска”. На рис. 5.9 приводится блок-схема, демонстрирующая принцип работы промежуточного программного обеспечения Кедих. Создадим теперь промежуточное ПО для перехвата действия типа зеаксн и исполь- зуем его для создания сетевой службы. Рис. 5.9. Реализация сетевых вызовов посредством промежуточного ПО Кес!их Получив ответ на сетевой запрос с результатами поиска, создадим новое действие типа 5Едксн_КЕ5иьт8, которое даст возможность сохранения результатов поиска в общем состоянии КеНих. Объект нашего действия будет выглядеть, как показано в листинге 5.15. Листинг 5.15. Код объекта действия 8Ы®сн { "Ёуре": "ЗЕАКСН", "рау1оасГ: "Зоте зеагск ЁехЁ" ) А в листинге 5.16 приведен код из файла ахю8М1с1с11е\уаг.]8, при помощи которого мы будем перехватывать действия зеансн. Листинг 5.16. Код для перехвата действии веавсн нпрогЁ ах1О5 Егот 'ахЁоз' сопзё ахЕозМЕскНеыаге = (зЁоге) => (пехЁ) => (асЁ1оп) => { И (асЁЁоп.Ёуре === 'ЗЕАКСН') { сопзё Ёегтз = асЁ1Оп.рау1оад 1Е (Ёегтз) { ;(азупс О => { Ёгу ( зЁоге. сНзраЁсИ({ Ёуре: 'ЗЕАКСН_КЕЗСЕТЗ’, рау!оас1: { ЁоайЁпд: Ёгие, с!аЁа: пиИ,
еггог: пи11, }, }) сопзк гезропзе = аыазЛ аххоз. де!: (' /зеагск', { рагашз: { кеппз }, }) зЬоге. сНзраТюН ({ куре: 'ЗЕАКСН_КЕЗиЬТ5', рау1оа<5: { 1оасИпд: 1а1зе, еггог: пи11, бака: гезропзе.бака, }, }) } сакск (егг) ( зкоге.сНзракск({ куре: 'ЗЕАКСН_КЕЗиЬТЗ', рау1оас1: { 1оасНпд: 1а1зе, еггог: егг, бака: пи11, }, }) } }) О } } гекигп пехк(аскхоп) } ехрогк с!екаи1к аххозШскИеиаге Выражение функции для промежуточного ПО Кедих может быть не совсем понят- ным. Его можно рассматривать как функцию, которая в качестве аргументов при- нимает хранилище, действие и другую функцию, называющуюся пехк, которая мо- жет перенаправить действие остальным частям Кедих. В коде в листинге 5.16 выполняется проверка, не появилось ли действие типа зеаксн. Если да, то выполняется сетевой вызов. В противном случае исполняется команда пехк (асМоп), которая передаст это действие любому другому коду, про- явившему к нему интерес. Новое действие ЗЕАксн кЕзиьтз можно создавать при отправке сетевого вызова, получении данных или обнаружении ошибок (листинг 5.17). Листинг 5.17. Код действия типа зеакст^кезцьтв зкоге. сЧзракск({ Суре: '8ЕАКСН_КЕЗиЬТЗ',
рау1оас1: { 1оасИпд: ..., еггог: даЁа: ... }, }) Наше новое действие содержит следующую полезную нагрузку: ♦ булеву переменную 1оасИпд, значение которой равно егие, пока исполняется сетевой запрос; ♦ объект баса, содержащий ответ от сервера; ♦ объект еггог, содержащий информацию о любой случившейся ошибке6. Теперь можно создать преобразователь, который сохранит действие зеансн кезоьтз в общем состоянии (листинг 5.18). ^Листинг 5.18. Преобразователь для сохранения действия зеаксн ке8ЦЪтз сопз1: гебисег = (зЪа^е, ас±1оп) => { И (асЫоп.Еуре === 15ЕАВСН_КЕЗиЬТЗ') { геРигп { ...з^аЁе, зеагсИКези1Лз: { .. .асЫоп.рау1оас1 }, } } ге^игп { ...зСа1:е } } ехрогЁ де^аиК: гесЗисег Нам также нужно зарегистрировать наше промежуточное ПО при создании храни- лища Кедих, используя для этого функцию Кес1их арр1уМ1сШеиаге. В нашем примере это делается в файле Арр.)8 (листинг 5.10). Листинг 5.19. Регистрация промежуточного ПО ипрогЁ { РгсллШег } Егот 1 геасЕ-гес!их1 ЕтрогЕ { сгеаЕеЗЕоге, арр1уМ1с!с11еиаге } Егот ’гедих' Етро г Е './Арр.сзз' ЕтрогЕ гес!исег Егот 1 ./гесЕисег' ЕтрогЕ Зеагсй Егот './ЗеагсИ' ХтрогЕ ахЕозМЕскЦеиаге Егот 1. /ахЕозМЕс!д1енаге' сопзЕ зЕоге = сгеаЕеЗЕоге (гебисег, арр!уМЕс1с11еиаге (ахЕозМЕскЛеыаге)) 6 Дня простоты мы просто сохраняем весь объект. В настоящем приложении следует обеспечить, чтобы объект еггог содержал только сериализуемый текст.
ГипсРХоп Арр () { геРигп ( <сИ\л сХаззМате="Арр"> <Ргсд71с1ег зРоге={зРоге}> <3еагск /> </Ргоуз.с1ег> </<±ГУ> ) } ехрогр деХаиХР Арр Наконец, мы можем собрать все в компоненте ЗеагсЬ., который будет отправлять запрос поиска, а затем считывать результаты посредством селектора Кес1их (лис- тинг 5.20). Листинг 5.20. Объединение всех составляющих приложения в компоненте &мшзН Хтрогр '. /Арр. сзз' ХшрогР { изеЗЁаЁе } Хгот 'геасР' ХтрогР { изеОХзраРсЪ, изеЗеХесРог } Ргот ' геасР-гебих' сопзР ЗеагсЪ = ()=>{ сопзР [Регтз, зеРТегтз] = изеЗРаРе() сопзР { дара: гезиХРз, еггог, ХоасЧпд, } = изеЗеХесРог((зРаРе) => зРаРе.зеагскНезиХРз || {}) сопзР сЦзраРсН = изеСХзраРсИ() геРигп ( «XIV сХаззЫате="Арр"> <1приР рХасекоХ6ег="5еагсЬ..." Руре="РехР" VаXие={Регтз} опСЬапде={(е) => { зеРТегтз(е.РагдеР.уаХие) сИзраРсЪ({ Руре: 'ЗЕАКСН', рауХоаё: е.РагдеР.уаХие, }) }} /> {еггог ? ( <р>Еггог: {еггог.теззаде}</р> ) : ХоасЦпд ? ( <р>ЬоадХпд...</р>
) : гези1кз && гезиИз. 1епдкк ? ( <каЫе> <ХЬеас1> <кг> <кк>С1ку</кк> <кк>Зкаке</кЪ> </кг> </Г.кеаб> {гези1кз.шар((г) => ( <кг> <кб> {г. паше} </кд> <кс!>{г .зкаке)</кд> </кг> ))} </каЫе> ) : ( <р>Ко гези1кз</р> )} </сИу> ) } ехрогк 6екаи1к Зеагск На рис. 5.10 показаны разные стадии исполнения этого приложения. 13------- ЬоасКпд... Сйу 81а1е СЫсадо 1Шпсн5 Ое1гоН Мк±11дап №с№а Кап$а5 Еггог: Недоев) ЬЛес) у/ИЬ 5*а№5 соде 500 БгапН Каркав МюЫдап М/аггеп МгсЫдап ЗДегНпд Не<дЫ5 М1сЫдап Алл АгЬог Ьапзгпд МскЛа Еа11к СЫсо СНШоп МгсГйдап М1сЫдап Техаз Са№от!а М1сЫдап Рис. 5.10. Окно приложения при загрузке данных (слева), с загруженными данными (посередине) и при ошибке (справа) Обсуждение Преобразователи Кедих всегда обрабатывают действия в строгом порядке их от- правки. То же самое относится и к сетевым запросам, генерируемым промежуточ- ным ПО. Но если создавать большое количество сетевых запросов в быстрой
последовательности друг за другом, то может оказаться, что ответы на них возвра- тятся в другом порядке. Если это обстоятельство повышает вероятность возник- новения ошибок, то следует рассмотреть возможность использования маркеров отмены7. Также может быть желательным рассмотреть возможность перемещения всего кода Кедих изеДхзраъсЬ () /изеЗеТесьог о из компонентов в специализированные хуки, что даст более гибкую архитектуру вследствие отделения служебного уровня от кода компонентов. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬЛря://огей.1у/У1дЕБ'. 5.5. Подключение к СгарЬОЬ ЗАДАЧА Язык запросов СгарЬрЬ замечательно подходит для создания АР1-интерфейсов. Для тех из вас, кто в течение некоторого времени пользовался службами КЕ8Т, не- которые возможности ОгарЬС^Ь будут выглядеть странными (или даже святотатст- вующими), но, поработав на нескольких проектах с использованием ОгарЬС^Ь, мы, безусловно, можем порекомендовать вам рассмотреть возможность его применения в своем следующем разрабатываемом проекте. Когда говорят о ОгарНрЬ, то могут иметь в виду разные вещи. Это может быть язык СтгарНрЬ, управляемый и поддерживаемый организацией СгарЬ()Е Гоипдабоп. Язык ОгарЬрЬ предназначен для указания АР1-интерфейсов и создания запросов для доступа и изменения данных, хранящихся за этими интерфейсами. Это также может быть сервер СтарЬС^Ь, который объединяет несколько низкоуровневых методов доступа к данным в полноценную веб-службу. Наконец, это может быть клиент СгарЬрЬ, который позволяет быстро создавать клиентские запросы при очень небольшом объеме кода и передавать по сети только требуемые данные. Но как можно интегрировать возможности ОгарЬрЬ в приложения КеасГ? РЕШЕНИЕ Прежде чем рассматривать использование ОгарНрЬ в Кеас1, создадим простой сер- вер СтгарЬрь. Первое, что нам нужно, — это схема СгарЬОЬ. Схема — это фор- мальное определение данных и служб, которые будет предоставлять наш сервер ОгарЫ^Ь. В листинге 5.21 приведена схема зсЬеша.дгарЪд1, которую мы будем применять в нашем сервере. Это спецификация ОгарЬрЬ примера форумного сообщения, рас- смотренного ранее в этой главе. 7 См. раздел 5.3.
I < ! Листинг 5.21. Схема ааЬвта.дгарЬ?! для форумного сообщения Ёуре Оиегу { теззадез: [Меззаде] } Ёуре Меззаде { 1с1: 1Б! аиЁЪог: 8ЁгЁпд! ЁехЁ: ЗЁгЁпд! Ёуре МиЁаЁЁоп { аскЗМеззаде ( аиЁког: 8ЁгЁпд! ЁехЁ: ЗЁгЁпд! ): Меззаде } Данная схема определяет один запрос (метод для чтения данных), называющийся теззадез, который возвращает набор объектов типа Меззаде. Каждый объект Меззаде имеет идентификатор аиЁЬог с ненулевым значением строчного типа и ненулевую строку ЁехЁ. В объекте также указана одна мутация (метод для изменения данных), называющаяся адс1Меззаде, которая будет сохранять сообщение на основании строки аиЁЬог И Строки ЗЁгЁпд. Прежде чем приступать к созданию нашего сервера, установим требуемые фрейм- ворки и библиотеки: $ прт ЁпзЁаИ аро11о-зегуег $ прт ЁпзЁаИ дгаркд! $ прт ЁпзЁаИ гедиЁге-ЁехЁ Фреймворк арроХо-зегуег предназначен для создания серверов ОгарЬрЬ. А библио- тека гедиЁге-ЁехЁ служит для чтения файла схемы зсЬета.дгарЪд!. Теперь можно соз- дать наш сервер. Его код из файла зегуег^з приведен в листинге 5.22. Листинг 5.22. Код сервера ОгарЬОк сопзё { Аро11о8егуег } = гедиЁге ('ароИо-зегуег ’) сопзё гедиЁгеТехЁ = гедиЁге(’гедиЁге-ЁехЁ’) сопзё ЁуреВеЁз = гедшгеТехЁ (’./зсЬета.дгарЬд!1, гедиЁге) сопзё теззадез = [ { Ёд: О, аиЁЬог: ’8С', ЁехЁ: 'КоИз сотрЁеЁе апс! а рЁЁск ёз ргодгат. Опе ВКАУО.’, },
{ 1С1: 1, аиЁЪог: 'РАО', 1ех1: 'Опе ВРА70 15 ап аЬог! сопёго! шоде1. АИНибе 15 2 тНе5.', }. { 1д: 2, аиЁ1юг: 'САРСОМ', ЁехЁ: 'АН 15 ме11 а! Нои5Ёоп. Уои аге дооб а! 1 пйпиЁе.', Ь ] сопбё ге5о1уег5 = { Оиегу: { ше55аде5: () => те55аде5, Ь МиЁаЁ1оп: { аск1Ме55аде: (рагепЁ, те55аде) => { соп51 Нет = { 1д: те55аде5.1епдЁк + 1, ...те55аде } те55аде5. ри5к (Нет) геЁигп Нет Ь Ь } соп51 5егуег = пей Аро11о5егуег({ ЁуреБеН, ге5О1уег5, )) зегуег .ИзЁеп ({ рогЁ: 5000 }).Ёкеп(({ иг1 }) => { соп5о1е.1од(Ьаипскед а! ${иг1}!) }) Сервер сохраняет сообщения в массиве, который предварительно заполнен не- сколькими сообщениями. Сервер запускается на исполнение следующей командой: $ пос1е ./5егуег.]5 Сервер работает по адресу ЬКр:/Лоса1Ьо51:5000. В браузере по этому адресу откры- вается окно клиента СгарЬрЬ Р1ау^гоипс1 (рис. 5.11). Клиент Р1ау§гоипс1 позволяет тестировать запросы и мутации, исполняя их в интерактивном режиме, прежде чем использовать их в рабочем коде. Теперь можно приступать к рассмотрению кода клиента Кеас!. Но сначала устано- вим клиент Аро11о: $ прт 1п51а11 @аро11о/сИепЁ Сервер ОгарЬрЬ поддерживает как сет- так и гозт-запросы, но клиент АроПо от- правляет запросы и мутации в виде запросов розт, что позволяет избежать междо- менных проблем, а также подключаться к сторонним СгарЬрЬ-серверам, не прибе-
гая к использованию прокси-сервера. Вследствие этого клиент ОгарЬрЬ должен самостоятельно обеспечивать функции кеширования. Поэтому нам нужно задать кеш и адрес сервера в настройках клиента в файле Арр.]з (листинг 5.23). Рис. 5.11. Клиент (ЗгарпОк Р1аудгоипд исполняется по адресу Ьпр://1оса1Коз1:500й Листинг 5.23. Конфигурирование кеша и адреса сервера ипрогк './Арр.С55' 1трогк { АроНоСИепк, Аро11о₽гоу1дег, ТпМетогуСасИе, } к г от 'бароНо/сИепк* йпрогк Еогит Г гот './Еогит* сопзк сИепк = пеы АроНоСИепк ({ иг1: 'Ьккр://1оса1Ьо5к:5000', саске: пеы ТпМетогуСасЪе(), }) Гипсккоп Арр() { гекигп ( <сИу с1аз5Ыате="Арр"> <Аро11оРгоУ1с1ег с11епк={с11епк}> <Еогит /> </Аро11оРгоу1с1ег>
</сИу> ) } ехрогр де^аи1Л Арр Компонент Арро1оРгоу1с1ег обеспечивает доступность клиента любому дочернему компоненту. Если не включить этот компонент в список импортирования, то весь клиентский код СгарЬрЬ не будет работать. Вызовы к серверу СггарйрЬ будем осуществлять из компонента гогшп. Для этого будем выполнять два действия: ♦ запрос меззадез, считывающий все сообщения; ♦ мутацию Адбмеззадез, отправляющую новое сообщение. Запрос и мутация составлены на языке СгарЬ(^Ь. Содержимое запроса Меззадез при- ведено в листинге 5.24. Листинг 5.24. Содержимое запроса Меззадез диегу Меззадез { теззадез { аиМтог 1:ехЁ } } Данный запрос означает, что мы хотим прочитать все сообщения, но возвратить только строки аиЁИог и ЁехЁ. Идентификатор 1<з сообщения не будет возвращен, по- скольку мы не запрашиваем его. Это одна из составляющих гибкости СгарЬрЬ: требуемые элементы указываются в запросе, а не в отдельном вызове АР1 для каж- дой разновидности. Мутация Аббмеззаде чуть посложнее, поскольку ее нужно параметризировать, чтобы можно было указывать значения для аи^Иог и 1:ех1 при каждом ее вызове (лис- тинг 5.25). Листинг 5.25. Код мутации АсШезваде тЩаЧоп Адс1Меззаде ( $аи!:Ьог: 31:г1пд! ${:ех1:: 51:г1пд! ) { аскЗМеззаде ( аиМюг: $аЩЬог 1:ехЁ: $1зех'Ь ) { аиЁЪог 1:ех1:
Мы будем использовать хуки изебиегу и изеМи^аМоп, предоставляемые клиентом Аро11о среды бгарЬрЬ. Хук изеОиегу возвращает объект с атрибутами даг.а, 1оасИпд и еггог8. А хук изеМи^аМоп возвращает массив с двумя значениями: функцией и объ- ектом, представляющим результат. В разделе 5.2 мы рассмотрели, как автоматически обновлять данные после того, как они были изменены на сервере какой-либо мутацией. К счастью, клиент Аро11о оснащен готовым механизмом решения этой задачи. В частности, при вызове мута- ции можно указать массив других запросов, который нужно возвратить в случае успешного выполнения мутации (листинг 5.26). Листинг 5.26. Вызов мутации аайМавзаде с указанием массива другого запроса аиаЁЁ айдМеззаде({ уагЁаЫез: { аиЁЬог, ЁехЁ }, геЁеЁсИОиегЁез: ['Меззадез1], }) Строка меззадез представляет название запроса СгарЬрЬ, означая, что мы можем исполнять множественные запросы к службе СгарЬрЬ и указывать, который из них может требовать обновления после изменений. Наконец, у нас есть все составляющие компонента гогит, полный код которого при- веден в листинге 5.27. Листинг 5.27, Полный код компонента Гогит йпрогЁ { дд1, изеМиЁаЁгоп, изеОиегу } Ёгот ' бароИо/сИепЁ' йпрогЁ { изеЗЁаЁе } Ёгот 'геасЁ' сопзё МЕЗЗАСЕЗ = дд!' диегу Меззадез { теззадез { аиЁког ЁехЁ } } сопзё А00_МЕ35АСЕ = дд!' тиЁаЁЁоп АскЗМеззаде($аиЁког: ЗЁгЁпд!, $ЁехЁ: ЗЁгЁпд!) { аскЗМеззаде(аиЁЬог: $аиЁког, ЁехЁ: $ЁехЁ) { аиЁког ЁехЁ } ) 8 Это стандартный набор значений для асинхронной службы. Мы использовали их в других рецептах в этой главе.
сопзй Еогит =()=>{ сопзТ; { 1оас11пд: теззадезЪоасИпд, еггог: теззадезЕггог, даЁа, } = изеОиегу(МЕЗЗАСЕ8) сопз!: [аскЗМеззаде] = изеМиЁа1:1оп(АВВ_МЕ35АСЕ) сопз1: [1:ех<:, зе^Тех!:] = изеЗСа^еО сопзё [аиЁИог, зеЁАиЁЬог] = изеЗЁаЁеО сопзё теззадез = даЁа && (ЗаЁа.теззадез геЁигп ( <с11У с1аз5Ыате="Арр"> <1приЁ Ёуре="ЁехЁ" уа1ие={аиЁког} р1асеЬо1дег="АиЁЪог" опСЪапде={(еУЁ) => зеЁАиЁЪог(еУЁ.ЁагдеЁ.уаЁие)} /> <ЁехЁагеа уа1ие={ЁехЁ) р1асеЬо1дег="Меззаде" опСЬапде={ (еУЁ) => зеЁТехЁ(еУЁ.ЁагдеЁ.уаЁие)} /> <Ьпёёоп опС11ск={азупс () => { Ёгу { аыазЛ аскМеззаде ({ уагтаЫез: { аиМюг, ЪехТ: }, ге^е1:ск0иег1ез: [ 'Меззадез' ], }) зеЁТехЁ('') зе^АиЁког('’) } са^сИ (егг) (} }) РозГ: </Ьи(11:оп> (теззадезЕггог ? ( «Цу с1аззЫате="еггог"> 5оте1Ь1пд мепЬ мгопд: <с11\7 с1аззЫате="еггог-соп1еп1:з',> {теззадезЕггог.теззаде) </сИу> </сНу> ) : теззадезЬоасИпд ? ( <сИу с1аззНате="1оад1пд">Ьоад1пд...</сНу>
) : теззадез && теззадез. 1епд1:Ь ? ( <с!1> {теззадез.тар((т) => ( <сИ> {т. аи!1Ьог} < /с11:> <с1с1> {т. СехГ:} < / ск1> </> ))} </61> ) : ( ’Ыо теззадез' )} </с11У> ) } ехрогр деГаиН Еогит Теперь отправленное сообщение автоматически добавляется в конец списка сооб- щений, как показано на рис. 5.12. РАО |А1Люг Оонп гапд», 1 аНн. 3-4 жхХвл пом, 1л 2,1^5 р*г жисопй. жг* ГЬгаидЬ гЪ* хедХсп о! шзихгжиж Йупли1с рг*«жчги пои. В И11*я Црип г*пдвг 1? *ж1рс11:у в,ООО р<г »*сопй. Меззаде 5С Ро1з сотр1е1е апй а рЙсИ 1в ргодгат Опе ВПАУО. РАО Опе ВЯАУО15 ап аЬоП соп1го1 тоде!. А№1иде !з 2 тИез. САРСОМ АН 13 ше11 а! Нои&оп. Уои аге доод а! 1 гптЛе. __________________________________________________4 | Рея! ] 5С ВоНз сотр!е* *е апй а рйсН 1з ргодгат. Опе ВЯАУО, РАО Опе ВЙАУО13 ап аЬой соп(го! то<1е1. АЙ11иде !з 2 тНез. САРСОМ АП 13 жеИ а( Ноиз1оп. Уои аге доой а* 1 литЛе. РАО Осшп гапде, 1 тЛез. АМГийе 3-4 тйез пом, уеккйу 15 2,195 Тее1 рег зесопд. У/е аге ШгоидЬ 1Ье тедюл о( тахяпит дупатю ргеззиге псш. 8 гта1ез <Ь»п гапде, 12 т!1е$ НдЬ, уе1осЙу 4,000 1ее* рег зесогх). Рис. 5.12. Отправленные сообщения автоматически добавляются в конец списка Обсуждение Платформа 6гарЬ()Е особенно полезна, когда группа разделена на разработчиков бэкенда и разработчиков фронтенда. В отличие от платформы КЕ8Т, система ОгарИрЬ не требует, чтобы разработчики бэкенда предоставляли специализирован- ную обработку для каждого клиентского вызова АР1. Вместо этого они могут про- сто предоставить монолитную и единообразную структуру АР1, оставив команде фронтенда решать, как именно они будут с ней работать.
При создании приложений К.еас1: на основе СтгарЬ()Е может быть полезным рас- смотреть возможность переместить все вызовы хуков в изеОиегу и изеМисаПоп в спе- циализированные хуки9. Таким образом вы создадите более гибкую архитектуру, в которой компоненты не так сильно привязаны к подробностям служебного уровня. Исходный код данного рецепта можно загрузить на веб-сайте бйНиЬ по адресу ЬПр$://оге11.1у/хТсАК. 5.6. Уменьшение сетевой нагрузки при помощи очищенных запросов ЗАДАЧА Работая в системе разработки, легко забыть об эффективности приложения. Воз- можно, это и к лучшему, поскольку более важно, чтобы код работал правильно, нежели быстро, но при этом неправильно. Но когда приложение попадает в реальную обстановку, например в среду для тес- тирования на приемлемость для пользователя, тогда вопрос производительности становится более важным. Ассоциированные с ЯеасС динамические интерфейсы часто осуществляют множество сетевых вызовов, и стоимость этих вызовов будет заметна только тогда, когда серверу необходимо одновременно обслуживать боль- шое число клиентов. В этой главе мы несколько раз работали с приложением поиска. В этом приложе- нии пользователь может искать город по его имени или по названию штата, в кото- ром он находится. Поиск выполняется моментально, пока пользователь вводит данные. Если в браузере открыть окно Инструменты веб-разработчика, а в нем выбрать вкладку Сеть, то мы увидим, что приложение создает сетевой запрос при вводе каждого символа искомой строки (рис. 5.13). Большинство из этих сетевых запросов не предоставляют почти никаких полезных данных. Средний пользователь, наверное, вводит один символ каждые полсекунды, Нате 8131119 Туре [3 веагсЪ?1егт8=с 200 хЬг |3 зеагсЬ?1егтз=сЬ 200 хЬг ] зеагсН?!егтз=сЫ 200 хЪг |3 зеагсК?10гт8=сЫс 200 кЬг {3 зеагсЬ?1еппз=сЪ»са 200 хЬг (3 зеагс11?!егтз=сЬюад 200 хЬг 3 &еагсН?1егтз*сЬ|садо 200 хЬг Рис. 5.13. Приложение поиска исполняет сетевой запрос при вводе каждого символа 9 Во многом подобно обработке сетевых вызовов НТТР в разделе 5.2.
и если при этом он смотрит на клавиатуру, то, скорее всего, он даже не увидит ре- зультатов поиска, возвращаемых после каждого символа. Из семи запросов данного примера, отправленных серверу, он, скорее всего, прочитает результаты только одного: последнего. Это означает, что сервер выполняет в семь раз больший объем работы, чем необходимо. Что можно сделать, чтобы не отправлять столь много напрасных запросов? РЕШЕНИЕ Для решения этой задачи мы очистим сетевые запросы от лишних вызовов поиска. Под очисткой сетевых запросов имеется в виду задержка отправки сетевого запроса на очень короткое время, скажем полсекунды. Если в течение этого периода ожи- дания поступает другой запрос, мы отбрасываем первый запрос и опять задержива- ем отправку нового запроса, и т. д. Таким образом, мы задерживаем отправку всех запросов до тех пор, пока не будем получать новых запросов в течение полсекунды. Чтобы разобраться, как это работает, рассмотрим код хука поиска нашего примера приложения из файла и$е$еагсН.]8 (листинг 5.28). Листинг 5.28. Код хукаивеЗвахсИ ЁтрогЁ { изеЕИИесЁ, изеЗЁаЁе } И г от 'геасЁ' йпрогЁ ах1оз Г гот 'ахтоз' сопзё изеЗеагсЬ = (Ёегтз) => { сопзё [йаЁа, зеЁВаЁа] = изеЗЁаЁе([]) сопзё [1оасИпд, зеЁЬоасИпд] = изеЗЁаЁе(Иа1зе) сопзё [еггог, зеЁЕггог] = изеЗЁаЁе() изеЕГЁесЁ(() => { 1еЁ сИдСапсе! = Га1зе зеЁЕггог(пи!1) 1Ё (Ёегтз) { ;(азупс () => { Игу { зеЁЬоасНпд (Ёгие) сопзё гезропзе = ама1Ё ахЁоз.деЁ('/зеагсИ', { рагатз: { Ёегтз }, }) (! сНс1Сапсе1) { зеЁБаЁа(гезропзе.даЁа) } } саЁсЪ (егг) { зеЁЕггог(егг) } ПпаНу { зеЁЬоасИпд(Иа1зе) } }) О
} е1зе { зеЁБаЁа([ ]) зеЁЬоасИпд (Га1зе) } геЁигп () => { сНдСапсе! = Ёгие } }, [Ёегтз]) геЁигп { даЁа, ХоасНпд, еггог } } ехрогЁ (ХеГаиХЁ изеЗеагсЪ Код, который отправляет сетевой запрос, находится в блоке (азупс ()....)(). Нам нужно задержать исполнение этого кода, пока не получим полсекунды без новых запросов. После этой задержки код запустится на исполнение функцией 1ауа8спр1 зеЁТХтеоих. Это будет ключевым моментом реализации возможности очистки: сопзё пеыНтег = зеЁТнпеоиЁ (ЗОМЕЕСЫСИСЖ, 500) Новое значение пекИтег можно учесть, чтобы очистить счетчик тайм-аута. Если сделать это достаточно быстро, то вызов функции никогда не будет осуществлен. Чтобы разобраться, как с помощью этого кода реализовать очистку сетевых запро- сов, рассмотрим код файла изе8еагсЬ.]з, модифицированный кодом очистки и теперь хранящийся в файле и8еЭеЬоипсес18еагсЬ.]8 (листинг 5.29). Листинг 5*29. Код файла и8е0еЬоипсе<13еагс1-1.> Хтрогк { изеЕХХесЁ, изеЗЁаЁе } Ггот 'геасЁ' ХтрогЁ ахХоз Ггот 'ахХоз' сопзХ: изеБеЬоипсесХЗеагск = (Ёегтз) => { сопзХ: [даЁа, зеЁБаЁа] = изеЗЁаЁе ([]) сопзХ: [ХоасНпд, зеЁЬоасИпд] = изеЗЁаЁе(ЁаХзе) сопзХ: [еггог, зеЁЕггог] = изеЗЁаЁе() изеЕХГесЁ(() => { зеЁЕггог (пиХХ) XX (Ёегтз) { сопзХ: пеиТХтег = зеЁТХтеоиЁ (О => { ;(азупс () => { Х:гу { зеЁЬоасИпд(Ёгие) сопзё гезропзе = аиаХЁ ахХоз.деё('/зеагсЬ', { рагатз: { Ёегтз }, )) зеЁБаЁа(гезропзе.даЁа) } саЁск (егг) { зеЁЕггог(егг)
} Ё1па11у { зеЁЬоасЦпд (Га1зе) } }) О ], 500) геЁигп () => с1еагТ1теоиЁ(пемТнпег) } е1зе { зеСВаЁа([]) зеЁЬоасИпд(Ёа1зе) ) ), [Ёегтз]) геЁигп { йаЁа, 1оасИпд, еггог } ) ехрогЁ деГаи1Ё изеСеЬоипседЗеагск Мы передаем код сетевых операций функции зетшеоиЁ, а затем возвращаем сле- дующее: () => с!еагТ1теоиЁ(пеиТйпег) Если возвращать функцию из хука изеЕИеси, то этот код вызывается непосредст- венно перед следующим запуском данного хука. Если пользователь вводит симво- лы довольно быстро, то отправка сетевого запроса будет откладываться. Только при отсутствии ввода от пользователя в течение свыше полсекунды этот код отпра- вит сетевой запрос. Исходная версия этого хука, изеЗеагсЪ, отправляла сетевой запрос для каждого вво- димого символа. А в модифицированном варианте с кодом очистки, изеОеЬоипседЗеагсъ, ввод искомой строки со средней скоростью вызовет только один сетевой запрос (рис. 5.14). Мате 21а!из Туре 0 8вагсП?1блт5=сЁ4садо 200 хЬг Рис. 5.14. Модифицированный вариант хука поиска будет отправлять меньше запросов Обсуждение Очистка сетевых запросов от избыточных вызовов поиска уменьшит объем сетево- го трафика и нагрузку на сервер. При этом важно помнить, что очистка запросов только уменьшает число ненужных сетевых запросов, но не устраняет проблему возврата ответов на запросы в неправильном порядке. Подробно решение пробле- мы возврата ответов в неправильном порядке рассматривается в разделе 5.3. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу ЬПрз ://огеН.1у/5паВ.
Библиотеки компонентов Для разработки приложения любого размера, скорее всего, потребуется библиотека компонентов. Круг типов данных, поддерживаемых нативным НТМЬ, несколько ограничен, а их реализации могут варьироваться в зависимости от браузера. На- пример, поле для ввода даты выглядит очень по-разному в браузерах СИготе, ГйеГох и Ес1§е. Библиотеки компонентов позволяют создать единообразный внешний вид для раз- рабатываемого приложения. Они также часто хорошо подстраиваются при пере- ключении между настольными и мобильными клиентами. Самое важное, библиоте- ки компонентов часто повышают удобство в использовании приложения. Такие библиотеки или создаются согласно всесторонне протестированным проектно- конструкторским стандартам (например, библиотека Ма1епа1 Ые81§п), или разраба- тываются в течение нескольких лет. Поэтому все проблемные аспекты обычно сглажены. Но при этом следует иметь в виду, что идеальных библиотек компонентов не суще- ствует. Все они имеют свои сильные и слабые стороны, и нужно выбирать такую библиотеку, которая лучше всего отвечает вашим требованиям. При наличии большой ЫХ-команды разработчиков и робастного набора существующих стандар- тов проектирования вам, скорее всего, будет нужна библиотека, позволяющая вы- полнять большое количество подстроек, чтобы соответствовать вашим корпора- тивным темам. Примером такой библиотеки будет библиотека Ма1епа1-Ы1, которая позволяет довольно значительно модифицировать свои компоненты. В случае же небольшой ЫХ-команды или вообще при ее отсутствии вам, скорее всего, подойдет библиотека наподобие удобной и функциональной библиотеки Бетапйс Ш, кото- рая позволит вам подготовиться и приступить к работе в быстром порядке. Но независимо от выбранной библиотеки всегда помните, что важным аспектом проектирования и разработки пользовательских интерфейсов приложений является не внешний вид приложения, а его поведение. Пользователи вскоре перестанут об- ращать внимание на всю крикливую графику, навешанную на интерфейс, но они никогда не забудут, а главное, не простят, если какая-либо часть интерфейса будет вызывать у них раздражение при каждом использовании.
6.1. Использование библиотеки Ма1епа1 Оее!дп совместно с библиотекой Ма1епа1-1Л ЗАДАЧА В настоящее время многие приложения предназначены для исполнения как в Сети, так и в виде нативного ПО на мобильных устройствах. Компания Ооо§1е разра- ботала спецификацию Ма1епа1 Безщп для обеспечения одинакового восприятия приложений на всех платформах. Элементы этой спецификации будут выглядеть знакомыми вашим пользователям, если они также пользуются смартфонами на Апс!го1с1 или любыми иными продуктами разработки Ооо§1е. Существует несколько реализаций этой спецификации, одной из которых является библиотека Ма1епа1-1Л для платформы Кеас1. Но как нам установить эту библиотеку и как потом работать с ней? РЕШЕНИЕ Библиотека Ма1епа1-1Л устанавливается следующим образом: $ прт 1П5Ёа11 бтаСегтаХ-иг/соге Базовая библиотека содержит основные компоненты, но в ней отсутствует одна важная возможность — стандартный шрифт. Чтобы придать такое же восприятие компонентов библиотеки, как в нативных мобильных приложениях, нужно устано- вить шрифт КоЬо1о компании 6оо§1е: $ прт тпзХаН ГопХзоигсе-гоЬоХо В спецификации МаХепа! Безг^п также определен большой набор стандартных значков. Эти значки предоставляют общий визуальный язык для таких стандартных задач, как редактирование, создание новых элементов, коллективное использование содержимого и т. п. Чтобы получить высококачественные версии этих значков, нужно также установить библиотеку значков Ма1епа1-1Л: $ прт хпзХаИ @таХег1а1-и1/1сопз Теперь, когда у нас все готово для использования библиотеки Ма1епа1-1Л, выясним, что можно делать с ее помощью. Здесь мы не можем описать подробно все доступ- ные компоненты библиотеки, поэтому рассмотрим только некоторые наиболее по- пулярные возможности1. Начнем с исследования основ возможностей стилистического оформления, предос- тавляемых библиотекой Ма1епа1-1Л. Чтобы обеспечить единообразный вид компо- нентов МаХепаИЛ в разных браузерах, библиотека содержит компонент СззВазеИпе, который нормализует базовое стилистическое оформление разрабатываемого при- ложения, удаляет отступы и применяет стандартные фоновые цвета. Этот компо- 1 Подробная информация обо всем наборе компонентов предоставляется на веб-сайте Ма1епа1-ПА (|Щр$.7/та<епа1-и1.сот).
нент следует вставить в разрабатываемое приложение где-то в самом начале. На- пример, в приложениях сгеате-геаст-арр его следует добавить в файл Арр.] 8, как показано в листинге 6.1. Листинг 6.1. Добавление компонента СазВазеХхпд в файл Арр.]з 1ШрогЁ СззВазеИпе Г гот '@таТег1а1-и1/соге/СззВазе11пе’ ^ипсЁхоп Арр() { // ... геТигп ( <с!гу с1аззЫаше="Арр"> <СззВазеИпе /> </с!гу> ) } ехрогЪ де^аиИ Арр Далее, взглянем на компоненты АррВаг и тоо1Ьаг. Эти компоненты предоставляют стандартные заголовки, которые можно увидеть в большинстве приложений с ис- пользованием Ма1епа1 Ое81§п и в которых будут отображаться другие элементы, такие как меню "гамбургер" и панели меню. Мы поместим компонент АррВаг вверху экрана, а внутрь его вставим компонент Тоо1Ьаг (листинг 6.2). Это даст нам возможность посмотреть, как обрабатывается печать внутри Ма1епа1-1Л. Листинг 6.2. Добавление компонентов АррВаг и ТооИэах <сИу с1аззМате=,’Арр,’> <СззВазеИпе/> <АррВаг ро51Т1оп=,ге1аТ^е,> <Тоо1Ьаг> <ТуродгарЬу сотропепС=’Н1' уаг!апТ= ’ И6' со1ог='1пЪег1Т ’ поИгар> МаТег1а1-1Д СаИегу </ТуродгарЪу> </Тоо1Ьаг> </АррВаг> <та1п> {/* Сюда вставляется основное содержимое...*/} </па1п> </сПу> Хотя в приложениях с использованием Ма1епа1-1Л можно вставлять обычное тек- стовое содержимое, в целом будет лучше отображать его внутри компонента туродгарку, который обеспечит совместимость текста со стандартами МаТепа! Эе81§п.
Этот же компонент пригоден для отображения текста внутри соответствующих элементов разметки. В данном случае мы будем отображать текст в компоненте тоо1Ьаг в формате элемента ы. Именно таково определение атрибута сотропепх ком- понента туродгарку: элемент НТМЬ, который должен применяться для заключения в нем текста. Но мы также можем дать указание библиотеке Ма1епа1-1Л придать тексту стилистическое оформление, например в виде заголовка Ьб. Это уменьшит размер текста и сделает заголовок менее бросающимся в глаза. Далее рассмотрим, каким образом посредством возможностей библиотеки Ма(епа1- VI осуществляется стилистическое оформление вывода. Для этого предназначены темы. Тема— это объект 1ауа8сг1р1, который определяет иерархию стилей С88. Темы можно определять централизованно, что позволяет управлять общим внеш- ним видом разрабатываемого приложения. Темы по умолчанию можно модифицировать. Но чтобы получить возможность создавать модифицированные версии тем по умолчанию, нужно импортировать функцию шакеЗЪуХез: Хтрогк { такеЗкуХез } Хгот 'бтакегХаХ-иХ/соге/з^уХез' Наше приложение будет отображать галерею изображений, поэтому нам нужно создать стили для элементов галереи, описаний и т. п. Стили для всех этих разных экранных элементов мы можем создать при помощи функции такез^уХез, как пока- зано в листинге 6.3. ЛисШнг в.З. Срз^айие стилей при помощи функции сопз!: изеЗкуХез = такеЗЪуХез ((ккете) => ({ даХХегуСгЫ: { раскЦпдТор: Скете. зрасхпд (4), }, даХХегуХкетОезсгХрЫоп: { оуегПои: ' к5.с1с1еп', кехкОуегЯоы: 1 еХХХрзХз', мкХкеЗрасе: 'поигар', }, })) В этом упрощенном примере мы расширяем базовую тему, включая в нее стили для классов даИегуСгЩ и даИегуНешОезсгХркХоп. Атрибуты С88 можно добавлять на- прямую или (в случае атрибута раскЯпдТор в классе даХХегуСгХб) можно ссылаться на какое-либо значение в текущей теме (в данном случае это ккете.зрас1пд(4)). Таким образом, появляется возможность перенаправить части стилистического оформле- ния к центральной теме, где мы сможем изменить их в дальнейшем. Хук изеЗкуХез, возвращаемый функцией шакеЗкуХез, создает набор классов С88, а затем возвращает их названия, чтобы к ним можно было обращаться изнутри раз- рабатываемого компонента.
Например, нам нужно будет отобразить матрицу изображений, используя компо- ненты СопЬахпег и Спс12. Для этого мы можем прикрепить к ним стили из темы, как показано в листинге 6.4. Листинг 6.4. Прикрепление стилей из темы к компонентам сопзё сЕаззез = изе5Еу1ез() геЁигп ( с1аззМате="Арр"> <татп> <СопЁа1пег с1аззНате={ сЕаззез. даНегуСгЁс!} > <Сг1с1 сопЁатпег зрас1пд="4"> <Сг1с1 1Ёет>.. .</Сг1с1> <Сг1с1 1Еет>.. .</Сг1<1> </Сг1с1> </СопЁа1пег> </таЁп> </сИу> Каждый компонент сгзД является или контейнером, или элементом. Мы будем отображать одно изображение галереи в каждом элементе. Согласно политике МаЕепа! Оезщп важные элементы отображаются в карточках. Карточка представляет собой прямоугольную панель, которая кажется плавающей на небольшой высоте над фоном. Если вам когда-либо приходилось использовать магазин Ооо§1е Р1ау, то вы должны были видеть карточки, в которых отображаются приложения, музыкальные дорожки или другие вещи, предлагаемые для загрузки. Мы поместим по карточке в каждый элемент Спд и будем применять ее для пред- варительного просмотра изображения, отображения описания изображения, а также кнопки для вывода более подробной версии изображения. На рис. 6.1 показано такое использование карточек в разрабатываемом приложении. Библиотека МаГепа1-1Л также предоставляет обширную поддержку для диалоговых окон. В листинге 6.5 приведен пример кода для пользовательского диалогового окна. Листинг 6.5. Пример кода дляпользовательского диалогового окна [ ЁтрогЁ БтаЕод Егот '@таЁег1а1-и1/соге/В1а1од' ЕтрогЕ БЕаЕодТЕЕЕе Егот '@таЁегЕа1-иЕ/соге/0Еа1одТЕЁ1е' ЕтрогЕ ТуродгарЬу Егот '@таЕегЕа1-иЕ/соге/ТуродгарЪу' ЕтрогЕ ВЕа1одСопЕепЕ Егот '@таЁегЕа1-иЕ/соге/ОЕа1одСопЕепЁ' ЕтрогЕ ВЕаЕодАсЕЕопз Егот 'бтаЕегЕаЕ-иЕ/соге/ВЕаЕодАсЕЕопз' ЕтрогЕ ВиЕЕоп Егот '@таЁегЕа1-иЕ/соге/ВиЕЕоп' 2 Дополнительная информация по этим компонентам предоставляется на веб-сайте библиотеки Ма1епа1-1Л зйе (ЬПр$://та(епа1-и1.сот).
нпрогЁ С1озе1соп Ггогп ' (ЭтаТеггаД.-ит/тсопз/СЗ.озе' сопзТ Му01а1од = ({ опС1озе, ореп, ЫЫе, сИНбгеп }) => { геТигп ( <Б1а1од ореп={ореп} опС1озе={опС1озе}> <В1а1одТИ1е> <Туродгарку сотропепЁ="Ы." Vа^^апТ="1^.5’' со1ог="1пЪег1Ё" поИгар > {Ё1Ме} </ТуродгарИу> </С1а1одТ1Л1е> <01а1одСопТеп!:> {сЬНдгеп} </В1а1одСопТепТ> <01а1одАсТ1опз> <ВиМ:оп уаг!апТ:=,,ои1:11песГ з!:аг!:1соп={<С1озе1соп />} опС11ск={опС1озе} С1озе </ВиГХоп> </Б1а 1одАс1:1 оп з > </С1а1од> ) } ехрогЬ де^аи1Ё МуС1а1од Рис. 6.1. Карточки находятся внутри элементов матрицы 6пс1, которые, в свою очередь, находятся внутри контейнеров
Обратите внимание на то, что мы импортируем значок 8УО из коллекции значков библиотеки Ма1епа1-1Л, которую установили ранее. Вверху диалогового окна нахо- дится его название вХаХодтХЁХе. Действия БХаХодАсЁХопз представляют кнопки, кото- рые отображаются внизу диалогового окна. Основное тело диалогового окна опре- деляется В ОХаХодСопЁепЁ. В листинге 6.6 приведен полный код файла Арр.]з приложения. Листинг 6.6. Полный код файла Арр.)$ приложения ХшрогХ './Арр.сзз' ХтрогЁ СззВазеХХпе Хгот '@таЁегХаХ-иХ/соге/СззВазеХХпе' ХтрогЁ АррВаг Ггот '0таЁегХаХ-иХ/соге/АррВаг1 ХтрогЁ { ТооХЬаг } Хгот '@таЁегХаХ-иХ/соге* ХтрогЁ СопЁаХпег Ггот '0таЁегХа1-иХ/соге/СопЁаХпег1 ХтрогЁ СгХс! Хгот ' @таЁегХа!-иХ/соге/СгХсГ ХтрогЁ Саге! Хгот ' @таЁе^XаX-иX/со^е/Са^с^, ХтрогЁ Сагс1Мес1Ха Ггот ' @таЁегХаХ-иХ/соге/Сагс!Мес1Ха1 ХтрогЁ СагсХСопЁепЁ Ггот 10таЁегХа1-иХ/соге/Саг6СопЁепЁ1 ХтрогЁ Сагс1АсЁХопз Ггот ' ЙтаЁегХаХ-иХ/соге/СагсХАсЁХопз' ХтрогЁ ТуродгарЬу Хгот '@таЁегХа!-иХ/соге/ТуродгарИу1 ХтрогЁ { такеЗЁуХез ) Ггот '@таЁегХаХ-иХ/соге/зЁуХез' ХтрогЁ { изеЗЁаЁе } Ггот 'геасЁ’ ХтрогЁ МуБХаХод Хгот './МуБХаХод' ХтрогЁ ХтадеЗеагсЫсоп Хгот '@таЁегХаХ-иХ/Хсопз/1тадеЗеагск' ХтрогЁ даХХегу Хгот './даХХегу.^зоп' ХтрогЁ ХсопВиЁЁоп Хгот 1@таЁегХаХ-иХ/соге/1сопВиЁЁоп' сопзё изеЗЁуХез = такеЗЁуХез((ЁЬете) => ({ даХХегуСгХб: { раскНпдТор: ЁЬете. зрасХпд (4), Ь даХХегуХЁет: { НеХдНЁ: '100%*, (ХХзрХау: ТХех', ХХехСХгесЁХоп: 'соХитп', // тахИХ<ХЁЬ: '200рх' }, даХХегуХтаде: { раддХпдТор: '54%', }, даХХегуХЁетСезсгХрЁХоп: { оуегХХоы: 'ЪХйдеп', ЁехЁОуегХХоы: 'еХХХрзХз', мЬХЁеЗрасе: 'помгар', }, }))
^ипсМоп Арр () { сопзР [зкомОекаНз, зекЗкоыВекаИз] = изеЗЬаке(^а1зе) сопзк [зеТескесПтаде, зекЗеХескедТтаде] = изеЗкакеО сопз1: с1аззез = изеЗкуХез () геГигп ( <с!гу с1аззМате="Арр"> <СззВазеИпе /> <АррВаг ро51Х1оп="ге1аХгуе"> <Тоо1Ьаг> <ТуродгарЪу сотропепк="к1" уаг1апР=’'Ъ6" со1о^="^пке^^к,, поИгар > МаЁег1а1-1Л Са11егу </ТуродгарИу> </Тоо1Ьаг> </АррВаг> <ша1п> <Соп(:а1пег с1аззЫате={сХаззез. даИегуСгЫ} > <Сг1с1 сопкахпег зрас1пд="4"> {даИегу.тар ((зЛет, 1) => { гекигп ( <Сг1с1 1кет кеу={ 'ркоко-${1}'} хз={12} зт={3} 1д= {2}> <Сагд с!аззМате={с!аззез.да11егу1Ёет}> <СагдМе<Иа 1таде={гнет.1таде} с1аззМате={с1аззез.да11егу1таде} ЁзЛ1е="А рЪо1:о" /> <Сагс1Соп1:епЁ> <ТуродгарЪу диРРегВоРРот уаг1апР="Ь6п сотропепЁ=,,Ъ2" 1таде </ТуродгарЪу> сТуродгарку с1аззЫаше={с1аззез.да11егу1кет0езсг1рЁ1оп} > {1кет.с1езсг1рк1оп} </Туродгарку> </СагдСопкепк> <Сагс1Аск1ОПЗ> <1сопВиккоп аг1а-1аЬе1=,,де1еЪе"
опСИск={() => { зе^ЗеТесЬесИшаде (Нет) зеЁЗЪомБе^аИз (^гие) )) со1ог="рг1тагу" <1тадеЗеагсЫсоп /> </1сопВиНоп> </Сагс1Ас(: 1опз> </Сагс1> </Сг1д> ) })} </Сг1с1> </Соп!:а1пег> </та!п> <МуБ1а1од ореп={зНоиЮеЪа11з} Ё1Р1е="0еЁаНз’' опС1озе={() => зе113ЪоиСе1:а11з (Га1зе)} <1тд згс={зе1есХес11таде && зе1есГ:ес11таде.1таде} аН="Ггот Р1с5ит" /> <Туродгарку> {зеХесХесПтаде && зе1ес±ес11таде.с1езсг1р(:1оп) </Туродгарку> </МуР1а1од> </<±И> ) ) ехрогр с!еГаи1Р Арр Обсуждение Библиотека Ма1епа1-1Л замечательно подходит для разработки веб-приложений; это одна из наиболее популярных библиотек, существующая в настоящее время для разработки приложений Кеас1. Посетители вашего приложения почти наверняка сталкивались с оформлением средствами этой библиотеки в других приложениях, что повысит его удобство в использовании. Но прежде, чем приступать к работе с библиотекой Ма1епа1-1Л в своем приложении, будет полезным уделить некоторое время изучению принципов платформы Ма(епа1 Пезщп (Ъйр8://оге11.1у/Лк7>у). Таким образом, ваше приложение будет не только привлекательным, но также удобным в использовании и доступным (Ьйр» ://о гейЛу/КЛ XVI) для пользователей. Исходный код данного рецепта можно загрузить на веб-сайте СгйНиЬ по адресу ЫСрз ://огеП.1у/Тц УЕх.
6.2. Простой пользовательский интерфейс посредством Неас1 Воо(з1гар ЗАДАЧА Наиболее популярная библиотека С88 в последние 10 лет — это, наверное, библио- тека ВооЫгар от компании ТичИег. Данная библиотека будет также хорошим выбо- ром, когда при разработке нового приложения у вас нет лишнего времени, чтобы создавать специализированный пользовательский интерфейс, и вы просто хотите взять какой-либо интерфейс, который был бы удобным и знакомым многим поль- зователям. Но библиотека Воо181гар имеет свои истоки в эпохе, когда фреймворков наподобие Кеас! не существовало. Эта библиотека содержит ресурсы С88 и набор библиотек 5ауа8спр1, предназначенных для работы с веб-страницами, содержащими неболь- шой объем специализированного клиентского кода. Функциональность базовой библиотеки Воо1з1гар не очень хорошо вписывается в совместную работу с фрей- ворками наподобие Кеас!. Так как же оснастить возможностями ВооЫгар разрабатываемые нами приложения на основе К.еасГ? РЕШЕНИЕ Существует несколько модификаций библиотеки Воо1з1гар для работы с Кеас1. В этом рецепте мы рассмотрим одну из них— библиотеку Кеас! ВооШгар. Эта библиотека работает совместно со стандартными библиотеками С88 Воо181гар, расширяя код 5ауа8спр1 библиотеки ВооЫгар, чтобы улучшить совместимость с Кеас(. Для начала установим компоненты Кеас! ВооЫгар и библиотеки Лауа8спр1 Воо18(тар: $ прт 1пзЪа11 геасЬ-ЬооТз<:гар ЬооТзГгар Библиотека Кеас! ВооЫгар не содержит никаких собственных таблиц стилей С88, поэтому вам нужно будет самому включить их копию. Стандартный способ для этого — загрузить требуемый файл из сети доставки контента (СБЫ) в коде НТМЬ. Например, для приложений сгеате-геаст-арр в файл риЫгс/шдех.Ьйп! следует вста- вить код, пример которого показан в листинге 6.7. Листинг 6.7. Подключение ресурсов С55 библиотеки вооЫгар <Ипк ге1="зТу1езкееТ" кгеЕ="ЬТ1;рз: //тахсс!п.ЬооЁ5Тгарс(1п.сот/ЬооТзТгар/4.5.0/сзз/Ьоо1:з!:гар.т1п. сзз" ^пТед^^^у="5ка384-9аIТ2пКрС12^к9д89Ьа^1411^^АрЕтС26ЕмАОН8Ид215МУУxЕЕс+^сРЬ1с^КС’, егоз зог1д1п="апопутоиз" />
Будет крайне желательным заменить это самой последней доступной стабильной версией ВооШгар. Управление версиями ВооЫгар нужно будет выполнять вруч- ную, поскольку она не обновляется автоматически при обновлении библиотек 1ауа8спр1. Библиотека ВооЫгар хорошо подходит для решения общих задач, но ее особенно сильная сторона— поддержка форм. Разметка форм требуемым образом может занять много времени и усилий. Библиотека ВооЫгар выполняет всю тяжелую ра- боту, позволяя разработчику концентрироваться на функциональности формы. Компонент Гогт библиотеки Кеас! ВооЫгар содержит почти все необходимые сред- ства для создания форм. Например, подкомпонент Еогт.сопрго! создает ввод 1при1: по умолчанию, подкомпонент гопп.ьаЬе! создает метку 1аЬе1, а подкомпонент Еопп.сгоир ассоциирует эти два компонента и размечает их должным образом (лис- тинг 6.8). Листинг 6.8. Использование подкомпонентов компонента Кох® <Еогт.Сгоир сопЁго11д="5Ёаг1:ирЫате*,> <Еогт. ЬаЬе1>8Ёаг1:ир пате</Еогт. ЬаЬе1> <Еопп.Сопёго1 р1асеИо1дег="Мо патез епсНпд 1п р1еазе"/> </Еопп.Сгоир> Поля формы обычно отображаются в одной строке и занимают всю доступную ши- рину экрана. Разместить больше одного поля в строке можно с помощью подком- понента Еопп.кои, как показано в листинге 6.9. Листинг 6.9. Использование подкомпонента гогш.ко» | <Еопп.Ком> <Еогт.Сгоир аз={Со1} соп1:го11с1="з1:аг1;ирЫап1е"> <Еогт. ЬаЬе1>8Ьаг1;ир пате</Еопп. ЬаЬе1> <Еогт.СопЁго1 р1асе1ю1с1ег="Мо патез епсНпд 1п______1у, р1еазе"/> </Еогт.Сгоир> <Еогт.Сгоир аз={Со1} соп1:го11с1=,,тагке1:"> <Еогт.ЪаЬе1>Магке1:</Еогт.ЬаЬе1> <Еогт.СопЁго1 р1асеИо1с1ег="е.д. зепгогз оп Т1к-Ток"/> </Еогт.Сгоир> </Еогт.Еои> Компонент Со1 обеспечивает правильные размеры меток и полей. Если нужно, что- бы поле формы применялось не для ввода, а для чего-либо другого, можно исполь- зовать атрибут аз, как показано в листинге 6.10. Листинг 6.10. Использование атрибута аз <Еопп.Сопёго1 аз="зе1ес1:" 6еГаи1ЛУа1ие="Скоозе... "> <ор1;1оп>Ргодгез5ДЛ7е меЬ аррИсаЁ1оп</орЁ1оп> <ор1:1оп>Сопзегуа!:1уе меЬ арр11са^1оп</ор111оп>
<орЕЕоп>Апс!гоЕс1 паЕЕуе</орЕЕоп> <орЕЕоп>Е08 паЕЕуе</орЕЕоп> <орЕЕоп>Ыем ^гзеу паЕЕуе</орЕЕоп> <орЕЕоп>УТ220</орЕЕоп> </Гогт.СопЕго1> Код, приведенный в листинге 6.10, создает элемент зе1есЕ в стиле ВооЫгар. Сложенные все вместе (листинг 6.11), эти фрагменты кода создают форму, пока- занную на рис. 6.2. УС ЕипсПпд Яед151гайоп ЯаПир пате Магке! Туре о! арр!гса1юп В|Мег О $дгип(1ед Боаа1 пеГшогкегз ЮЗпаЙуе V Оееспрйол Оке ТтНег гпее1з То! ТЬе ВгНет В агу-8ооа 1т) 1п1егТасе а)1ож$ уои Ю зКаге уоиг сштепГ е(а<и$ Ьу сЬсУпд а Щюг М ЬиПоп. Ией уег&оп (еаЧяез Кепск ап<1 ГаиагПез. ТесЬпо1од1е5 имб (сЬеск а( 1ез513) В1осксНаш I МасЫпе еагптд ОиапЫт сотрийпд I АиТопотоиз иекис1е* М/е’П Ье т ЮисЬ! * Опе оГ оиг раНпеге иЗ) Ье т 1оисЬ зИоП!у Рис. 6.2. Форма и диалоговое окно А1ег1, созданные посредством Реас1 ВооЫгар Листинг 6.11. Полный код формы, собранный из предыдущих фрагментов йпрог!: Еогт Егот ' геасЕ-ЬооЕзЕгар/Гопп' ЕтрогЕ Со1 Егот 1геасЕ-ЬооЕзЕгар/Со!1 трогЕ ВиЕЕоп Егот 'геасЕ-ЬооЕзЕгар/ВиЕЕоп' ЕтрогЕ А1егЕ Егот 'геасЕ-ЬооЕзЕгар/А1егЕ' ЕтрогЕ { изеЗЕаЕе } Егот 'геасЕ' ЕтрогЕ 1./Арр.сзз' ЕипсЕЕоп Арр() ( сопзЕ [зиЬтЕЕЕес!, зеЕЗиЬтЕЕЕед] = изеЗЕаЕе (ЕаЕзе) геЕигп ( <с!Еу с1аззНате="Арр"> <Ъ1>УС ГипбЕпд КедЕзЕгаЕЕоп</М> <Еогт> <Еогт.Вом> <Еогт.Сгоир аз={Со!} сопЕго11с!="зЕагЕирЫате,’> <Еогт.ЬаЬе1>ЗЕагЕир пате</Гогт.ЬаЬе1>
<Еопп.Сопёго1 р!асеко1с1ег= "Мо пашез епсИпд 1п р1еазе" /> </Еогш.Сгоир> <Еогш.Сгоир аз={Со1} сопЁго11<3="п1агкеЁ’'> <Еопп.ЬаЬе1>МагкеЁ</Еогт.ЬаЬе1> <Еопп.Сопёго1 р1асеко1с1ег= "е.д. зепЁогз оп Т1к-Ток" /> </Еопп.Сгоир> <Еопп.Сгоир аз={Со1} сопЁго!1с1="аррТуре',> <Еогт.ЬаЬе1>Туре оГ аррИсаЁ1оп</Еопп.ЬаЬе1> <Еопп.Сопёго1 аз="зе!есЁ" с!еЁаи1ЁУа1ие="Скоо5е.. ."> <орЁ1оп>Ргодгезз1Уе меЬ арр11саЁ1оп</орЁ1оп> <орЁ1оп>СопзегуаЁ1уе иеЬ арр11саЁ1оп</орЁ1оп> <орЁ1Оп>Апс1го1с1 паЁ1уе</орЁ1оп> <орЁ1оп>108 паЁ1Уе</орЁ1оп> <орЁ1оп>Ыеи ^гзеу паЁ1Уе</орЁ1оп> <орЁ1оп>УТ220</орЁ1оп> </Еогт.СопЁго1> </Еопп.Сгоир> </Еопп.Кои> <Еогт.Ром> <Еопп.Сгоир аз={Со!} сопЁго!1д="с1езсг1рЁ1оп',> < Еопп. ЬаЬе 1 >Ве з сг 1р111оп< / Еопп. ЬаЬе 1 > <Еопп.Соп11го1 а5='Ч:ех11агеа" /> </Еопп.Сгоир> </Еогт.Ком> <Еопп. Сгоир 1с1="Ёес1'1по1од1е5115ес1"> <Еогт.ЬаЬе1> ТесЪпоХодгез изес! (сЬеск а!: 1еаз1: 3) </Еогт.ЬаЬе1> <Еогт.Сопиго1 аз="зе1есЁ" ти11:1р1е> <ор1:1оп>В1оскска1п</орЁ1оп> <орЫоп>Маск1пе 1еагп1пд</ор1:1оп> <орЁ1оп>С)иагЛи1Г1 сотриЁ1пд</ор1:1оп> <орС1оп>АиГопо1поиз уеЪ1с1ез</орЁ1оп> <ор!;1оп>Еог-1оорз</ор1;1оп> </Еогт.Соп1го1> </Еогт.Сгоир> <ВиГЬоп уаг1апЁ="рг1тагу’' опСНск={ () => зекБиЬпйЁЁес! (1:гие) }> ЗиЬпйЛ </Ви1:11оп> </Еогт> <А1егГ з коы={зиЬпп ёёеё} уаг1апЁ="зиссезз" опС1озе={() => зеЁЗиЬпйЁЁес! (Га1зе)} сНзтЁззЁЫе
<А1ег1. НеасИпдЖе111 Ье 1п 1оисИ!</А1ег1:.Неас11пд> <р>Опе о! оиг рагЁпегз «111 Ье 1п 1оисЬ зЬогМу.</р> </А1ег1> </сНу> ) } ехрогХ с!е1аи11 Арр Обсуждение Набор инструментов Воо1$1гар для создания пользовательского интерфейса намного старше, чем платформа Ма1спа1 Оезщп, но до сих пор есть рыночные ниши, в ко- торых он предпочтительнее. Если разрабатываемое приложение должно выгля- деть как традиционный веб-сайт, этого можно добиться с помощью библиотеки ВооЫгар. Если же вам нужно что-то, имеющее вид межплатформенного приложе- ния, следует рассмотреть возможность использования библиотеки Ма1епа1-1Л3. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу Ьир8://оге11.1у/ХргБ’3, 6.3. Просмотр наборов данных посредством окна Кеас< У71пс1ош ЗАДАЧА Некоторые приложения отображают кажущийся бесконечный поток данных. На- пример, в приложениях наподобие ТхуЩег мы не хотим отображать все сообщения в ленте, поскольку это может занять несколько часов, дней или даже месяцев. Ре- шение этой проблемы — отображать данные отдельными окнами. При отображе- нии окна со списком элементов в памяти размещаются только отображаемые в на- стоящее время элементы. При прокрутке вверх или вниз приложение загружает данные, требующиеся для текущего представления. Но создание этой оконной логики — довольно сложная задача. Нам нужно не толь- ко тщательно отслеживать данные, отображаемые в настоящее время4, но также быть осторожным и эффективно кешировать оконные данные, чтобы не возникали проблемы с памятью. Как же можно реализовать возможность оконного просмотра данных в приложении Кеас1? РЕШЕНИЕ Данную задачу можно решить посредством библиотеки Кеас1 ХМшдоху. Эта библио- тека представляет собой набор компонентов для приложений, в которых нужно 3 Дополнительная информация предоставляется в разделе 5.1. 4 Включая обработку всех неприглядных граничных случаев, возникающих при изменении размера окна.
выполнять прокрутку больших массивов данных. Рассмотрим, как при помощи этой библиотеки создать большой список фиксированного размера5. Для начала нам нужно создать компонент, который будет отображать подробности одного элемента списка. В нашем примере приложения мы создадим набор из 10 000 строк дат. Каждая дата будет отрисовываться компонентом Бараком, который станет нашим отрисовщиком элементов. Библиотека Кеас! \Упи1о\\ отрисовывает только те элементы, которые присутствуют в текущем окне просмотра. При про- крутке списка вверх или вниз она будет создавать новые элементы по мере их по- явления в окне и удалять те, которые выводятся из окна. При вызове отрисовщика элементов библиотека Кеас1 У/1пс1о\у передает ему два свойства: номер элемента, начинающийся с цифры 0, и объект стиля. В листин- ге 6.12 приведен код отрисовщика элементов Ба^еКои. Листинг 6.12. Код отрисовщика элементов БаЬэНо» ХтрогЕ тотегЛ 1гот 'тотегЛ' сопзЪ Ба^еКои = ({ 1пс1ех, з!:у1е }) => ( ссИлг с1аззЫате={ 'аБа!:е ${1пс1ех % 2 && 'аБаЁе-обб'}'} зГу1е={з1:у1е}> {тотегй: () .асйЦпдех, 'д') . ГогтаЁ (’с!с1с1с1, ММММ Бо УУУУ')} </сИ\7> ) ехрогЪ деГаиЗЛ Ба1:еКои Данный компонент вычисляет дату, находящуюся через 1псЗех дней в будущем. Но в более реалистичном приложении этот компонент, скорее всего, загружал бы эле- мент даты с сервера бэкенда. Для генерирования самого списка используем компонент ПхедЗхгеЫз!:. Наш список должен иметь фиксированную ширину и высоту. Библиотека Кеас1 Мпс1о\у вычис- ляет количество видимых в окне элементов по высоте окна и высоте каждого эле- мента на основе значения атрибута НетЗхге. Например, если высота окна равна 400, а высота элемента зхегоНе1д1л равна 40, то список должен содержать только 10 или 11 компонентов Бакеном (рис. 6.3). В листинге 6.13 приведена конечная версия кода приложения. Листинг 6.13. Конечная версия кода приложения ипрогЁ { Г1хед31геЫз1: } Г г от 'геасЁ-ыхпдои' 1трог11 Ба^еНом Ггот './БаЪеКоы' хтрогР '. /Арр. сзз' Гипс1:1оп Арр() { 5 Посредством библиотеки можно создавать списки и матрицы как переменного, так и фиксированного раз- мера. Дополнительная информация предоставляется в документации по библиотеке (ЬПр$://оге!1.1у/ рСУад).
ге^игп ( <сИу с1аззНате="Арр"> <Е1хед312еЫз1: Ье1дЫ:={ 400} 1ЁешСоип1:= {10000} Иет31ге={40} и!сН±={300} > {Оа^еКои} </Г1хес1512еЫзЁ> </сНл7> ) } ехрогЁ (ЗеГаиИ; Арр Зип&у, Липе 6(Ь 2021 Молс1ау, Зиле 701 2021 ТиекОну, 1иле 8012021 1Ме4левс1ау, Ате 901 2021 ТКигаОау, Липе 1001 2021 Епйау, Ате 1101 2021 5а1иг0ау, Зиле 121Ь 2021 Випйау, Апе 13012021 МопОау, Зиле 1401 2021 Тиезйау, Зиле 15012021 Рис. 6.3. Список содержит только видимые в окне элементы Обратите внимание на то, что компонент НхеазггеЫзт не содержит экземпляра компонента Оатеком. Это объясняется тем, что для генерирования множественных элементов при прокрутке списка он должен использовать функцию Оаьекои. Поэто- му вместо компонента <БаТекоы/> в списке применяется сама функция {РатеКом}. Осталось только указать, что поскольку элементы добавляются в список и удаля- ются из него динамически, необходимо соблюдать осторожность при использова- нии селектора и-го потомка в С88: .аСа1е:гЛЬ-с1"п1с1 (еуеп) { /* Это неправильно */ Ьаскдгоипс1-со1ог: #еее; } Вместо этого нужно динамически проверять, является ли текущий индекс элемента нечетным, выполняя для этого деление по модулю 2, как показано в следующем примере: <сИу с1аззЫате={'аБа1;е ${1пс1ех % 2 && ' аРа^е-обсГ }'} ...>
Обсуждение Библиотека компонентов Кеас! \Утс1о\\ предназначена для решения узкого круга задач, но может быть полезной в случаях, когда нужно отображать огромные набо- ры данных. Ответственным за загрузку и кеширование отображаемых в списке данных продолжает оставаться разработчик, но это сравнительно простая задача по сравнению с отображением их динамически в окне, реализуемым библиотекой Кеас1 \Ушс1о\у. Исходный код данного рецепта можно загрузить на веб-сайте СкНиЬ по адресу Ьйр$://огеП.1у/1ХхуЗ. 6.4. Создание реагирующих диалоговых окон посредством библиотеки Ма1епа1-1Л ЗАДАЧА При использовании библиотеки компонентов существует высокая вероятность, что в определенный момент будет отображено диалоговое окно. Диалоговые окна по- зволяют вставлять дополнительную информацию в пользовательский интерфейс, не вызывая у пользователя чувства перехода на другую страницу. Они хорошо под- ходят для создания контента или как быстрое средство отображения дополнитель- ной информации об элементе. Но диалоговые окна не очень хорошо работают на мобильных устройствах. Мо- бильные устройства имеют небольшой экран, и диалоговые окна часто занимают много места у края для отображения фоновой страницы. Как можно создать реагирующее диалоговое окно, отображающееся в виде всплы- вающего окна на настольных устройствах, но в виде отдельной полноэкранной страницы на мобильных? РЕШЕНИЕ Библиотека Ма1епа1-1Л содержит компонент высшего порядка (Ы^Ьег-огбег сотропеЩ) юхьмоЬПеП1а1од, способный определять исполнение приложения на мобильном устройстве и соответственно отображать диалоговые окна как полноэк- ранные окна (листинг 6.14). Листинг 6.14. Использование компонента и'хитМоЬНеШаХод хтрогр { и1Р1тМоЫ1е01а1од } Г г от 1бта^егхаТ-их/соге1 сопз1; КезропзгуеВ1а1од = м1Ш4оЬ11е01а1од() ( ({ ГиНЗсгееп }) => { // Ве^игп зоте сотропеп!: изхпд М1е ^и115сгееп (1:гие/Га15е) ргорегЁу } )
Компонент и1ЁкМоЫ1еВ1а1од придает любому заключенному в него компоненту дополнительное булево свойство ыпзсгееп, посредством которого поведение ком- понента О1а1од можно изменять. Например, если передать свойство Еиххзсгееп ком- поненту охаход, как показано в листинге 6.15, то диалоговое окно будет отображать- ся по-разному при исполнении на мобильных и настольных устройствах. । Листинг 6.15. Передача свойства ГиШсгееп компоненту В1а1од ХтрогЕ { мХЕИМоЬХХеБХаХод } Егот '0таЕегХаХ-иХ/соге' ХтрогЕ ОХаХод Егот '@таЕегХаХ-иХ/соге/ВХаХод' ХтрогЕ ВХаХодТХЕХе Егот 'бтаЕегХаХ-иХ/соге/ЭХаХодТХЕХе' ХтрогЕ Туродгарку Егот 'бтаЕегХаХ-иХ/соге/ТуродгарНу' 1трог1: СХаХодСопЕепЕ Егот 'бтаЕегХаХ-иХ/соге/ВХаХодСопЕепЕ' ХтрогЕ БХаХодАсЕХопз Егот ' 0таЕегХаХ-иХ/соге/ОХаХодАсЕХопз' ХтрогЕ ВиЕЕоп Егот '0таЕегХаХ-иХ/соге/ВиЕЕоп' ХтрогЕ СХозеХсоп Егот '@таЕегХаХ-иХ/Хсопз/СХозе' сопзЕ ВезропзХуеБХаХод = мХЕЪМоЬХХеВХаХод() ( ({ опСХозе, ореп, ЕХЕХе, ЕиХХЗсгееп, сЬХХдгеп }) => { геЕигп ( <БХаХод ореп={ореп} ЕиХХЗсгееп={ЕиХХЗсгееп} опСХозе={опСХозе}> <ОХаХодТХЕХе> <ТуродгарЬу сотропепЕ=,,ЬХ" уагХапЕ="Х15" соХог="ХпИегХЕ" поИгар > {Е1ЕХе} </ТуродгарЬу> </ВХаХодТ1ЕХе> <СХаХодСопЕепЕ>{сЫХс1геп}</ОХаХодСопЕепЕ> <ВХаХодАсЕХопз> <ВиЕЕоп Vа^^апЕ="оиЕX^пес^" зЕагЕ1соп={<СХозе1соп />} опСХ1ск={опСХозе} СХозе </ВиЕЕоп> </БХаХодАсЕХопз> </ОХаХод> ) } ) ехрогЕ сЗеЕаиХЕ ВезропзХуеБХаХод
Предположим, мы модифицируем приложение, созданное нами в разделе 5.1.3 ис- ходном приложении щелчок по изображению в галерее открывает диалоговое окно, которое на мобильном устройстве будет выглядеть, как показано на рис. 6.4. Рис. 6.4. По умолчанию при исполнении на мобильном устройстве между краями диалогового окна и краями экрана есть пустое пространство 0е(аЛ5 А соип&у гоай X СЬОЗЕ Рис. 6.5. Модифицированное диалоговое окно заполняет весь экран мобильного устройства Если заменить стандартный компонент диалогового окна компонентом Незропз1уе01а1од, то на большом экране он будет отображаться таким же образом, но на мобильном устройстве заполнит весь экран, как показано на рис. 6.5. Такой подход не только создает дополнительное пространство для отображения содержимого диалогового окна, но также упрощает его восприятие пользователем мобильного устройства. В частности, оно будет ощущаться как отдельная страница, а не как всплывающее окно. Обсуждение Дополнительные идеи по применению реагирующих интерфейсов рассматриваются в разделе 2.1. Исходный код данного рецепта можно загрузить на веб-сайте бйНиЬ по адресу ЬНр$://огеН.1у/83612.
6.5. Создание консоли администратора посредством Кеас< Абгтпп ЗАДАЧА Разработчики уделяют так много времени разработке и поддержке приложений для конечных пользователей, что часто упускают из виду одну важную задачу: созда- ние консоли администрирования приложения. Консоли администрирования пред- назначены не для клиентов, а для персонала бэк-офиса и администраторов, осуще- ствляющих обзор текущих наборов данных и выясняющих причины проблем с данными в приложении для их устранения. Некоторые системы хранения данных, например ЕиеЬазе (Ьир8://огеП.1у/и^уЕ1), оснащены довольно продвинутыми встроенными консолями администрирования. Но это далеко не стандартный под- ход в случае с большинством служб бэкенда. Вместо этого разработчикам нередко приходится выяснять причины проблем с данными, напрямую обращаясь к базам данных, которые часто находятся под несколькими уровнями облачной инфра- структуры. Как же нам создать консоль администрирования почти для любого приложения Веас1? РЕШЕНИЕ Эту задачу можно решить при помощи фреймворка Кеас! Аскпт. Хотя данная глава посвящена библиотекам компонентов, Кеас1 Адпнп содержит намного больше, чем одни компоненты. Это фреймворк для разработки приложений, который облегчает задачу создания интерфейсов, предназначенных для исследования и обслуживания администраторами данных приложения. Разные приложения используют разные уровни сетевых служб, например К.Е8Т, ОгарЬрЬ или еще какую-либо из многих других систем. Но в большинстве случаев доступ к данным реализуется как к набору ресурсов, хранящихся на сервере. Фреймворк Кеас( Адтт содержит большинство компонентов, необходимых для создания приложения администрирования, которое позволит работать с каждым ресурсом, предоставляя возможности для создания и обслуживания данных, поиска требуемых данных, а также экспорта данных во внешние приложения. Чтобы продемонстрировать работу фреймворка Кеас1 Адпнп, мы создадим консоль для администрирования приложения доски объявлений (рис. 6.6), которое было разработано в главе 5. Службы бэкенда для этого приложения предоставляются простым сервером ОгарИрЬ. Сервер СгарИрЬ имеет сравнительно простую схему, которая определяет сообщения на языке описания схем, как показано в листинге 6.16. Листинг 6.16. Описание сообщения на основе схемы 1:уре Меззаде { 16: 1Б!
аикког: Зкгхпд! кехк: Зкгхпд! Каждому сообщению присваивается уникальный идентификатор щ а в строковых переменных кехр и аиРког сохраняются текст и автор сообщения соответственно. РАО I |АхЛН<» 1 С111е5. А1Г1 1—4 пИдо пои, вд1<эсг1Еу 1,155 рег кессэгнА, аг«* ЬЪе гвдХсп с? пленили йуплпис р|«ячигв пси. Я г?11ес Дсип гапде, 1? »11м ЫдЬг уе!ое1Су 4 + 0С0 рее аесопб. ГР<**| 5С Но11з сотр1е1е апй а рПсЬ $ ргодгат Опе ВЯА1/О. РАО Опе ВРАУО15 ап аЬог! соп1го! то<1е1. АИИиде 15 2 ггЛез САРСОМ АП |$ же11 а( Нои5(оп Уои аге доой а! 1 гтпи(е 5С РоПз сотр!е!е апд а рИсЬ 15 ргодгат. Опе ВКАУО РАО Опе ВКАУО15 ап аЬогГ соп!го1 тсн1е1. А№1иВе 15 2 тйез. САРСОМ АП <5 ие11 а! Ноиз(оп. Уои аге доо<1 а! 1 тти№. РАО Оомп гапде, 1 тПев АКНиде 3'4 тНев пом, уе1оа1у в 2,195 (ее! рег зесопй. № аге 1(1гоид(1 1Ке гедюп о! тахилит Йупаггпс ргеззиге пом 8 тЛев Вомп гапде, 12 тПе5 ЫдК, уе1осИу 4 000 (ее! рег зесопс!. I Рис. 6.6. Окно исходного приложения доски объявления В исходном приложении пользователь мог изменять данные только одним спосо- бом, а именно добавляя сообщение. Также мог исполняться только один тип запро- сов: чтение всех сообщений. Чтобы создать приложение с использованием Кеас1 Асйгнп, сначала нужно создать обычное приложение Кеас(, а затем в его папке установить библиотеку геаск-асЫп: $ прт 1пзЁа11 геаск-асЫп Основной компонент библиотеки называется АсЫп. Этот компонент будет оболоч- кой всего нашего приложения: <А<±п1п с1акаРгоУ1<Зег={... }> ...Сюда вставляется пользовательский интерфейс для отдельных ресурсов... </А<±П1П> Для работы компонента А<±п1п требуется источник данных — адаптер, который бу- дет подключать приложение к службе бэкенда. Наша служба бэкенда использует сервер СгаркОЬ, поэтому нам нужен соответствующий источник данных: $ прт 1пзка11 дгаркд1 $ прт 1П5ка11 га-с1ака-дгарЪд1-51тр1е Для большинства служб бэкенда существуют соответствующие источники данных. Подробная информация по этому вопросу предоставляется на веб-сайте фреймвор-
ка Кеас! Адтт (й11рз://огеП.1у/2ц1УУ). Прежде чем использовать источник данных, его нужно инициализировать. Конфигурирование сервера СгарЬрЬ осуществляется При ПОМОЩИ функции ЬиПбСгарЪОЬРгсллбег (листинг 6.17). Но поскольку эта функция работает в асинхронном режиме, то нужно следить за тем, чтобы она была в состоянии готовности, прежде чем вызывать ее. Листинг 6.1/Конфигурирование сервера (ЗгарЬОС йпрогЁ { Абттп } Ггот 'геасГ-абтхп' трогГ ЬиПбСгарЪ0ЬРгоУ1бег Ггот 'га-баГа-дгарЪцГ-зГтрГе' ГтрогГ { изеЕГГесГ, изеЗГаГе } Ггот 'геасГ' ГипсГхоп Арр() { сопзГ [баГаРгоутбег, зеГБаГаРгоухбег] = изеЗГаГе() изеЕГГесГ(() => { 1еГ бтбСапсе! = Га1зе ;(азупс О => { сопзГ бр = аиалГ Ьи11бСгарИ0ЬРгоУ1бег({ сПепГ0рГ1опз: { ига.: 'ЪГГр://1оса1ЪозГ:5000' }, }) 1Г (!б1бСапсе!) { зеГБаГаРгоухбег(() => бр) } }) О геГигп () => { бхбСапсе! = Ггие Ь []) геГигп ( <бгу с1азз№те="Арр"> {баГаРгоухбег && ( <Абт1п баГаРгоУ1бег={баГаРгоухбег}> ...гезоигсе 01 Неге... </Абт1п> )} </61у> } ехрогГ беГаиГГ Арр Источник данных подключается к серверу ОгарИрЬ, работающему на порту 5 0006. После подключения источник данных сначала загружает схему для приложения, 6 Код сервера можно найти в исходном коде для этой главы. Сервер запускается на исполнение командой побе ./зегуег.^з.
которая информирует его о доступных ресурсах (в данном случае только один ресурс, меззадез) и об операциях, которые он может выполнять с ними. Если запустить приложение на данном этапе, то оно не будет ничего делать. Это объясняется тем, что хотя оно и знает о присутствии на сервере ресурса меззадез, ему неизвестно, что с ним нужно что-либо делать. Поэтому нам нужно добавить ресурс Меззадез в приложение. Чтобы приложение отображало список всех сообщений на сервере, нам нужно соз- дать простой компонент Ыз^меззадез (листинг 6.18). Для формирования его интер- фейса потребуется несколько готовых компонентов библиотеки геаш-асЫп. Листинг 6.18. Создание компонента ЫвъМеззадез сопзЪ ЫзГМеззадез = (ргорз) => { геГигп ( <ЫзЪ {...ргорз)> <ВаТадг1д> <Техте1с1 зоигсе="1сГ /> <ТехСЕ1е1<1 зоигсе="аШИог" /> <ТехЪЕ1е1с1 зоигсе^’^ехТ" /> </ВаЁадг1д> </Ь1зЪ> С помощью этого компонента создается таблица со столбцами для идентификатора Ш сообщения, его автора ашког и текста ьех^. Теперь можно сообщить системе ад- министрирования о новом компоненте, передав атрибут кезоигсе компоненту Абпй.п: <Ас1т1п с1а1:а₽гоу1с1ег= {даОаРгоугдег} > <Кезоигсе пате^’Меззаде" 11зГ={ЫзГМеззадез}/> </А(±п1п> Компонент АсЫп обнаружит новый атрибут кезоигсе, обратится к серверу и считает с него сообщения, которые затем отрисует посредством компонента Ызгмеззадез (рис. 6.7). Может показаться, что обновление экрана осуществляется каким-то волшебством, но это потому, что сервер должен следовать определенным соглашениям, чтобы адаптер ОгарНрЬ знал, какие службы вызывать. В данном случае он находит запрос апмеззадез, который и возвращает сообщения (листинг 6.19). Листинг 6.19. Код запроса аНМеезадев 1:уре Оиегу { Меззаде (Ш: 10!): Меззаде аИМеззадез (раде: 1п1, регРаде: 1Ш, зогГЕ1е!с1: 8Ёг1пд, зогГОгдег: ЗГгхпд, (Шег: МеззадеЕНГег) : [Меззаде] }
Меззадез с е Мееаадее ♦ ЕХРОВТ □ И ф АШЬОГ тех: □ ЗЬс62804-1аН-44Я>-Ьа<И- ЬГЬЬОв02МЮ5 САРСОМ АН № ноийоп той аге дооО «1 1 тйкле. □ 21x80451-6843-450©- 9101-270908019744 ЙС НоПв сотрШе апй а рНсП 1в ргодгат Опа ВНАУО. □ 2аа»5б38-6<й>9-41ба- 9013-581749в89аЬ8 РАО Опе ВЯАУО № ап аЬол сол№1 тоде1. АНПида й 2 иШеа Ножа раг раде; ю ▼ 1-3013 Рис. 6.7. Отображение сообщений, полученных с сервера В результате вам может потребоваться изменить АР1-интерфейс бэкенда, чтобы он соответствовал требованиям вашего источника данных. Но добавленные службы, скорее всего, будут полезными для основного приложения. Запрос аНМеззадез позволяет интерфейсу администрирования обрабатывать постра- нично данные с сервера. Он может принимать свойство тсег и использовать его для поиска данных. Фильтр меззадеШбег в схеме примера приложения позволит консоли администрирования находить сообщения, содержащие строковые значения в переменных аиПюг и ЁехЁ. Он также даст возможность отправлять общую строку поиска (д) для поиска сообщений, содержащих строковое значение в любом поле. В листинге 6.20 приведено определение объекта меззадеШХег. Подобный объект фильтра нужно создавать для каждого ресурса разрабатываемого вами приложения. г । Листинг 6.20. Определение объекта фильтра меезадеШЪвг 1приЁ МеззадеШЁег { д: 2ЁГ1пд аиЁЬог: 2Ёг1пд ЁехЁ: ЗЁг1пд Чтобы разрешить фильтрацию и поиск на фронтенде, нужно сначала создать несколько полей фильтрации в компоненте, который мы назовем МеззадеШЁег (листинг 6.21). Несмотря на одинаковое название, это совсем иная сущность, чем одноименный объект в схеме, хотя она и содержит поля с одинаковыми названиями. ; Листинг 6.21. Определение компонента мэззадегнъег сопзё МеззадеГНЁег = (ргорз) => ( <ШЁег (...ргорз}> <ТехЁ1приЁ 1аЬе1="АиЁ1юг" зоигсе="аиЁког" />
<ТехЁ1приЁ 1аЬе1="ТехР" зоигсе="ЁехЁ" /> <ТехЁ1приС 1аЬе1="8еагсЬ" зоигсе="д" а1маузОп /> </Е11Ёег> Теперь МЫ можем добавить компонент МеззадеГШег В компонент Ьд-ЗЁМеззадез, в ре- зультате чего сможем постранично просматривать сообщения, фильтровать их и искать требуемые (рис. 6.8). Листинг 6.22. Добавление компонента МеззадеГИЪег В компонент ЫвЫезвадеб сопзё ХАзЁМеззадез = (ргорз) => { геЁигп ( <Ызё {...ргорз} Ё11Ёег5={<МеззадеГ1ХЁег />}> <ОаЁадг1с1> <ТехЁЕ1е1с1 зоигсе="1(1" /> <ТехРЕ1е1с1 зоигсе="аиЁЬог" /> <ТехЁЕ1е1с1 зоигсе="ЁехЁ" /> </ВаЁадгЁс1> </Ызё> ) } Меззадез Меззадез АиПтог (ген ♦ ехронт ЗеагсЪ □ К ф АиМог □ 2ЬсаИ4а1-ЙВ43-450е- 9101-279806019744 8С □ 2аа15в36-МЬ9-415а- 90*3-581749689ЯМ РАО |вХ( НоНв согпр1еш аге а рИсЬ 1з ргодгат. Опе ВНАУО. Опе ВНАУО 1з ап аЬол соп1го1 тойе1. А1Иибв 18 2 тИеа. Тех1 ношз рег раде. ю 1-2 012 Рис. 6.8. Фильтрация сообщении в таблице по автору или тексту Также можно получить возможность создавать новые сообщения. Для этого снача- ла создадим компонент СгеаЁеМеззаде, как показано в листинге 6.23. Листинг 6.23. Создание компонента СгеаЁеМеззаде сопзё СгеаЁеМеззаде = (ргорз) => { геЁигп ( <СгеаЁе Ё1Ё1е="СгеаЁе а Меззаде" {...ргорз}> <51тр1еГопп> <ТехЁ1приР зоигсе="аиТИог" />
сТех^ТпрсЛ тикШпе зоигсе="ЁехЁ" /> </51тр1еГогт> </СгеаЁе> ) } Затем добавим этот компонент в атрибут кезоигсе: <Кезоигсе пате="Меззаде" Из1:={Ыз1:Меззадез} сгеа11е={Сгеа1:еМеззаде}/> На рис. 6.9 показан конечный результат. Рис. 6.9. Создание сообщений в консоли администрирования Сообщения будет создавать источник данных СгарНрЕ, передавая содержимое формы СгеаСеМеззаде мутации, называющейся Сгеа1:еМеззаде (листинг 6.24). I Листинг 6.24. Мутация СхеаЪеМавваде 1:уре МиРартоп { сгеаЁеМеззаде( аиЫтог: 5Ёг1пд! ^ехС: ЗЁггпд! ): Меззаде } Подобным образом можно добавить возможность обновлять или удалять сообще- ния. При наличии сложной схемы с подресурсами средствами геаск-айпап можно отображать подэлементы в таблице, а также обрабатывать дисплеи разных типов и отображать изображения и ссылки. Фреймворк Кеас1 Адпнп также содержит ком- поненты для отображения ресурсов на календарях или в графиках. На рис. 6.10 по- казан пример такого отображения онлайновым демонстрационным приложением (Ьйр8://оге11.1у/ГтЕ>уЯ)7. Консоли администрирования также могут работать со- вместно с установленными системами безопасности. 7 Некоторые из таких компонентов доступны только подписчикам на версию фреймворка для предприятий.
$ СМ*» в кмйон| УУе1соте (о (Ье геасЬайгтип еп(егрг&е е<11(юп дегтю ТЫ* 1я Ше айгтпп о ап (таднагу рм№ аЬор »л тт»|пд <т№рИзо осйьоп р<гхя(е тойиЕоя мде Гее (гее о ехрЬге аги! той1(у I е с!а(а К а (оса! И уош сотриШг алй «1 геае! еаоЬ 1тм уои ге!ом1 р ЗЕЕ ТНЕ 5НСЖСА5Е | Й ВЕАСТ-АОМ1Ы ЕМТЕВРК1ВЕ ЕО1ТЮЫ вгге Ф ЕМПЦ8Н * 0 © Не* сииотеге 11 $ Лм**м« а МопЕЫу Неиепие ие$з,50б РеткИпд НвИет 6 ЯеиОгдега 19 С1шкЙа аоЕтаюп Сапдас РгПасН Мас НаЬег Вгк1де(СупсЬ ии!п Не*сЬе< Етегеол СоппеИу Рис. 6.10. Различные типы представлении данных в демонстрационном приложении 0 Обсуждение Хотя для работы консоли администрирования на основе геасе-асЬйп необходимо модифицировать бэкенд, добавив дополнительные службы, существует высокая вероятность, что эти новые службы также пригодятся и для основного приложения. Но даже если и не пригодятся, то предоставляемые этим фреймворком компоно- вочные блоки, скорее всего, сократят время, требуемое для разработки системы бэк-офиса. Исходный код данного рецепта можно загрузить на веб-сайте ОкНиЬ по адресу !Шр8://огеП.1у/2811Ьр. 6.6. Использование 5етапНс III вместо дизайнера ЗАДАЧА Качественное стилистическое оформление может значительно повысить визуаль- ную привлекательность приложения. И наоборот, плохое стилистическое оформле- ние способно заставить даже хорошее приложение выглядеть дешевой люби- тельской разработкой. У многих разработчиков8 очень ограниченные дизайнерские 8 В число которых входит, по крайней мере, один из авторов этой книги...
способности. Для таких разработчиков, которые не могут позволить себе привлечь профессионального дизайнера, наличие простой и четкой библиотеки компонентов пользовательского интерфейса позволит концентрироваться на функциональности приложения, не тратя бесконечные часы на корректирование расположения кнопок и границ. Проверенные временем фреймворки типа ВооЫгар могут предоставить хотя и про- стую, но качественную основу для большинства приложений9. Но даже такие фреймворки требуют уделять много внимания внешнему виду. Для тех, кто хочет сконцентрироваться на функциональности приложения, но при этом получить ак- куратный практический внешний вид, хорошим решением будет использование библиотеки 8етапВс VI. Но библиотека Зетапбс VI уже довольно старая, берет свое начало во времена цар- ствования фреймворка ]С?иегу. На момент работы над материалом данной книги она не обновлялась свыше двух лет. Так что же нам делать, если мы хотим совместить с Кеас! эту надежную, проверенную временем библиотеку Зетапбс 1)8? РЕШЕНИЕ Данная задача решается посредством использования библиотеки Зетапбс 1Л Кеас1. Эта библиотека представляет собой оболочку, которая делает библиотеку Бстапбс 1Л доступной для приложений КеасТ. Как можно судить по ее названию, библиотека Бетапйс 1Л концентрируется на значении интерфейса. В ней управление внешним видом осуществляется средства- ми С88, а не через компоненты. А компоненты библиотеки 8егпапбс VI, наоборот, концентрируются на функциональности. Например, при создании формы мы про- сто указываем, какие поля она должна содержать, не говоря ничего о том, где на форме их размещать. Это позволяет получить опрятный и единообразный внешний вид, требующий очень незначительной ручной корректировки, или вообще без нее. Чтобы начать работать с библиотекой 8етап11с 1Л, нужно установить саму библио- теку и составляющую для поддержки стилистического оформления: $ прт тпзРаН зетапЁ1с-и1-геасЁ зетап1:1с-и1-сзз Также необходимо вставить ссылку на таблицу стилей в файл тдех.|8 приложения, как показано в листинге 6.25. ---------------------------------------------------------------------------5 Листинг 6.25. Подключение таблицы стилей 1 ттрогЁ КеасР {гот 'геасТ' трогЁ КеасТБОМ {гот 1 геаср-скэт' пирог{ './1пбех.сзз' 1трог{ Арр {гот './Арр' 9 Руководство по использованию библиотеки ВооМгар в разрабатываемом приложении приводится в разде- ле 6.2.
ХшрогР герогРИеРА/ХЕаХз Егот './герогРИеЬХ/ХРаХз1 ХтрогЕ ' зетапРХс-иХ-сзз/зетапЕХс.тХп.сзз1 ВеасРООМ.гепс1ег( <ВеасР. 8РгХсРМос1е> <Арр /> </КеасР. ЗРгХсРМос1е>, доситепр. деРЕХетепРВуХс! (’ гооР') ) // Если вы хотите выполнять замеры поддерживаемых метрик производительности // в своем приложении, передайте соответствующую функцию в функцию // герогРИеЬУХРаХз(), чтобы сохранить результаты в журнале (например, // герогРКеЬ\7ХРа1з(сопзоХе.Ход)), или же отправьте ее // на узел аналитики(СоодХе). Дополнительную информацию // см. по адресу: ИРРрз://ЫР. 1у/СВА-чХРа1з герогРКеЬ71Ра1з() Для демонстрации работы с библиотекой БетапПс 1Л Кеас1 мы воссоздадим наше старое приложение для отправки сообщений. Нам потребуется форма с текстовыми полями для ввода имени автора сообщения и для ввода самого сообщения. Компо- ненты библиотеки БетапПс 1Л разработаны так, чтобы быть как можно более похожими на простые элементы НТМЬ. Таким образом, при создании формы мы импортируем компоненты Еопп, Хприр, техрдгеа и Вирроп для отправки сообщений (листинг 6.26). ХшрогР { ВиРРоп, Еопп, ХприР, ТехРАгеа } Егот 'зетапЕ1с-иХ-геасЕ' ХтрогЕ 1./Арр.сзз1 ХтрогЕ { изеЗРаРе } Егот 'геасР' ЕипсРХоп Арр() { сопзР [аиРЬог, зеРАиРЬог] = изеЗРаРе('') сопзР [РехР, зеРТехР] = изеЗРаРе('') сопзР [теззадез, зеРМеззадез] = изеЗРаРе([]) геРигп ( <с!гу с1аззМате="Арр"> <Еопп> <Еогт.ЕХе1с1> <1аЬе1 кРт1Еог="аиРЪог">АиРког</1аЬе1> <1приР уа!ие={аиРЬог} Хё="аиЕЬог" опСИапде={(еур) => зеРАиРЬог(еур.РагдеР.уа!ие)} /> </Еогт.ЕХе1с1> <Еопп.ЕХе1с1> <1аЬе1 ЪЕт1Гог="ЕехЕ’,>Меззаде</1аЬе1>
<ТехЁАгеа уа1ие={11ехи 1с1="Тех(/’ опСНапде={ (еу<:) => зеЁТехЁ (еу!:.Ёагде1:^а1ие)} /> </Еогтп.Г1е1с1> <ВиГХоп Ьазтс опСИск={() => { зе^Меззадез((го) => [ { 1соп: 'репсП', йаЁе: пек Ба11е () . 1о51:г1пд (), зиттагу: аи1±ог, ех^гаТех!;: 1:ехЁ, }, .. .т, ]) зеГАиМтог (' ') зе1:Тех11 ('') Н > РОЗЁ </ВиГХоп> </Гогт> </сИхл> ) } ехрогГ <1е^аи1Л Арр Этот код должен быть вам знаком. Компонент гогт имеет вспомогательное поле Пезд что немного упрощает группировку меток и полей, но, помимо этого, этот код выглядит похожим на код обычной формы НТМЬ. В примере приложения мы "отправляем" сообщения, добавляя их в массив теззадез. Возможно, вы заметили, что сообщения добавляются в этот массив в определенную объектную структуру, показанную в листинге 6.27. Листинг 6.27. Структура для добавления сообщений зе1:Меззадез ((т) => [ { 1соп: 'репсИ', даЁе: пек Ва1:е () . 1:о81:г1пд (), зиттагу: аи^Иог, ехТгаТехТ: Ьех!:, Ь . . .т, ])
Эти атрибуты не были выбраны каким-либо случайным образом. Хотя большинст- во компонентов библиотеки Зетапбс ЕЛ простого типа, она также содержит неко- торые более сложные компоненты, предназначенные для поддержки ряда распро- страненных ситуаций. Компонент гееб — один из таких. Он предназначен для отрисовки потока сообщений, наподобие сообщений в социальной сети ТулНег или 1пз1а§гат. Компонент отрисовывает последовательность сообщений, включая мет- ки времени, заголовки, значки и т. д. Конечный код нашего приложения с включе- нием в него компонента гееб приведен в листинге 6.28. Листинг 6.28. Конечный код приложения йпрогЕ { ВиЕЕоп, Еопп, 1приЕ, ТехЕАгеа, Ееед, } Егот 'зетапЕ1с-и1-геасЕ' йпрогЕ 1./Арр.сзз1 йпрогЕ { изеЗЕаЕе } Егот 'геасЕ' ЕипсЕгоп Арр() { сопзЕ [аиЕЪог, зеЕАиЕког] = изеЗЕаЕе('*) сопзЕ [ЕехЕ, зеЕТехЕ] = изеЗЕаЕе(*') сопзЕ [теззадез, зеЕМеззадез] = изеЗЕаЕе([]) геЕигп ( <сйу с1аззМате="Арр"> <Еогт> <Еогт.Е1е1д> <1аЬе1 ЬЕт!Гог="аиЕЪог">АиЕИог</1аЬе1> <1приЕ уа1ие={аиЕЪог} 1с1="аиЕЪог" опСЬапде={(еуЕ) => зеЕАиЕЪог(еуЕ.ЕагдеЕ.уаЕие)} /> </Гогт.Пе1с1> <Еогт. Пе1д> <1аЬе1 кЕт1Гог="ЕехЕ">Меззаде</1аЬе1> <ТехЕАгеа уа!ие={ЕехЕ] 1с1="ЕехЕ" опСЬапде={(еуЕ) => зеЕТехЕ(еуЕ.ЕагдеЕ.уа!ие)} /> </Еогт.Е1е1с1> <ВиЕЕоп Ьазхс
опСИск={() => ( зеЁМеззадез((го) => [ { хсоп: 'репсН', даЁе: пей БаЁеО .ЁоЗЁгЁпдО , зипипагу: аиЁког, ехЁгаТехЁ: ЁехЁ, }, .. .ш, ]) зеЁАиЁЬог('') зеЁТехЁ('') }) > РОЗЁ </ВиЁЁоп> </Гопп> <Гее<1 еуепЁз= {теззадез} /> ) } ехрогЁ с1еГаи1Ё Арр В результате исполнения этого кода получаем простой и аккуратно оформленный интерфейс (рис. 6.11). Аи№ог САРСОМ Мемэде АН 1в игаП а( НоивЮп. Уои аге дооб а11 тши!е} ___________________________________________________________________________________& РО51 А Уйм! 16 196919:33:31 СМТ+0100 (ВпШЬ ЗиттегТте) РАО Опе ВКАУОв ап аЬог1 соп!го1 тсх1е1. АИйийе 1в2 тЯе5. УУей 1и116 1969 19:32:00 СМТ+0100 (ВиИзЬ Зитптег Пте) 5С Ро|1$ сотр1е!е агх) а р!1сЬ !$ ргодгат. Опе ВРА1/О. ______________________________________________________________________________ Рис. 6.11. Интерфейс, созданный при помощи библиотеки Бетапйс 1)1
Обсуждение Хотя библиотека ЗетапПс 1Л довольно старая, это не означает, что она плохая. Ее проверенные временем средства позволяют создавать приятный и функциональный интерфейс. Это один из наилучших способов создать полноценное приложение, не прибегая к услугам визуального дизайнера. Эта библиотека особенно полезна при создании приложений типа Ьеап ЗТагШр, когда нужно быстро разработать тестовое приложение, чтобы проверить, существует ли рынок для вашего продукта10. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ьйр8://огеП.1у/де№ду. 10 Дополнительная информация на эту тему приводится в книге ТИе Ьеап 8(аг1ир, автор Эрик Райс (Епс Клея), издательство Сгохуп Виятеяя.
Безопасность В этой главе мы рассмотрим разные способы придания безопасности разрабаты- ваемым приложениям. Для этого мы опишем распространенные методы интегриро- вания приложения со стандартными системами обеспечения безопасности. Также мы выясним, как проверить код на отсутствие распространенных уязвимостей в системе безопасности. Во многих рецептах в этой главе мы будем использовать АР1-интерфейс стандарта ХУеЬАиШп для интегрирования приложения с устройст- вами безопасности, такими как сканер отпечатков пальцев и физические ключи. Эта интересная, но недостаточно применяемая технология может повысить как уровень безопасности разрабатываемого приложения, так и уровень восприятия приложения пользователем. 7.1. Защищаем запросы, а не маршруты ЗАДАЧА В разделе 2.6 мы увидели, как можно на основе средств маршрутизации Кеас! соз- дать безопасные маршруты. В частности, мы рассмотрели, как предотвратить несанкционированный доступ к определенным путям приложения, заставляя поль- зователей заполнить форму входа в систему. Защита маршрутов — это хороший, довольно общий подход на начальных этапах разработки приложения. Но некоторые приложения не поддаются легкому включе- нию в эту статическую модель безопасности. Некоторые страницы будут защищен- ными, а некоторые — нет. Но во многих приложениях легче защитить службы дан- ных, а не страницы. Иными словами, важно не то, на какой странице находится пользователь, а то, какие данные он просматривает. Все эти задачи обычно можно прямолинейно решить на уровне АР1-интерфейса. Но механизмы защиты нежелательно воспроизводить в алгоритмах клиента фронтенда. По этим причинам простой подход с обозначением одних страниц как требующих защиты, а других — нет, не будет достаточно хорошим. РЕШЕНИЕ Если классификации маршрутов на требующие и не требующие защиты недоста- точно для обеспечения безопасности разрабатываемого клиента, то полезно рас- смотреть возможность управления доступом к приложению, используя запросы на подтверждения, получаемые от сервера бэкенда.
При таком подходе изначально предполагается, что пользователю доступны все маршруты приложения, Мы не разделяем маршруты на требующие и не требующие защиты, а просто работаем с маршрутами. Если пользователь посещает маршрут, содержащий закрытые данные, сервер АР1 возвращает ошибку, обычно статус НТТР 401 (Доступ запрещен). Когда происходит такая ошибка, система безопасно- сти перенаправляет пользователя на форму входа на защищенную страницу. При этом подходе сервер АР1 устанавливает политику, какие маршруты являются закрытыми, а какие — открытыми. В случае изменения политик безопасности нуж- но только откорректировать должным образом код на сервере АР1, не меняя код клиента. Давайте освежим в памяти исходный рецепт защищенных маршрутов. В нашем приложении мы вставляем в файл Арр.]з компонент ЗесигахуРгч^Мег, который управляет безопасностью всех своих дочерних компонентов (листинг 7.1) Листинг 7.1. Добавление компонента 8всигх<у₽га»1двг в файл Арр.}» 1трог{ '. /Арр. сзз' 1трог{ { ВгомзегРои{ег, Кои{е, Зи1{ск } {гот ' геас{-гои{ег-с!от' 1трог{ РиЬИс {гот './РиЬИс' 1трог{ Рго^а{е1 {гот '. /Ргд.уа{е1' 1трог{ Рг1уа{е2 {гот './Ргал;а{е21 1трог{ Ноте {гот './Ноте' 1трог{ ЗесигНуРгоуЫег {гот ’. /ЗесигИуРгоухбег' 1трог{ 5есигеКои{е {гот './ЗесигеЕои{е' {ипс{1оп Арр() { ге{игп ( <с1гу с1аззНате="Арр"> <ВгоизегЕои{ег> <Зесиг1{уРгоу1с1ег> <3«1{сЬ> <Кои{е ехас{ ра{И="/"> <Ноте /> </Кои{е> <ЗесигеРои{е ра{И="/рг1Уа{е1"> <Рг1уа{е1 /> </ЗесигеРоиге> <ЗесигеНои{е ра{Ь="/рггуа{е2"> <Рг1уа{е2 /> </ЗесигеКои{е> <Еои{е ехас{ ра{Ъ="/риЫ1с"> <РиЬИс /> </Нои{е> </5м1{ск> </8есиг1{уРгоу1с1ег> </ВгомзегКои{ег>
</с11у> ) } ехрогГ 6еГаи1Г Арр Можно видеть, что приложение содержит открытые маршруты воигез и закрытые маршруты зесигесЗВоигез. При попытке пользователя получить доступ к закрытому маршруту он перенаправляется на форму для входа на данную страницу (рис. 7.1). Пользователь может получить доступ к закрытой странице (рис. 7.2), предоставив на этой форме правильные имя пользователя и пароль 1_од1п Раде Уои пеед 1о 1од ш. (ЬюГ 1гу 1гес1/ра55мог(3) Цзегпате) Раюутогсд 1| Рис. 7.1. При попытке получить доступ к закрытому маршруту необходимо заполнить форму входа РгКгайе раде 1 Н)дЫу 5есге( тГогтаНоп Ьеге. | Ьодоиг ] Рис. 7.2. Предоставив правильные имя и пароль, пользователь получает доступ к закрытой странице Для защиты, предоставляемой АР1-интерфейсом бэкенда, все закрытые маршруты ЗесигедКоиРез нужно заменить открытыми страницами воигез. Приложение просто не будет знать, какие данные являются закрытыми, а какие — открытыми до тех пор, пока сервер АР1-интерфейса не предоставит ему эту информацию. Приложение в этом рецепте будет иметь две страницы, содержащие смесь открытых и закрытых данных. Страница Тгап8асИоп8 будет считывать с сервера закрытые данные, а стра- ница О/уёг$— открытые. Модифицированный соответствующим образом файл Арр.)$ будет выглядеть, как показано в листинге 7.2. ГЛистинг 7.2. Модифицированный файл Арр.|$ ШрОГГ './Арр.сзз' шрогГ { ВгокзегВоиГег, ВоиГе, ЗыГГсИ } Ггот 'геасГ-гоиГег-бот' хтрогГ ТгапзасГГопз Ггот '. /ТгапзасГГопз' нпрогГ ОГГегз Ггот './ОГГегз' ГтрогГ Ноте Ггот './Ноте' ипрогГ ЗесигГГуРгоуГбег Ггот './Зесиг1ГуРгоУ1с1ег' ГипсГГоп Арр() ( геГигп ( <с!гу с1аззНате="Арр"> <ВгоызегВоиГег> <5есиг1ГуРп>у1с1ег> <Зи1ГсЬ> <ВоиГе ехасГ раГк="/">
<Ноте /> </КоиЁе> <РоиЁе ехасЁ раЁк="/ЁгапзасЁ1опз"> <ТгапзасЁ1опз /> </КоиЁе> <КоиЁе ехасЁ раЁЬ="/оЬЬегз"> <0ЬГегз /> </РоиЁе> </Зи1ёсН> </5есиг1ЁуРгоу1с1ег> </ВгоизегКоиЁег> </с!гу> ) } ехрогЁ с!еГаи1Ё Арр Нам также нужно внести изменения в компонент зесигЬЁуРгоуЫег. В модели безо- пасности на основе АР1-интерфейса клиент начинает работу, предполагая, что все данные находятся в открытом доступе, что противоположно подходу с закрытыми маршрутами, в котором предполагается отсутствие права доступа, пока пользова- тель не докажет обратное, предоставив правильные учетные данные. Это означает, что в исходном состоянии входа на страницу наш новый компонент 5есигИ1уРгоу1с1ег должен по умолчанию иметь значение Ёгие (листинг 7.3). Листинг 7.3. Модифицированный компонент 8®<^1фРгоу1<1ег ЬтрогЁ { изеЗЁаЁе } Ггош 'геасЁ' йпрогЁ ЗесигЁЁуСопЁехЁ Ьгот './ЗесигЁЁуСопЁехЁ' ЁтрогЁ ЬодЬп Ьгот '. /ЬодЁп' ЬтрогЁ ах!оз Ггот 'ах!оз' сопзё ЗесигЁЁуРгоуЩег = (ргорз) => { сопзё [1оддес11п, зеЁЬоддесИп] = изеЗЁаЁе (Ёгие) геЁигп ( <Зесиг1ЁуСопЁехЁ. РгоуЁс1ег Vа1ие={{ ГодЁп: азупс (изегпате, раззиогд) => { амаЁЁ ахЬоз.розЁ('/арЁ/ЬодЬп', { изегпате, раззмогд }) зеЁЬоддесИп(Ёгие) }, 1одоиЁ: азупс 0 => { ама1Ё ахЁоз.розЁ('/ар1/1одоиЁ') геЁигп зеЁЬоддесЦп(Ёа1зе) ), опГаИигеО { геЁигп зеЁЬоддесИп(ЬаЬзе) },
1оддед1п, Н (1оддес11п ? ргорз.сЪИдгеп : сЬодхп />} </8есиг1ЁуСопЁехЁ. Р]хл/1с1ег> ) } ехрогЕ деЁаи1Ё ЗесигхЁуРгоугдег В исходное приложение также были внесены следующие дополнительные измене- ния: ♦ Код, принимающий решение, должен ли пользователь заполнять форму входа, был перемещен в компонент весигзЛуР^Ыег. В исходном приложении этот код находился в компоненте ЗесигеЖоиЁе, но теперь он исполняется централизован- но. ♦ Мы заменили имитаторы проверки имени пользователя и пароля вызовами служб бэкенда /арШодш и /арШсдои!:. Но лучше всего будет заменить их кодом для предоставления доступа, используемым в вашей системе. ♦ Компонент 8есиг1ЁуРгоУ1дег теперь предоставляет новую функцию опГаПиге, ко- торая просто помечает пользователя как вышедшего из закрытой страницы. При вызове этой функции она заставляет пользователя выполнить вход. Но если у нас больше нет закрытых маршрутов, то в какой точке мы выполняем проверку для предоставления доступа? Теперь это осуществляется в самих вызовах АР1- интерфейса. В реальном приложении рекомендуется добавить код для обработки неуспешных . попыток входа. Здесь мы опустили такой фрагмент, чтобы уменьшить объем кода \ приложения. При неуспешной попытке входа пользователь остается на странице —формы входа без отображения каких-либо сообщений об ошибке. Теперь посмотрим, как выглядит код нового компонента тгапзасЁ1опз, хранящегося в файле 8Гс/Тгап8асбоп8.]8 (листинг 7.4). Этот компонент считывает данные тран- закций и отображает их на экране. Листинг 7~ м ЁшрогЁ изеТгапзасЁгопз Ёгот './изеТгапзасЁ1опз' сопзё ТгапзасЁЁопз = () => { сопзё { йаЁа: ЁгапзасЁтопз } = изеТгапзасЁхопз() геЁигп ( <сНу> <М>ТгапзасЁ1оп5</Ъ1> <та1п> <ЁаЫе> <ЁЬеас1> <ЁГ> <ЁИ>РаЁе</Ёк>
<ГЬ>АтоипГ</Гй> <Гк>безсг1рГ1оп</Гк> </Гг> </Гкеас1> <ГЬос1у> {ГгапзасГГопз && ГгапзасГГопз.тар((Ггх) => ( <ГГ> < Гс1> { Ггх . баГе}</Гб> < Гс1> {Ггх. атоипГ} </Гс1> < Гс1> {Ггх. дезсгхрГГоп} </Гс1> </Гг> ))} </ГЬос1у> </ГаЫе> </та!п> </сНу> ) } ехрогГ деГаи1Г ТгапзасГгопз Хук изетгапзасрхопз содержит сетевой код для считывания данных с сервера. Как раз в этот хук нам нужно добавить проверку на ответ 401 (Запрещено) от сервера (листинг 7.5). Листинг 7.5. Добавление в хук иееТгапзасРхоп проверки на ответ от сервера ипрогГ { изеЕГГесГ, изеЗРаРе } Ггот ’геасР' йпрогР аххоз Ггот 1ахГоз' ипрогР изеЗесигхРу Ггот './изеЗесиггГу' сопзР изеТгапзасПопз = О => { сопзР зесигГГу = изеЗесигГГу() сопзР [ГгапзасГГопз, зеГТгапзасГГопз] = изеЗРаРе([]) изеЕГГесГ(() => ( ; (азупс () => { Ггу { сопзГ гезиГГ = анаГГ ахГоз.деГ('/арГ/ГгапзасПопз') зеГТгапзасГГопз(гези1Г .<1аГа) } саГсЬ (егг) { сопзГ зГаГиз = егг.гезропзе && егг.гезропзе.зГаГиз ГГ (зГаГиз === 401) { зесигГГу. опЕаПиге () } // Сюда вставляется код для обработки других исключений. (Рассмотрите возможность использования общего обработчика ошибок. Подробности см. в другом месте в этой книге.) }
}) О }, []) геЁигп { с1аЁа: ЁгапзасЁтопз } } ехрогЁ с!еЁаи1Ё изеТгапзасЁЁопз В приложении для обращения к серверу мы используем библиотеку аххоз. Эта биб- лиотека обрабатывает как исключения такие ошибки НТТР, как ошибка 401 (статус НТТР ЫпаиШопгес! — Запрещено). В результате немного легче определить, какой код обрабатывает неожиданный ответ. При использовании АР1-интерфейса другого стандарта, например ОгарЬС^Ь, обработку ошибок безопасности можно реализовать аналогичным образом, исследуя содержимое объекта ошибок, возвращаемого сер- вером СгарЬрЬ. При получении от сервера ответа, что доступ к ресурсу запрещен, хук изеТгапзасЁюпз вызывает функцию опГаПиге ИЗ компонента 5есипЁуРгсл/1с1ег. Страница ОГГегз создается таким образом. Код в файле 8гс/Ойег8.]8 будет занимать- ся форматированием данных оПегз, предоставляемых сервером (листинг 7.6). Листинг 7.6. Код компонента о^Гегв нпрогЁ изеОЁЁегз Ггогп './изеОГГегз1 сопзё ОГЁегз =()=>{ сопзё { йаЁа: оЁЁегз } = изеОГЁегзО геЁигп ( <сИу> <М >01 Ёег з</Ы > <та1п> <и!> {оГГегз && оГЁегз.тар((оГЁег) => <11 с1аззЫате="о1Ёег">{о11ег}</11>)} </и1> </та!п> </сНу> ) } ехрогЁ с1еЁаи1Ё ОНегз А данные считываются хуком изеОИегз, код которого находится в файле 8гс/и8еОЙ'ег8.)8 (листинг 7.7). Листинг 7.7. Кед хука иаиЮ^Геха нпрогЁ { изеЕЁ^есЁ, изеЗЁаЁе } 1г от ’геасЁ' ЁтрогЁ аххоз 1г от 'ахгоз' йпрогЁ изеЗесиг1Ёу 1г от './изеЗесигхЁу'
сопзё изеОГГегз =()=>{ сопзё зесигхЁу = изеЗесиггЁу () сопзё [оЁЕегз, зеЁОЯегз] = изеЗЁаЁе ([]) изеЕГЕесЁЦ) => ( ;(азупс () => { Игу { сопзё гези1Ё = ама1Ё ах!оз.деЁ ('/арх/оПегз') зеШИегз (гези1Ё.с1аЁа) } саЁсИ (егг) { сопзё зЁаЁиз = егг.гезропзе && егг.гезропзе.зЁаЁиз (зЁаЁиз ===== 401) { зесиггЁу. опГаНиге () } // Сюда вставляется код для обработки других исключений. (Рассмотрите возможность использования общего обработчика ошибок. Подробности см. в другом месте в этой книге.) } )) О ), []) гебигп { «Заба: оЕЕегз } ) ехрогЁ с!еЁаи1Ё изеОЕЕегз Хотя конечная точка /ар|/оЯегз не защищена, у нас предусмотрен код, который про- веряет на наличие нарушений безопасности. Одно из следствий подхода к обеспе- чению безопасности с использованием АР1-интерфейса— все конечные точки • должны рассматриваться как защищенные просто на случай, если они станут за- щищенными в будущем. При запуске нашего приложения открывается домашняя страница (рис. 7.3). Ноте • ЗНоуу Уап5ас110Л5 • $Ноуу оЯег$ Ёодои! ОНегв • Ргее 1агс1 • Виу опе, де! опе вате рпсе • Виу Паю, де1 опе Рис. 7.3. Домашняя страница приложения Рис. 7.4. Список предложений, отображаемый при щелчке по ссылке ОНегз При щелчке по ссылке ОГГегз отображается список предложений, считанный с сер- вера (рис. 7.4). Эти данные открытые, поэтому нам не нужно выполнять вход для их просмотра. Если теперь возвратиться обратно на домашнюю страницу и щелкнуть по ссылке ТгапзасГюпз, то приложение предложит нам выполнить вход на эту страницу, от- крыв соответствующее окно (рис. 7.5).
При попытке загрузки данных страницы транзакций с сервера появляется ответ 401 (Запрещено). Программа рассматривает это как исключение и вызывает функ- цию опГаПиге из компонента 8есиг1пуРгоу1с1е, которая и отображает форму входа на рис. 7.5. Ьодт Раде Уои пееб Ю 1од !п. {Ып1:Тгу (гед/разз^огс!) Рис. 7.5. Для доступа к странице Тгапзасбопз необходимо предоставить правильные учетные данные Тгапзасйопз Оа№ АтоигН ОезспрОоп 2023-12-04 3.45 СоНее 2023-12-05 6.15 Веагс! оП Рис. 7.6. После успешного входа страница Тгапзасйопв становится доступной После ввода имени пользователя и пароля приложение отправляет их на сервер. При условии, что это не вызывает ошибку, компонент ЗесипТуРгоукЗег скрывает форму входа, выполняется повторная отрисовка страницы Тгапзасйопз (рис. 7.6), и мы получаем доступ к ее данным, поскольку выполнили вход. Обсуждение Теперь наше приложение не содержит ничего, что указывало бы, какие АР1-интер- фейсы закрытые, а какие — нет. Вся эта работа выполняется на сервере. Безопас- ность приложения теперь полностью в ведении конечных точек АР1-интерфейса. При таком подходе все вызовы АР1 должны обрабатываться единообразно в аспек- те безопасности. Одно из достоинств размещения вызовов АР1 в специализирован- ных хуках состоит в возможности использования всеми хуками общего кода обес- печения безопасности. Хуки могут вызывать другие хуки, при этом общий подход заключается в создании хуков, работающих как вызовы общего назначения сет и розт1. Хук общего назначения сет может не только обрабатывать ошибки доступа, но также содержать запросы отмены, очистку сетевых запросов (см. разделы 5.3 и 5.6), а также общую обработку ошибок (см. раздел 4.1). Еще одно достоинство подхода с использованием АР1-интерфейса для обеспечения безопасности— возможность полного отключения защиты в некоторых обстоя- тельствах. Например, защиту можно отключить на стадии разработки приложения, устранив необходимость настройки поставщика идентификации для разработчиков. Также можно создавать различные конфигурации защиты для разных разверты- ваний. Наконец, для автоматизированных систем тестирования, например Сургезз, кото- рые могут эмулировать сетевые ответы, тестирование функциональности приложе- ния можно отделить от тестирования нефункциональных аспектов, таких как обес- 1 Или в случае ОгарЬрЬ аксессоры и мутаторы.
печение безопасности. Целесообразно выполнить дополнительное тестирование безопасности только серверной стороны, отдельное от тестирования пользователь- ского интерфейса, чтобы убедиться в безопасности самого сервера. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу 1Шр$://огеП.1у/Ву\УУХ. 7.2. Аутентификация посредством физических ключей ЗАДАЧА Для обеспечения безопасности одних учетных данных не всегда достаточно, по- скольку их могут украсть или угадать. Поэтому некоторые пользователи могут от- давать предпочтение только приложениям, предоставляющим дополнительный уровень безопасности. В настоящее время все больше систем предоставляют возможность двухфакторной аутентификации. Система двухфакторной аутентификации требует, чтобы для входа пользователь сначала предоставил свои учетные данные, а затем еще какую- либо дополнительную информацию. Такой дополнительной информацией может быть код, отправленный пользователю в 8М8-сообщении, или одноразовый пароль, создаваемый приложением на его смартфоне. Или же, что еще более безопасно, вторым фактором может быть использование физического устройства-ключа, на- пример УиЫКеу (йПр$://уупэу.уиЫсо.сот), подключаемого в нужное время к ком- пьютеру и активируемого нажатием кнопки. Подобные физические ключи применяют шифрование с открытым ключом, в кото- ром генерируется открытый ключ для данного приложения, а сообщения зашифро- вываются с помощью секретного ключа. Приложение может отправить устройству произвольную проверочную строку, генерируя подпись посредством секретного ключа. Затем приложение может использовать открытый ключ, чтобы проверить правильность подписи строки. Но как можно интегрировать такие возможности физических ключей в приложения КеасГ? РЕШЕНИЕ Широко поддерживаемый2 стандарт ХУЗС для аутентификации в Интернете \\^еЬ АшЬепбсабоп (также называется ХУеЬАшИп) позволяет браузеру взаимодействовать с физическими устройствами наподобие ключей УиЫКеу. Аутентификация в Интернете осуществляется в два этапа. Первый этап называется аттестацией (айезЫйоп). В процессе аттестации пользователь регистрирует в приложении устройство обеспечения безопасности. А на втором этапе, называю- 2 За исключением браузера 1п1егпе( Ехр1огег.
щимся утверждением (аззегбоп), пользователь может подтвердить свою личность, чтобы войти в систему. Рассмотрим сначала аттестацию. На этом этапе пользователь регистрирует физиче- ское устройство по своей учетной записи. Это означает, что на всем протяжении этого этапа пользователь должен находиться в системе под своей учетной записью. Код для данного рецепта содержит эмуляцию сервера Коде, который можно запус- тить из папки зегуег кода приложения: $ сс! зеп/ег $ прт 1пзЕа11 $ прт гип зЕагЕ Аттестация содержит три шага: 1. Сервер генерирует запрос на аттестацию, указывая разрешенный тип устройства. 2. Пользователь подключает устройство и активирует его, например, нажимая на нем кнопку. 3. Устройство генерирует ответ, содержащий открытый ключ, который отправля- ется на сервер, где он сохраняется под учетной записью пользователя. Узнать, поддерживает ли конкретный браузер стандарт УУеЪАиШп, можно, прове- рив наличие интерфейса мгпдои.РиЬИсКеуСгедепЕга!. Запрос на аттестацию на сервере создается конечной точкой Поэтому начнем с вызова этой точки: хтрогк ахгоз Егот 'ахгоз' // Запрос начать регистрацию физического ключа для текущего пользователя сопзЕ гезропзе = амагЕ ахгоз.розЕ (’ /зЕагЕКедгзЕег') В листинге 7.8 приведен код запроса на аттестацию. Листинг 7.8. Код запроса на аттестацию "грЫате": "РИузгса! Токеп Зегуег", "грЮ": "1оса1козЕ", "изегЮ": "1234", "изегЫате": "Егеда", "ехс1ис!еСгес1епЕга1з": [ {"гсГ: "ехгзЕгпдКеу1", "Еуре": "риЫгс-кеу") ], "аиЕЬепЕгсаЕогЗе1есЕгоп": { "изегУеггЕгсаЕгоп": "дгзсоигадесГ }, "ехЕепзгопз": { "сгедРгорз": Егие }
Названия некоторых атрибутов начинаются с букв гр..что означает ге1аут$раг1у, т. е. передающая сторона. Передающая сторона — это приложение, которое сгене- рировало запрос. Переменной грЫаше присваивается произвольная текстовая строка, описывающая приложение. Идентификатору грМ следует присвоить значение в виде имени теку- щего домена. В данном случае это 1оса1Ьозс, поскольку мы работаем с сервером для разработки. Строка изегю— однозначный идентификатор пользователя, а строка изегКаше — ИМЯ пользователя. Атрибут ехс1идеСгес1епЁ1а1з довольно интересный. Пользователи могут регистриро- вать по своим учетным записям по несколько устройств. Значение данного атрибу- та — это список уже зарегистрированных устройств, чтобы не регистрировать одно и то же устройство дважды. При попытке зарегистрировать одно и то же устрой- ство более одного раза браузер немедленно создаст исключение, сообщая, что устройство уже зарегистрировано где-то в другом месте. Атрибут аи^ЬепМсаМопЗеТеЛхоп позволяет задавать разные действия, которые поль- зователь должен выполнить при активировании своего устройства. В данном слу- чае атрибуту изегУегШсаШоп присвоено значение ^а1зе, чтобы не допустить выпол- нения пользователем каких-либо дополнительных шагов (например, ввода ПИН) при активировании устройства. Следовательно, при запросе подключить свое уст- ройство к компьютеру пользователь вставит его в разъем П$В и нажмет на нем кнопку, без необходимости выполнять какие-либо другие действия. Расширение сгейРгорз запрашивает устройство возвратить дополнительные свойст- ва учетных данных, которые могут быть полезными для сервера. После того как сервер сгенерирует запрос аттестации, нужно дать указание пользо- вателю подключить свое устройство безопасности к компьютеру. Это делается сле- дующей функцией браузера: пау!да1:ог. сгейепМаТз. сгеа^е () Функция сгеаьео принимает в качестве параметра объект запроса аттестации. К сожалению, данные в этом объекте должны быть разных двоичных типов низкого уровня, например массивы байтов. Мы можем значительно упростить себе жизнь, установив с депозитория ОйНиЬ библиотеку ыеЬаи<±п-]зоп, которая позволит пода- вать запрос в формате 18О1Ч: $ прт 1пзТа11 "@д1РЪиЬ/кеЬаи1±п-35Оп" Затем мы сможем передавать содержимое запроса ХУеЬАщНп версии функции сгеате о с ОйНиЬ, как показано в листинге 7.9. Листинг 7.9. Передача запроса \Л/еЬАиШп функции сгеаЬэ О хтрогр { сгеа^е } Г г от '@д1ЁЪиЬ/иеЬаиЫ1п-ззоп' гтрогЁ ах!оз Ггот 1ах1оз' // Запрос начать регистрацию физического ключа для текущего пользователя сопз!: гезропзе = аиазЛ ахгоз .розЁ (' /з^агЁКедгзЁег1)
// Передаем конфигурацию ИеЬАибЬп функции сгеабе() библиотеки ыебаиббт-^зоп сопзб аббезбаббоп = аыабб сгеабе({ риЫбсКеу: гезропзе.баба }) В этой точке браузер дает указание пользователю подключить свое устройство безопасности к компьютеру и активировать его (рис. 7.7). Й<.Я|,1ег 5епд гезропзе бодои* Рис. 7.7. При вызове функции сгеабе () браузер дает пользователю указание подключить физический ключ Функция сгеабе () возвращает объект аттестации, который можно рассматривать, как регистрационную информацию для устройства. Сервер может использовать объект аттестации для проверки личности пользователя при его входе в систему. Нам нужно зарегистрировать данный объект аттестации по учетной записи пользо- вателя. Для этого мы передаем его обратно концевой точке /ге^бег нашего сервера (листинг 7.10). Листинг 7.10. Регистрация объекта аттестации бтрогб { сгеабе } бгот 'йдббЬиЬ/иеЪаибИп-дзоп1 бтрогб ахбоз бгот 'ахбоз' // Запрос начать регистрацию физического ключа для текущего пользователя сопзб гезропзе = аиабб ахбоз.розб(1/збагбКедбзбег') // Передаем конфигурацию ИеЬАибЪп функции сгеабе() библиотеки меЬаибЬп-^зоп сопзб аббезбаббоп = аиабб сгеабе ({ риЫбсКеу: гезропзе.даба }) // Отправляем подробности физического ключа УиЬбКеу для регистрации по учетной записи пользователя сопзб аббезбаббопКезропзе - аиабб ахбоз.розб('/гедбзбег', { аббезбаббоп, }) Так выглядит процесс регистрации нового устройства безопасности для пользова- теля. Но куда его вставить в коде приложения? Наше приложение имеет страницу АссоиШ ра&е (рис. 7.8), и в эту страницу мы до- бавим кнопку для регистрации нового физического ключа. А в листинге 7.11 приведен код для регистрации физического ключа.
Ассоип!раде Ред!в1ег нем Нагдмаге кеу Кед1з1ег Еодои( Рис. 7.8. Кнопка для регистрации нового физического ключа на странице АссоипЕ раде Листинг 7.11. Код для регистрации физического ключа шрогЕ { изеЗЕаЕе } Егот 'геасЕ' шрогЕ ЬодоиЕ Егот './ЬодоиЕ' шрогЕ ахЁоз Егот 'ахгоз' шрогЕ { сгеаЕе } Егот ’@дЕЕкиЬ/меЬаиЕкп-]зоп' сопзЕ РгЕуаЕе2 =()=>( сопзЕ [Ьизу, зеЕВизу] = изеЗЕаЕе(ЕаЕзе) сопзЕ [теззаде, зеЕМеззаде] = изеЗЕаЕе() геЕигп ( <с11У с1аз5Ыате="Рг1УаЕе2"> <Ы>АссоипЕ раде</Ы> {мЕпдои.РиЫЕсКеуСгейепЕЕа! && ( <р>КедЕзЕег пем Ьагдиаге кеу</р> <ЬиЕЕоп опСИск={азупс () => { зеЕВизу(Егие) Егу { сопзЕ гезропзе = аыаЕЕ ахЕоз.розЕ('/зЕагЕКедЕзЕег') зеЕМеззаде('Зепд гезропзе') сопзЕ аЕЕезЕаЕЕоп = амаЕЕ сгеаЕе({ риЫЕсКеу: гезропзе.баЕа, )) зеЕМеззаде('СгеаЕе аЕЕезЕаЕЕоп') сопзЕ аЕЕезЕаЕЕопРезропзе = амаЕЕ ахЕоз.розЕ( '/гедЕзЕег', { аЕЕезЕаЕЕоп, ) ) зеЕМеззаде (' гедЕзЕегес!!') ЕЕ ( аЕЕезЕаЕЕопКезропзе.баЕа && аЕЕезЕаЕгопЕезропзе.даЕа.уег1Е1ед ) { а!егЕ('Ыем кеу гедЕзЕегес!') )
} саЁсЪ (егг) { зеШеззаде ('' + егг) } Лпа11у { зеСВизу(^а1зе) } )) <11заЫед={Ьизу} КедгзЁег </Ьи1±оп> </> )} <сИу с1аззМате="Ассоип11-теззаде"> {теззаде }</д1У> <Ьодои1: /> </<±И7> ) ) ехрогХ с1е1аи1Л Рг1уа(:е2 При нажатии кнопки Ке^1$1ег на странице Ассоип1 ра%е браузер дает указание под- ключить устройство безопасности (рис. 7.9). Недйег 8епс1 гезропзе 1одои1 Рис. 7.9. Диалоговое окно с инструкциями по регистрации физического устройства После подключения и активирования устройства ключа приложение отправляет учетные данные устройства на сервер, а затем выводит сообщение о регистрации нового ключа (рис. 7.10). Теперь рассмотрим следующий этап— утверждение. Утверждение происходит, когда пользователь подтверждает свою личность при входе в систему. Шаги этого процесса во многом похожи на шаги для аттестации: 1. Приложение дает указание серверу создать запрос на утверждение. 2. Пользователь преобразует этот запрос в объект утверждения, активируя свое устройство безопасности.
3. Сервер проверяет утверждение на соответствие сохраненным на нем учетным данным, чтобы удостовериться, что данное лицо является тем, кем оно себя называет. Рис. 7.10. Сообщение о регистрации нового физического ключа Начнем рассмотрение этого процесса с первого шага, на котором создается запрос на утверждение. В листинге 7.12 приведен код запроса на утверждение. Листинг 7.12. Код запроса на утверждение "аИоиСгебепГхаТз": [ {"16": "еххзПпдТокепЮ", "Гуре": "риЬИс-кеу"} ], "аГГезкаПоп": "дхгеск", "ехГепзгопз": { "сгедРгорз": кгие, }, "грЮ": "1оса1Ьозк", "Пшеоик": 60000, "скаНепде": "зотеКапдотЗкггпд" Атрибут а11омСгебепг1а1з содержит массив допустимых зарегистрированных уст- ройств. Браузер использует этот массив для проверки, подключил ли пользователь правильное устройство. Запрос на утверждение также содержит строку сЪаПепде; это произвольная строка, для которой устройство безопасности должно создать подпись, используя свой сек- ретный ключ. Сервер проверяет действительность этой подписи, используя откры- тый ключ, чтобы убедиться в том, что подключено корректное устройство. Значение переменной Пшеоиг определяет время, в течение которого пользователь будет иметь возможность подтвердить свою личность.
При вызове конечной точки /§1аг1Уеп1у с использованием определенного иденти- фикатора пользователя изегю сервер генерирует запрос на утверждение: впрогГ ахГоз Ггот 'аххоз' //Запрашивает контрольное слово, чтобы проверить изегЮ пользователя сопзГ гезропзе = аматГ аххоз.розГ('/зГагГУегтГу', { изегЮ }) После этого запрос на утверждение можно передать функции дег () из библиотеки иеЬаи{±п-]зоп, которая дает пользователю указание подтвердить свою личность, подключив допустимое устройство (листинг 7.13). Листинг 7.13. Передача запроса на утверждение функции деЪ() 1трогГ { деГ } Ггот 1@д1ГЬиЬ/меЬаиГНп-]зоп' ГтрогГ ах1оз Ггот 'ахгоз1 сопзГ гезропзе = аиаЮ аххоз.розГ ('/зГагГ7ег1Гу', { изегЮ }) сопзГ аззегПоп = аиатГ деГ({ риЬПсКеу: гезропзе.баГа }) Результат исполнения функции дег о показан на рис. 7.11. © 1оса1Ьо5Г:3000/рГ1У31е2 1_од!п Раде 1оса№ов1 шап1в нЛКап&са!* уои ишпд а годз1агвд мсигЛу кау. Уои сап соппесТ ап4 аШЬопи опе под ог сапсе!. Уои пеед (о 1од (п. Сапсе! Цзегпате: Разодогс!: ..... едп Рис. 7.11. Функция дег () дает указание пользователю подключить устройство Функция дег о возвращает объект утверждения, который содержит подпись для контрольной строки сЬаПепде, отправленной обратно конечной точке /геи Гу сервера для проверки подписи. Ответ на вызов этой функции сообщит нам, подтвердил ли пользователь свою личность (листинг 7.14). Листинг 7.14. Подтверждение личности пользователя ГтрогГ { деГ } Ггот 1йдГГкиЬ/меЬаиГИп-дзоп1 Гтрогг аххоз Ггот 'ахтоз' сопзГ гезропзе - аиаГГ ахгоз .розГ (' /зГагГ7ег1Гу', { изегЮ }) сопзГ аззегГтоп = амахГ деГ ({ риЬИсКеу: гезропзе.баГа })
сопзё гезр2 = аиагЁ ахгоз.розЁ(1 ЛгегИу’, { изег!Р, аззегЁгоп }) г{ (гезр2.ёаЁа && гезр2.с!аЁа.уегг{гес1) { // Пользователь успешно подтвержден } Где в приложении можно вставить этот код? Наш пример приложения основан на рецепте защищенных маршрутов3. Он содер- жит компонент 5есиг1луРг^1с1ег, который управляет обеспечением безопасности всех своих дочерних компонентов. Компонент зесиггЁуРгоугбег предоставляет функцию Ходди, которая вызывается с параметрами изетате и раззиога, когда поль- зователь отправляет форму входа со своими учетными данными. В этот компонент мы и вставим код верификации, как показано в листинге 7.15. Листинг 7Л 5. Вставка кода верификации в компонент зесиггЁуРгоугвег гтрогЁ { изеЗЁаЁе ) {гот 'геасЁ' гтрогЁ ЗесиггЁуСопЁехЁ {гот './ЗесиггЁуСопЁехЁ' гтрогЁ { деЁ } {гот ’@дШ1иЬ/меЬаи{Ьп-]зоп’ гтрогЁ ахгоз {гот 'ахгоз' сопзё ЗесиггЁуРгоугаег = (ргорз) => { сопзё [1оддесПп, зеЁЬоддесЛп] = изеЗЁаЁе({а!зе) геЁигп ( <8есиг1ЁуСопЁехЁ.Ргоугаег уа!ие={{ 1одгп: азупс (изегпате, раззмогб) => { сопзё гезропзе = аыагЁ ахгоз.розЁ('/1одгп', { изегпате, раззкогб, )) сопзё { баЁа } = гезропзе 1{ (даЁа.ЁмоГасЁогНеедед) { сопзё изегТБ = ёаЁа.изегТП сопзё гезропзе = аыагЁ ахгоз.розЁ('/зЁагЁУегг{у', { изегЮ, }) сопзё аззегЁгоп = амагЁ деЁ({ риЫгсКеу: гезропзе. с!аЁа }) сопзё гезр2 = аиаЁЁ ахгоз.розЁ (’ЛгегИу', { изегЮ, аззегЁгоп, )) г{ (гезр2.6аЁа && гезр2.с!аЁа.уегг{гес1) { зеЁЪоддесЦп(Ёгие) ) 3 См. раздел 2.6.
} е1зе { зеЁЬоддесИп (Ёгие) } }, 1одоиЁ: азупс () -> { аиахЁ аххоз.розЁ('/1одоиЁ') зеЁЬоддесИп (ЬаЬзе) Ь 1оддес11п, }} > {ргорз. сЬИйгеп) </Зесиг1ЁуСопЁехЁ. Ргоу1с1ег> ) } ехрогЁ деЁаиХЁ ЗесигЁЁуРгоухдег Имя пользователя изегпате и пароль раззмогд отправляются конечной точке /1о^т. Если для пользователя есть зарегистрированное устройство безопасности, то в от- вете на вызов /1о$1п значению атрибута ЁкоГасЁогНеедед будет присвоено значение Ёгие. Затем вызываем конечную точку /з1аг1УепГу, передавая ей в качестве парамет- ра идентификатор 1Ь пользователя, и используем полученный запрос на утвержде- ние, чтобы дать пользователю указание активировать свое устройство. Полученное утверждение отправляется обратно на сервер. Если нет никаких ошибок, присваи- ваем переменной ЬоддесИп значение Ёгие и отображаем защищенную страницу. Рассмотрим весь этот процесс в действии. Предположим, что мы уже зарегистри- ровали физический ключ по нашей учетной записи. Открываем приложение и щел- каем по ссылке АссошИ ра^е (рис. 7.12). 1_од1п Раде Ноте • РиЬПс Раде • Рпуаге Раде 1 • Ассоип! раде Уои пеес! 1о 1од 1п. изегпате: Тгеда Р355УУОГС1:|«""" | Ьодт Рис. 7.12. На домашней странице приложения щелкаем по ссылке Ассоип1 раде Рис. 7.13. Форма для входа на закрытую страницу Страница АссошП ра^е закрытая, поэтому нам нужно запросить имя пользователя и пароль (рис. 7.13). В примере приложения имя пользователя Ггес1а, а пароль — развмогсЁ После ввода имени пользователя и пароля и нажатия кнопки Ьо§т на странице Ра^е браузер дает указание подключить устройство безопасности (рис. 7.14).
© | О «о ♦ 1оса№о51:3000/рг№<е2 ••• » кодт Раде Той пеед То 1од <п. (осЫЬок* шагйс 1о аи1Ьвпйс»1в уои иппд а гед蹫<1 мсигИу кеу. Уои сап соппас1 апй аиЖолвв опе по*», ы сапса! Сапсв1 изегпате: пеаа . -1- - Раззхл/огск........ 1_одт Рис. 7.14. Браузер дает пользователю указание активировать его устройство безопасности После подключения и активирования устройства безопасности пользователь полу- чает доступ к закрытой странице (рис. 7.15). Ассоип!раде Ред151ег пеуу Ьагйууаге кеу [ Редеет'] [ кодой! Рис. 7.15. После подтверждения своей личности пользователь получает доступ к странице Ассоип! раде Обсуждение Как вы, наверное, уже поняли, АР1-интерфейс \УеЬАи1йп довольно сложный. В нем используется довольно неясная терминология (аттестация вместо регистрации и утверждение вместо верификации), а также несколько низкоуровневых типов данных, работать с которыми, к счастью, не придется благодаря библиотеке ыеЬаикЬп-з зоп. Вся сложная часть находится на сервере. Сервер примера приложения задействует библиотеку 8ппр1е\УеЬАи1Нп для обработки большей части криптографической со- ставляющей. Если вы планируете применять библиотеку 81тр1е\УеЬАи1Ьп для сер- верной части своего приложения, будет полезно знать о наличии клиентской биб- лиотеки 8ппр1е\УсЬАи1Ип, работающей совместно с серверной. В данном примере мы не использовали эту библиотеку в исходном коде клиента, чтобы не делать код слишком привязанным к ней. При реализации двухфакторной аутентификации нужно подумать о том, что делать в случае, если пользователь потеряет свое устройство безопасности. С технической точки зрения все, что нужно сделать, чтобы реактивировать его учетную запись, это удалить устройство, зарегистрированное под его учетной записью. Но лучше всего быть предельно осторожным, поскольку типичный взлом двухфакторной
аутентификации злоумышленник осуществляет, позвонив в службу поддержи от имени владельца физического ключа и сообщив о его потере. Поэтому нужно будет создать достаточно строгую процедуру, в процессе которой выполняется удостоверение личности любого лица, запрашивающего сброс учет- ной записи. Исходный код данного рецепта можно загрузить на веб-сайте бйНиЬ по адресу Ьйр8://огеП.1у/В1у5В. 7.3. Работа с протоколом НТТРЗ ЗАДАЧА Протокол НТТРЗ наиболее часто встречается в рабочей среде, но иногда бывают обстоятельства, когда может быть полезным использовать этот протокол в процессе разработки. Например, некоторые сетевые службы могут работать только из стра- ниц, защищенных протоколом НТТРЗ. А технология ШеЬАийш может работать удаленно только через протокол НТТРЗ4. Если приложению требуется прокси- сервер с НТТРЗ, то его код может постепенно заполниться ошибками и возникнут проблемы. В настоящее время разрешение протокола НТТРЗ на рабочих серверах сравнитель- но прямолинейная задача5, но как это сделать на сервере для разработки? РЕШЕНИЕ Для приложений Кеас1, созданных посредством сгеаие-геас±-арр, протокол НТТРЗ можно разрешить следующим образом: ♦ создав самозаверенный ЗЗЬ-сертификат; ♦ зарегистрировав этот сертификат с используемым сервером для разработки. Чтобы создать самозаверенный сертификат, нужны некоторые знания о том, как работает протокол НТТРЗ. Протокол НТТРЗ — это просто протокол НТТР, пропускаемый через туннель за- шифрованного ЗЗЬ-подключения (Зесиге 8оске1§ Ьауег— протокол защищенных сокетов). Когда браузер подключается к сайту с НТТРЗ-адресом, он открывает со- единение с защищенным сокетом на сервере6. Сервер должен предоставить серти- фикат, изданный организацией, которой браузер доверяет. Если браузер примет этот сертификат, он отправит зашифрованные данные на защищенный сокет на сервере, которые будут там расшифрованы и переданы далее серверу НТТР. 4 Эту проблему можно обойти на устройствах Апс1го1<1, подключая их через компьютер для разработки. См. раздел 7.7. 5 Дополнительную информацию см. на веб-сайте ЬеСз Епсгур! (ЬИр*>://1еГчепсгу рГогц). 6 По умолчанию это будет на порту 443.
Главная трудность с организацией сервера НТТР8 состоит в получении сертифика- та, которому браузер будет доверять. Браузер хранит набор корневых сертифика- тов, изданных крупными надежными организациями. Когда НТТР8-сервер предос- тавляет браузеру сертификат, этот сертификат должен быть подписан одним из корневых сертификатов браузера. Чтобы создать 88Ь-сертификат, сначала нужно сформировать корневой сертификат и дать указание браузеру доверять этому сер- тификату. Затем нужно создать сертификат для нашего сервера разработки, подпи- сав его созданным ранее корневым сертификатом. Если это выглядит сложно и запутанно, то значит, так оно и есть. Начнем с создания корневого сертификата. Для этого на вашем компьютере нужно будет установить криптографическую библиотеку Ореп88Ь. Чтобы создать файл секретного ключа, нужно исполнить команду орепзз1. После этого необходимо дважды ввести парольную фразу (листинг 7.16). Листинг 7.16. Создание файла секретного ключа $ орепзз! депгза -дезЗ -оиЬ шукеу.кеу 2048 СепегаЫпд В8А рггуаЬе кеу, 2048 Ы€ 1опд гаосЫиз ...............................+++ ...............................+++ е 13 65537 (0x10001) ЕпЬег разз рЬгазе !ог шукеу.кеу: УегНухпд - ЕпЬег разз рЬгазе Гог шукеу.кеу: $ Созданный файл шукеу.кеу содержит секретный ключ, который можно использо- вать для шифрования данных. А файл ключа позволяет создать файл сертификата, содержащий информацию об издавшей его организации и конечной дате его дей- ствия. Файл сертификата создается, как показано в листинге 7.17. Листинг 7.17. Создание файла сертификата $ орепзз! гед -х509 -пей -побез -кеу шукеу.кеу -зЬа256 -бауз 2048 -ои! шуреш.реш ЕпЬег разз рЬгазе Гог шукеу.кеу: Уои аге аЬоиЬ Го Ье азкеб Го епГег 1пГоггааГ!оп ГЬаГ «Ш Ье 1псогрогаГес1 хпГо уоиг сегГхПсаГе гедиезГ. ИЬаГ уои аге аЬоиГ Го епГег !з мЬаГ 13 саИед а 0!зГ!пди!зЬес1 Каше ог а ОН. ТЬеге аге дихГе а Ген Г!е!с1з ЬиГ уои сап 1еаVе зоше Ыапк Еог зоше Пе1дз ГЬеге «Ш Ье а деГаи1Г уа!ие, 1Г уои епГег '.', ГЬе Г!е!с1 «Ш Ье 1еГГ Ыапк. Введите фразу пароля для файла шукеу.кеу: Далее нужно будет ввести информацию, которая будет вставлена в ваш запрос сертификата.
То, что вам нужно будет ввести, называется различимое имя (или РИ) . Будет несколько полей ввода, но некоторые из них можно оставить пустыми Некоторые из полей ввода будут содержать значение по умолчанию Если в поле ввести ’.', то оно будет оставлено пустым Соипбгу Ыате (2 1еббег соде) []:03 Збабе ог Ргоубпсе Иаше (би11 пате) []:МаззасЬизеббз ЪосаИбу Иате (ед, стбу) [] :СатЬг1бде Огдапбгаббоп Мате (ед, сотрапу) [] :О'КеШу МесНа Огдапбгаббопа! Бпбб Мате (ед, зесбгоп) []:Нагт1езз зсггЬез Соттоп Мате (ед, би11у диаИббес! Иозб пате) [] :Ьоса1 ЕтаН Аддгезз []:те@ехатр1е.сот $ В данном случае мы создаем сертификат, который будет действителен в течение следующих 2048 дней. Запрашиваемая здесь парольная фраза — это фраза, введен- ная при создании файла шукеу.кеу. Данные об организации не имеют значения, поскольку мы будем применять этот сертификат только на нашей локальной ма- шине. Сертификат хранится в файле турет.рет; этот файл нужно будет установить в ка- честве корневого сертификата на вашем компьютере7. Установить корневой серти- фикат на компьютере можно несколькими способами8. Корневой сертификат мож- но использовать для подписания сертификатов веб-сайтов, чем мы и займемся далее. Сначала создадим файл локального ключа и файл С8К (СегйЯсабе Кециез! — запрос на подписание сертификата), как показано в листинге 7.18. Листинг7:18. Создание файла локшшногрключдифайигш СЙЯ -7 ж.' * ~ $ орепзз1 гед -пей -зЪа256 -пойез -оиб тургбуабе.сзг -пеыкеу гза:2048 \ -кеуоиб тургбуабе. кеу \ -зиЬ] ’7С=иЗ/ЗТ=Маззаскизеббз/Ь=СатЬгб6де/0=0'Кеб11у \ Мес1ба/0и=Нагт1езз зсгбЬез/СМ=Ьоса1/етаб1Аддгезз=те@ехатр1е.сот" Сепегаббпд а 2048 Ьбб КЗА ргбуабе кеу мгбббпд пей ргбуабе кеу бо 'тургбуабе.кеу' $ 7 Расширение рет означает Рпуасу-ЕпЬапсед МаП — электронная почта повышенной секретности. Формат РЕМ изначально предназначался для использования с электронной почтой, но в настоящее время служит в качестве общего формата для хранения сертификатов. 8 Подробная информация на эту тему предоставляется в руководстве компании ВоипСА (Ьир$://огеН.1у/ 9ЯМН).
Далее создадим файл ех(Я1е.1х[ и вставим в него содержимое, приведенное в лис- тинге 7.19. Листинг 7.19. Содержимое файла ех№1е.1х1 аиЁког1ЁуКеу1с1епЁ1Г1ег=кеу1с1, хззиег Ьаз1сСопзРгагп1:з=СА: ГАЕ8Е кеуизаде=<Ид1ка181дпакиге, попКерисИаМоп, кеуЕпсхрЬеппепк,йа^аЕпсхркеппепк зиЬ]есРА1РКате=0Ы8: 1оса1ЬозЁ Наконец, исполним команду, которая создаст 88Ь-сертификат для нашего прило- жения (листинг 7.20). Листинг 7.20. Создание сертификата для приложения | $ орепзз! х509 -гед -1п шурггуаке.сзг -СА шурет.рет -САкеу тукеу.кеу \ -САсгеа1:езег1а1 -сгЛ \ турггуаЁе.сгё -бауз 500 -зЪа256 -ех^Не ./ехРШеАхЬ 51дпа1:иге ок 8иЬ]есЁ=/С=и5/8Т=МаззасЬизеЁ^з/Ь=СатЬг1(1де/0=0,Ке111у МесИа/011=Нагт1езз зсг1Ьез/СЫ=Ъоса1/ етаИАс1(1гезз=те@ехатр1е. сот Се^кхпд СА РгхуаЁе Кеу Епкег разз ркгазе ^ог тукеу.кеу: $ Помните, что указываемая здесь парольная фраза разз рЬгазе — это фраза, введен- ная при создании файла тукеу.кеу. В результате мы получим два файла, при помощи которых сможем обезопасить наш сервер для разработки: ♦ тург^а1е.С11— сертификат, подписанный корневым сертификатом, который убеждает браузер, что тот может доверять нашему приложению; ♦ тург1Уа1е.кеу — секретный ключ, который будет использоваться для шифрова- ния соединений между сервером для разработки и браузером. Для приложений, созданных при помощи средства сгеа^е-геаск-арр, протокол НТТР8 можно разрешить, добавив следующий код в файл *.еуп в папке приложе- ния: НТТР8=Ёгие 88Ь_СКТ_Г1ЬЕ=тург1уа1:е. сгё 88Ь_КЕУ_Е1ЕЕ=турга^аЁе.кеу Если теперь перезапустить сервер, то приложение будет доступно по адресу Ы1р$://1оса1Ьо$1:3000 вместо ЬКр://1оса1Ьо81:3000.
Обсуждение Задача создания самозаверенных сертификатов довольно сложна, но в некоторых ситуациях без таких сертификатов не обойтись. Но даже если вам и не требуется протокол НТТР8 для вашей среды разработки, все же может быть полезным разо- браться, что собой представляет этот протокол, как он работает и почему ему мож- но доверять. Исходный код данного рецепта можно загрузить на веб-сайте СтйНиЬ по адресу Ьйр8://огеП.1у/ВАКАЕ. 7.4. Аутентификация посредством отпечатка пальца ЗАДАЧА В разделе 7.2 мы рассмотрели, как можно использовать для двухфакторной аутен- тификации физические ключи, например устройства УиЫКеуз. Но в настоящее время физические ключи встречаются все еще сравнительно редко и могут быть довольно дорогостоящими. С другой стороны, большинство людей уже обладают мобильными устройствами, такими как сотовые телефоны или планшеты, многие из которых оснащены встроенными сканерами отпечатков пальцев. Теперь выяс- ним, как можно реализовать двухфакторную аутентификацию в приложении Кеас1 с помощью сканера отпечатков пальцев. РЕШЕНИЕ Данную задачу можно решить, применяя сканер отпечатка пальцев как ключ аутентификации ХУеЬАиЙт. Он подключается к АР1-интерфейсу таким же образом, хотя требует изменения нескольких конфигурационных настроек. Данный рецепт основан на материале из раздела 7.2. Как вы узнали из этого разде- ла, процесс аутентификации с использованием АР1-интерфейса стандарта \УеЬЛи1Кп включает два основных процесса: ♦ аттестация — пользователь регистрирует физическое устройство по своей учет- ной записи. Одним из способов аттестации может быть сканирование отпечатка пальца на смартфоне пользователя; ♦ утверждение — пользователь активирует свое устройство безопасности, или физический ключ, а сервер проверяет, что оно соответствует ранее зарегистри- рованному устройству или ключу. Как аттестация, так и утверждение состоит из трех этапов: ♦ Сервер генерирует запрос на аттестацию или утверждение. ♦ Пользователь активирует ключ, с помощью которого формируется ответ. ♦ Ответ отправляется на сервер.
Чтобы переключиться со съемного физического ключа на встроенный в мобильное устройство сканер отпечатка пальца, нужно изменить только поток запроса на атте- стацию. В запросе на аттестацию указывается тип ключа, который браузер может зарегистрировать для пользователя. Для рецепта с использованием съемного физи- ческого ключа, такого как УиЫКеуз, мы создали запрос на аттестацию, который выглядел, как показано в листинге 7.21. Листинг 7.21. Запрос на аттестацию для ключа УиЫКеуе { "грКате": "РЬуз1са1 Токеп Зегуег", "гр!В": "ХосаЦюзТ", "изегЮ": "1234", "изегИате": ’Тгеба", "ехс1ис1еСгес1епР1а15": [ {"1д": "ех1зР1пдКеу1", "Руре": "риЬИс-кеу"} ], "аиРкепР1саРогЗе1есР1оп": { "изегУегШсаРгоп": "сНзсоигадесГ }, "ехРепзгопз": { "сгедРгорз": ргие, Ь } Этот же запрос пригоден и со сканером отпечатков пальцев, нужно лишь слегка модифицировать его, как показано в листинге 7.22. Листинг 7.22. Запрос на аттестацию для сканера отпечатков пальцев { "грИате": "РЪузгса! Токеп Зегуег", "грЮ": "1оса1ЪозР", "изегЮ": "1234", "изегЫаше": "Ггейа", пехс1идеСгес1епМа1з": [ {"1сГ: "ех1зР1пдКеу1", "Руре": "риЬИс-кеу"} 1, "аиРЪепР1саРогЗе1есР1оп": { "аиРИепРхсаРогАРРасктепР": "р1аРГопп", "изег7ег1Г1саР1оп": "гедиггесГ }, "аРРезРаРгоп": "сЦгесР", "ехРепзгопз": { "сгедРгорз": Ргие, },
За исключением пары изменений, эти два запроса на аттестацию практически оди- наковы. Первое изменение касается выбора аутентификатора. Теперь мы хотим за- действовать аутентификатор рЕаЕЕогт, поскольку сканер отпечатков пальцев встро- ен в устройство и не поддается отсоединению. Это означает, что мы фактически даем возможность пользователю работать исключительно с текущим физическим устройством. В противоположность, физический ключ УиЫКеу можно отсоединить от одного устройства и подключить к другому. Мы также указываем, что необходимо реализовать прямую аттестацию. Это озна- чает, что нам не потребуется дополнительная верификация. Например, после ска- нирования отпечатка пальца пользователю не нужно будет вводить ПИН. В целом, за исключением изменения исходного объекта запроса на аттестацию, весь остальной код остается таким же. Когда пользователь отвечает на запрос на аттестацию, прижав палец к сканеру отпечатков пальцев, сканер сгенерирует от- крытый ключ, который будет сохранен по учетной записи пользователя. При по- следующем входе пользователя в систему и подтверждении своей личности скани- рованием отпечатка пальца, сканер создаст контрольную строку таким же образом, как это сделал бы ключ УиЫКеу. Таким образом, если планируется поддержка одного типа аутентификатора, целе- сообразно позволить пользователям применять как сканер отпечатков пальцев, так и съемные физические ключи. Если только съемный физический ключ не работает также и с мобильными устрой- ствами, например посредством МЕС-связи, маловероятно, что какой-либо пользо- ватель зарегистрирует как съемный физический ключ, так и отпечаток пальца. * Зарегистрировав отпечаток пальца, он не сможет войти в систему и зарегистриро- вать съемный физический ключ, и наоборот. В листинге 7.23 приведен модифицированный код компонента для регистрации фи- зического ключа, позволяющий регистрировать отпечаток пальца. I Листинг 7.23. Код компонента, модифицированный для регистрации | отпечатка пальца ЁтрогЕ { изеЗЕаЕе } Егот 'геасЕ' ЕтрогЕ ЬодоиЕ Егот 1./ЬодоиЕ' пироги ахЬоз Егот 'ахЬоз' ЬтрогЕ { сгеаЕе } Егот '@д1ЕЬиЬ/меЬаиЕЬп-]зоп' сопзё Рг1уаЁе2 = ()=>{ сопзё [Ьизу, зеЕВизу] = изеЗЕаЕе(ЕаЬзе) сопзё [теззаде, зеЕМеззаде] = изеЗЁаЁе() сопзё гедЁзЁегТокеп = азупс (зЁагЁКед1зЁгаЁ1опЕп<1ро1ПЁ) => { зеЕВизу(Егие) Ёгу { сопзё гезропзе = аиаЬЕ ахгоз.розЁ (8ЁагЁВед1зЁгаЁ1опЕпфо1пЁ) зеЕМеззаде('Зепй гезропзе') сопзЕ аЁЕезЕаЕЬоп = амаЬЕ сгеаЕе({ риЬИсКеу: гезропзе.с!аЁа }) зеЕМеззаде('СгеаЕе аЕЕезЁаЕЕоп')
сопзё аЁЁезЁаЁгопКезропзе = аиаЁЁ ахгоз.розЁ('/гедгзЁег', ( аЁЁезЁаЁгоп, }) зеЁМеззаде ('гедгзЁегеб!') И ( аЁЁезЁаЁгопКезропзе. баЁа && аЁЁезЁаЁгопКезропзе.даЁа.уег!Леб ) { а1егЁ('Меи кеу гедгзЁегеб') } } саЁск (егг) { зеЁМеззаде('' + егг) } ЁгпаНу { зеЁВизу(1а1зе) } } геЁигп ( «Ил/ с1аззЫате="Рг1УаЁе2,,> <Ы>АссоипЁ раде</Ь1> {игпбои.РиЫгсКеуСгебепЁга! && ( <р>КедгзЁег пен Ъагбнаге кеу</р> <ЬиЁЁоп опС1гск={ () => гедгзЁегТокеп (' /зЁагЁКедгзЁег')} бгзаЫеб={Ьизу) > КедгзЁег КепкгоаЫе Токеп </ЬиЁЁоп> сЬиЁЁоп опС1гск={ () => гедгзЁегТокеп (' /зЁагЁГгпдегрггпЁ')} бгзаЫеб={Ьизу} > КедгзЁег ЕгпдегрггпЁ </ЬиЁЁоп> )} <с!гу с1аззЫал1е="АссоипЁ-теззаде"> {теззаде }</с1гу> <1юдоиЁ /> </с!гу> ) } ехрогЁ с!еЁаи1Ё Рг1уаЁе2 Код листинга 7.23 отличается от исходного тем, что для регистрации отпечатка пальца вызывается другая концевая точка. Во всех остальных отношениях код остается прежним.
Чтобы испытать этот код в действии, требуется наличие устройства со сканером отпечатков пальцев. При исполнении приложения на локальном компьютере или удаленном сервере через протокол НТТР8 допустим только стандарт ХУеЬАнЙш. Для тестирования этого кода с мобильным устройством необходимо настроить протокол НТТР8 на сервере разработки (см. раздел 7.3), или же на устройстве нуж- но будет настроить прокси-сервер, чтобы перенаправлять подключения к 1оса1ко81 на ваш сервер для разработки (см. раздел 7.7). Для запуска примера приложения, нужно перейти в его папку и запустить сервер для разработки: $ прт гцп зЁаг1: Также необходимо запустить АР1-сервер. Для этого нужно открыть отдельный тер- минал, перейти в подпапку сервера и запустить его: $ сс! зегуег $ прт гип зЁаг!: Сервер для разработки будет исполняться на порту 3000, а АР1-сервер — на порту 5000. Сервер для разработки будет перенаправлять АР1-запросы АР1-серверу. Открываем приложение и щелкаем по ссылке Ассошй ра§е (рис. 7.16). Ноте • РиЬНс Реве • Рп уаТе Раде 1 • Ассоип! раде 1_од1п Раде Уои леей То 1од (п. Озетате: (гейа Ра&змогс!:... 1лдт Рис. 7.16. На домашней странице приложения нужно щелкнуть по ссылке Ассоип! раде Рис. 7.17. На форме входа в систему введите имя пользователя 1гес1а и пароль гпураззи/огс! В результате откроется страница входа в систему, на которой нужно ввести имя пользователя Ггес1а и пароль шураззиога в соответствующие поля (рис. 7.17). Эти зна- чения жестко закодированы в сервере примера. Откроется страница АссоипС ра^е, содержащая две кнопки для регистрации ключей по вашей учетной записи: одна кнопка для съемных физических ключей (Ке^18<ег
КешоуаЫе Токеп), а другая — для регистрации отпечатка пальца (Кец181сг Етцег- ргш<) (рис. 7.18). Нажмите кнопку для регистрации отпечатка пальца. Мобильное устройство отобразит указание прижать палец к сканеру отпечатков пальцев. После этого сканер сгенерирует открытый ключ, который будет сохранен по учетной записи пользователя, в данном случае ГгеДа. После успешной регистрации отпечатка пальца откроется окно Ассошй раце с соответствующим сообщением, как показано на рис. 7.19. 20:35 В О < О 1осайЮ81 ЗООО/рп ^а1е2 20 31 О < 0 О 1оса11ю51 О : Ассоип?раде Нед18(ег пе« Ьагдмаге кеу Нед!Б1ег РегпоуаЫе Токеп Нед1з1ег Япдегрйп! кодой! 1оса1Ьо5Г:3000 зауз кеу гед^егей ОК Рис. 7.18. Страница для регистрации съемных физических ключей или отпечатка пальца Рис. 7.19. Подтверждение успешной регистрации отпечатка пальца Теперь выполните выход из системы, а затем снова войдите по учетной записи Ггеда и паролю щураазтогд. Приложение выведет сообщение с указанием подтвер- дить вашу личность, прижав палец к сканеру отпечатков пальцев, и после успешно- го подтверждения позволит войти обратно в систему. Обсуждение Встроенные сканеры отпечатков пальцев встречаются намного чаще, чем съемные физические ключи наподобие ключей УиЫКеуз, и особенности использования этих двух средств безопасности отличаются. Ключи УиЫКеуз можно переносить с одно-
го устройства на другое, тогда как отпечаток пальца обычно привязан только к од- ному устройству9. Таким образом, съемные физические ключи предоставляют дополнительную гибкость для пользователей, которым, возможно, нужно подклю- чаться с нескольких устройств. А отрицательный аспект съемных ключей состоит в том, что их намного легче потерять, чем мобильный телефон. В большинстве слу- чаев целесообразно поддерживать оба типа ключей, оставляя за пользователями решение, какой из них подходит им больше. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу ЬПр$://огеП.1у/т8Ь§6. 7.5. Подтверждение действий, предоставляя учетные данные ЗАДАЧА Иногда пользователь может захотеть выполнить более опасные, чем обычно, или трудно обратимые действия. Например, он может захотеть уничтожить какие-либо важные данные, удалить учетную запись какого-либо пользователя или отправить сообщение электронной почты. Таким пользователем может оказаться и злоумыш- ленник, получивший доступ к оставленному без присмотра компьютеру, в который был выполнен вход. Как можно в таком случае не допустить выполнение вреди- тельских действий? РЕШЕНИЕ Многие системы заставляют пользователей подтвердить свою личность вводом своих учетных данных, прежде чем позволить им выполнять опасные операции. В случаях, когда такое подтверждение необходимо выполнять для нескольких раз- ных операций, полезно иметь возможность осуществлять все эти подтверждения централизованно. Для данного рецепта мы возьмем за основу код для безопасных маршрутов из раз- дела 2.6. В том разделе мы создали компонент зесигИуРгс^бег, предоставляющий функции входа и выхода из системы для своих дочерних компонентов (лис- тинг 7.24). Листинг 7.24. Код компонента йпрогЁ { изеЗЁаЁе } Ёгого 'геасЁ' йпрогЁ ЗесиггЁуСопЁехЁ Ёгот ' ./ЗесиггЁуСопЁехЁ1 сопзё ЗесигхЁуРгоутдег = (ргорз) => { сопзё [ЬоддесПп, зеЁЬоддесИп] = изеЗЁаЁе(ЬаЬзе) геЁигп ( <5есиг1ЁуСопЁехЁ. Р^ОV^с^е^ 9 Исключением может быть подключение с внешним сканером отпечатков пальцев.
ча1ие={{ 1од1п: (изегпаше, раззиогд) => { // Примечание для группы разработки: // Возможно, здесь нужен более высокий уровень безопасности... (изегпаше === '1гесГ && раззиогб === ’раззиогсР) { зеЁЬоддесПп(1гие) } }, 1одои€: () => зе!:Ьоддед1п(^а1зе), 1оддес11п, }} > {ргорз. сЬИдгеп} </5есиг11уСоп11ех1:. Ргоч1с1ег> ) } ехрогй с!е1аи]Л ЗесигИуРгочЫег Компоненты, которым нужно использовать функции входа и выхода из системы, могут получить доступ К НИМ ИЗ хука изе5есиГ1Гу: сопз! зесигдХу = изеЗесигзХу () //В любом месте, где нужно выполнить выход... зесигИу. 1одои(; () Для этого рецепта мы добавим в компонент 5есиг1хургоч1с1ег дополнительную функ- цию, которая будет позволять дочерним элементам подтверждать факт входа поль- зователя. После предоставления правильных учетных данных пользователю будет позволено выполнять опасную операцию. Можно создать функцию, которая принимает функцию обратного вызова, содер- жащую опасную операцию, которая вызывается приложением после того, как поль- зователь введет правильные учетные данные. Эту функцию будет легче реализо- вать в компоненте зесигдХуРгочЫег, но с ней будут некоторые проблемы, если вы- зывать ее из какого-либо другого компонента. По результатам ее исполнения можно было бы возвращать флаг успеха или неуспеха. Код вызова функции пока- зан в листинге 7.25. // Мы НЕ БУДЕМ использовать этот подход соп^1ппЪод1п((зиссезз) => { И (зиссезз) { // Сюда вставляется код для выполнения опасной операции } е1зе ( // Сюда идет код для обработки отмены проверки учетных данных
Недостаток описанного подхода состоит в том, что если забыть проверить значение флага зиссезз, то код выполнит опасную операцию, даже при отмене пользователем операции проверки учетных данных. Альтернативно, можно передавать две отдельные функции обратного вызова: одну для успешной верификации учетных данных и еще одну для отмены операции (листинг 7.26). | Листинг 7.26. Передача двух функций обратного вызова // Мы НЕ БУДЕМ использовать этот подход сопббппбодбп ( О => { // Сюда вставляется код для выполнения опасной операции }, О => { // Сюда идет код для обработки отмены проверки учетных данных }); Но этот код слегка уродлив. Поэтому мы реализуем подход с учетом обещания, что сам компонент будет слож- нее, зато упростится любой код, вызывающий его. В листинге 7.27 приведен окончательный код компонента зесигббуРгоубдег, содер- жащий последний вариант функции верификации учетных данных сопббгтъодбп. Листинг 7.27. Полный код компонента 5всиг11уРгот1аег бтрогб { изеКеб, изеЗбабе } бгот 'геасб' бтрогб ЗесигббуСопбехб бгот './ЗесигббуСопбехб' бтрогб ЬодбпЕогт бгот './ЬодбпЕогт' ехрогб бебаибб (ргорз) => { сопзб [зЬоиЬодбп, зебЗЬоиЬодбп] = изеЗбабе(бабзе) сопзб [боддеббп, зебЬоддес!1п] = изеЗбабе(бабзе) сопзб ^езобVе^ = изеКеб() сопзб гедесбег = изеКеб() сопзб опЬодбп = азупс (изегпате, раззыогс!) => { // Заметка для группы разработки: // Возможно, здесь нужен более высокий уровень безопасности... бб (изегпате === 'бгес!' && раззмогб === 'раззыогб') { зебЬоддесЛп(бгие) } } сопзб опСопббгтЬодбп = азупс (изегпате, раззногс!) => { // Примечание для группы разработки: // Возможно, здесь нужен более высокий уровень безопасности... гебигп изегпате === 'бгес!' && раззмогс! === 'раззиогсМ }
геЁигп ( <Зесиг1ЁуСопЁехЁ.Ргоухдег Vа1ие=(( 1од1п: опЬодхп, сопИппЬодЁп: азупс (саИЬаск) => { зеЬЗИоыЬодз-П (Ёгие) геЁигп пей РготЁзе((гез, гед) => { гезо!уег.сиггепЁ = гез ге^есЁег.сиггепЁ = ге^ }) }, 1одоиЁ: () => зеЁЬоддесПп(1а1зе), 1оддес11п, }} > {зЬоиЬодтп ? ( <Ьод1пГогт опЬод!п={азупс (изегпате, раззыогс!) => { сопзё уаИс! = аыахЁ опСопПгтЪодЁп (изегпате, раззмогс!) 1Ё (уаИс!) { зеЁЗЪомЪодЬп(Га1зе) гезо1уег.сиггепЁ() } Н опСапсе!={() => { зеЁЗИоыЬодхп(Ёа1зе) ге^есЁег.сиггепЁ() }} /> ) : пиИ) {ргорз. сЫ 1с1геп} </Зесиг1ЁуСопЁехЁ. РгоУ1<1ег> ) } При вызове функции сопНгтЬодхп компонент ЗесигтЁуРгоуЫег отображает форму ДЛЯ ввода учетных данных, чтобы пользователь мог подтвердить свою личность. Функ- ция сопНппЬодЁп возвращает обещание, которое будет улажено только при вводе пользователем правильных имени пользователя и пароля. При отмене пользовате- лем операции проверки учетных данных обещание отклоняется. Здесь мы не приводим подробный код компонента ьодхпГогт, но его можно найти в исходном коде для этого рецепта. Код в данном примере проверяет правильность имени пользователя и пароля, све- ряя их со статическими строками. Но в рабочей версии приложения эту проверку нужно заменить вызовом какой-либо службы безопасности.
При вызове функции сопЛгшЬодхп обещание сохраняется в ссылке геТ. Ссылки ге1 обычно указывают на элементы в модели СОМ, но их можно использовать для хранения любой части состояния. В отличие от хука изеЗЁаЁе, ссылки ге^обновля- * ются немедленно. В общем, использование в коде большого количества ссылок ге1 не приветствуется, поэтому мы используем их здесь лишь для того, чтобы иметь возможность немедленно сохранить обещание, без необходимости ожидать за- вершения исполнения хука изеЗЁаЁе. Но как использовать функцию сопЁ1гтЬод1п на практике? Предположим, что у нас есть компонент, содержащий кнопку, которая выполняет некую опасную операцию (листинг 7.28). I----------------— — —- —. ..............................— .....-.............. 1 I Листинг 7.28. Компонент с кнопкой, выполняющей опасную операцию йпрогЁ ( изеЗЁаЁе } Ёгот 'геасЁ' ЁтрогЁ ЬодоиЁ Ёгот './ЬодоиЁ' сопзё Ргт^аЁе! =()=>{ сопзё [теззаде, зеЁМеззаде] = изеЗЁаЁе() сопзё скэБапдегоизТЫпд = () => { зеЁМеззаде (' БАМСЕРОиЗ АСТЮЫ! ') } геЁигп ( «{IV с1аз5Нате="Рг1УаЁе1"> <Ы>₽г1уаЁе раде 1</Ы> <ЬиЁЁоп опС11ск={() => { доБапдегоизТМпд () }} Бо йапдегоиз ЁМпд </ЬиЁЁоп> <р с1аззЫате="теззаде">{теззаде}</р> <ЬодоиЁ /> </61У> ) ехрогЁ с1еЁаи1Ё РггуаЁе! Если мы хотим, чтобы для выполнения этой операции пользователь подтвердил свои учетные данные, то можем сначала получить контекст, предоставляемый ком- понентом ЗесигхЁуРгоуЫег: сопзё зесиг1Ёу = изеЗесигЁЁу() Затем в коде, выполняющем опасную операцию, мы можем ожидать обещание, возвращаемое функцией сопПгтъод1п: сопзё зесигхЁу = изеЗесигЁЁу() ама1Ё зесигхЁу.сопГ1гтЪод1п() зеЁМеззаде('БАЫСЕКОБЗ АСТ10Ы!')
Код, следующий за функцией сопИппЬодхп, будет исполняться только при условии предоставления пользователем правильных учетных данных. Если пользователь закроет диалоговое окно ввода учетных данных, обещание будет отклонено, и эту отмену можно обработать в блоке саЁсЬ. В листинге 7.29 приведена модифицированная версия компонента, выполняющего опасную операцию, который теперь требует подтвердить личность пользователя, прежде чем выполнять эту операцию. Листинг 7.29. Код компонента, требующий верификации учетных данных йпрогЁ { изеЗЁаЁе } Егот 'геасЁ' д.трогЁ ЬодоиЁ Егот ' . /ЬодоиЁ' ЕтрогЕ изеЗесигЕЕу Егот './изеЗесигЕЕу' ехрогр «ЕеЕаиЕЕ 0 => { сопзё зесигЕЕу = изеЗесигЕЕу() сопзё [теззаде, зеЁМеззаде] = изеЗЁаЁе() сопзё боОапдегоизТИЕпд = азупс () => { Ёгу { аиаЕЕ зесигЕЕу. сопЕЕгтЬодЕпО зеЁМеззаде (' ОАГГСЕКОЕЗ АСТЮЫ! ') } саЁсЬ (егг) { зеЁМеззаде('ОАМСЕКОиЗ АСТЮК САМСЕЬЬЕО!') } } геЁигп ( <сИу с1аззЫате="РгЕуаЕе1"> <Ъ1>Рг1уаЁе раде 1</Ь1> <ЬиЁЁоп опС1Еск={() => { боБапдегоизТЬЕпд () }) Во (Еапдегоиз ЕЬЕпд </ЬиЁЁоп> <р с1аззМате="теззаде">{теззаде}</р> <ЬодоиЁ /> </с1гу> ) } Чтобы испытать этот код, нужно сначала запустить приложение из его папки: $ прт тип зЁагЁ
В открывшейся домашней странице приложения (рис. 7.20) щелкните по ссылке Рпуа1е Ра^е 1. В ответ приложение отобразит указание ввести учетные данные (рис. 7.21) вместе с соответствующими полями. Введите имя пользователя Ггеа и пароль раззмога. Ноте • РиЬНс Раде • Рпуате Раде 1 Рпуа1е Раде 2 1_од1п Раде Уои пеед 1од !п. (111П1:1гу ^геб/раззу/огб) Цзетате: бге<1 Раззшог(1{ Н 11~од<п | Рис. 7.21. Страница закрытая, поэтому нужно верифицировать личность пользователя Рис. 7.20. На домашней странице приложения щелкните по ссылке РНуаСе Раде 1 Если теперь нажать кнопку для выполнения опасной операции, то прежде чем бу- дет разрешено выполнить эту операцию, нужно снова верифицировать личность пользователя, введя соответствующие учетные данные (рис. 7.22). Пзегпате: Ра55У/огф(—**"' | Ьод1п [ Сапсе! ] Рис. 7.22. Чтобы выполнить опасную операцию, нужно ввести свои учетные данные Обсуждение В данном рецепте код для верификации учетных данных находится в одном цен- тральном месте, а именно в компоненте ЗесипРуРгоутбег. Связанная с этим выгода состоит не только в уменьшении объема кода в компонентах, но также в получении возможности выполнять верификацию учетных данных пользователя в специали- зированных хуках. Если абстрагировать набор операций в определенную службу на основе хука10, то в эту службу можно также включить и верификационную логику. В результате наши компоненты не будут ничего знать о том, какие операции явля- ются опасными, а какие — нет. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬНр5://огеП.1у/гР75ц. 10 Пример такой службы можно посмотреть в хуке изеЕогит в разделе 5.2.
7.6. Однофакторная аутентификация ЗАДАЧА Мы уже видели, как съемные физические ключи и отпечатки пальцев позволяют повысить безопасность учетной записи пользователя в системе двухфакторной аутентификации. Но эти два способа обеспечения дополнительной безопасности можно использовать просто как удобный способ входа в систему. Многие мобильные приложения по- зволяют выполнить вход лишь одним сканированием отпечатка пальца, без необ- ходимости ввода имени пользователя и пароля. Но как можно разрешить однофакторную аутентификацию в приложении Кеас1? РЕШЕНИЕ Ключи безопасности, такие как отпечатки пальцев и П8В-устройства УиЫКеуз, нужно регистрировать на сервере по учетной записи их пользователя. Проблема с использованием этих ключей для однофакторной аутентификации состоит в том, что мы не знаем, кем должен быть пользователь, чей отпечаток пальца был отска- нирован. В двухфакторной системе он предварительно идентифицирует себя вво- дом своего имени пользователя и пароля, но в однофакторной системе нам нужно знать, кто это при создании запроса на утверждение". Проблемы можно избежать, сохраняя в браузере файл соокхе, содержащий иденти- фикатор пользователя, при входе в систему пользователя с учетной записью с под- держкой физических ключей11 12. При отображении формы для ввода учетных данных приложение может проверить наличие файла сооИе, а затем учесть его при создании запроса на утверждение и запросить у пользователя ключ безопасности. Если пользователь не желает исполь- зовать ключ, он может отменить запрос и просто ввести учетные данные в форму". В качестве идентификаторов пользователя часто применяются внутренние ключи, сгенерированные компьютером, которые не содержат никакой закрытой информа- Ции. Но если идентификатор содержит данные, позволяющие определить личность --Х.1 пользователя, например адрес электронной почты, то тогда этот подход не следу- ет использовать. 11 Запрос на утверждение требуется тогда, когда браузер дает указание пользователю отсканировать отпеча- ток пальца или активировать физический ключ. Запрос содержит список разрешенных устройств и поэтому будет уникальным для каждого данного пользователя. 12 Вследствие такого подхода пользователь сможет выполнять однофакторную аутентификацию только на том браузере, на котором он уже зарегистрировал свой ключ. При работе на другом браузере или после недавней очистки файлов соокде ему придется возвращаться к входу по учетным данным. " Это предполагает наличие файла сооИе, читаемого посредством ЗауаЗспрГ Но пригоден также файл соо- кзе НТТР-типа, читаемый только сервером (или сервис-воркером). В последнем случае на сервере нужно иметь код для проверки, должен ли пользователь предоставлять физический ключ.
Для данного рецепта мы возьмем за основу код для безопасных маршрутов из раз- дела 2.6. Все управление безопасностью будем осуществлять посредством компо- нента-оболочки 8есип^уРгоу1дег, который будет обеспечивать дочерние компонен- ты функциями входа в систему и выхода из нее. Мы добавим в него еще одну функцию — ЬодгпИЬЕЬТокеп. (Листинг 7.30. Добавление функции ЬодЬпШШГокеп в компонент 8есагЬЕу₽гст.аег хтрогр { изеЗЁаЁе } Егот 'геасЁ' гтрогЁ ЗесиггЁуСопЁехЁ Егот './ЗесиггЁуСопЁехЁ' гтрогЕ { деЁ } Егот 'ЙдгЕЬиЬ/меЬаиЕЬп-ззоп' гтрогЁ ахгоз Егот 'ахгоз' сопзё ЗесиггЁуРгоугдег = (ргорз) => { сопзё [ЬоддесПп, зеЕЬоддесПп] = изеЗЕаЕе(ЕаЬзе) геЕигп ( <ЗесиггЕуСопЕехЁ.Ргоугбег уаЬие={{ Ьодгп: азупс (изегпате, раззмогй) => { сопзё гезропзе = анагЕ ахгоз.розЕ('/Ьодгп', { изегпате, раззмогд, }) зеЕЬоддесПп(Егие) }, ЬодгпКгЕЪТокеп: азупс (изегЬЬ) => { сопзё гезропзе = аыагЕ ахгоз.розЕ('/зЕагЕУеггЕу', { изегЮ, )) сопзё аззегЕгоп = амагЕ деЕ({ риЫгсКеу: гезропзе.ПаЕа }) амагЕ ахгоз.розЕ (' ^еггЕу', { изегЮ, аззегЕгоп }) зеЕЬоддесПп(Егие) }, ЬодоиЕ: азупс 0 => { амагЕ ахгоз.розЕ('/ЬодоиЕ') зеЕЬоддесПп (ЕаЬзе) }, ЬоддесПп, }} {ргорз.скгЬПгеп} </5есиггЕуСопЕехЁ.Ргоуг6ег> ) } ехрогЕ (ЗеЕаиЬЕ ЗесиггЕуРгоугбег Функция ЬодгпИгЕЬТокеп принимает в качестве аргумента идентификатор изегЮ пользователя, а затем выводит указание пользователю верифицировать свою лич- ность посредством физического ключа следующим образом:
1. Вызывая функцию зЕагЕУепЕу на сервере, чтобы создать запрос на утверждение. 2. Передавая запрос интерфейсу ХУеЬАщЬп, чтобы дать указание пользователю прижать свой палец к сканеру отпечатков пальцев. 3. Передавая созданное утверждение обратно концевой точке г^егИу, чтобы прове- рить действительность физического ключа. В своей реализации приложения вам нужно будет заменить конечные точки для зЕагЕУегЁЕу и Vе^^Гу их версиями, соответствующими требованиям вашего приложе- ния. Чтобы В компоненте ЗесимЕуРгоУхаег вызвать функцию ЕодЁпИЕЕИТокеп, нам нужно найти в файлах соокде идентификатор текущего пользователя. Для этого сначала нужно установить библиотеку ]з-соок1е: $ прт 1пзЕа11 ]з-соокЁе Библиотека позволит считывать файл сооИе для изегю следующим образом: ЕтрогЕ СоокЕез Егот 'зз-соокЁе' сопзё изегЮСоокЁе = СоокЕез.деЕ ('изегЮ') Теперь мы можем использовать этот код в компоненте ъодхп, который будет прове- рять наличие файла соокхе для идентификатора изегю. При наличии такого файла, пользователю будет предложено выполнить вход посредством физического ключа. В противном случае пользователю будет предоставлена возможность выполнить вход посредством ввода имени пользователя и пароля. В листинге 7.31 приведен код окончательной версии приложения, содержащий все эти модификации. Листинг 7.31. Код окончательной версии приложения ЕтрогЕ { изеЕЕЕесЕ, изеЗЕаЕе } Егот 'геасЁ' ЕтрогЕ изеЗесигЕЕу Егот './изеЗесигЕЕу' ЕтрогЕ СоокЕез Егот ']з-соокЕе' сопзЕ ЬодЕп =()=>( сопзё { 1одЕп, ЕодЕпИЕЕНТокеп } = изеЗесигЕЕу() сопзё [изегпате, зеЕСзетате] = изеЗЕаЕе() сопзЕ [раззмогй, зеЕРаззиогс!] = изеЗЕаЕе () сопзЕ изегЕБСоокЕе = СоокЕез.деЕ ('изегЮ') изеЕЕЕесЕ(() => { ;(азупс () => { ЕЕ (изегЮСоокЕе) { ЕодЕпИЕЕЬТокеп (изегЮСоокЁе) } }) О }, [изегЮСоокЕе]) геЕигп ( <С1Еу>
<Ы>Ьод1п Раде</Ъ1> <р>Уои пеед Со 1од 1п.</р> <1аЬе1 ИСт1Гог=''изегпате">изегпате: </1аЬе1> <1приС 1д="изегпате" пате="изегпаше" Суре="СехС" уа!ие={изегпаше) опСЪапде={ (еуС) => зесизегпате(еуС.СагдеС.уа1ие)} /> <Ьг /> <1аЬе1 ИСт1Гог="раззыогсГ>Раззыогс1: </1аЬе1> <1приС 1с1=,’ра85иогс1" пате="раззиогсГ 1уре="раззиогсГ уа1ие={раззиогб} опСЪапде={ (еуС) => зеСРаззиогд^г.СагдеС.уа1ие)} /> <Ьг /> <ЬиССоп опСИск={() => 1од1п(изегпаше, раззмогд) }>Ьод1п</ЬиССоп> ) } ехрогС деСаи1С Ьодхп Теперь можно испытать наше приложение в действии. Сначала перейдем в папку приложения и запустим сервер разработки: $ прт гип зСагС Затем откроем другое окно терминала и запустим АР1-сервер: $ сб зеплег $ прт гип зСагС Сервер для разработки будет исполняться на порту 3000, а АР1-сервер— на пор- ту 5000. Открываем приложение и щелкаем по ссылке РпуаТе Ра§е 1 (рис. 7.23). Ноте • РцЬНс Раде • Рпудге РадёЛ • Рпуд1е Раде 2 Рис. 7.23. На домашней странице приложения щелкаем по ссылке Рпуа1е Раде 1
В открывшейся странице входа вводим имя пользователя Гге<1а и пароль шураззнога в соответствующие поля (рис. 7.24) В отрывшейся странице Ассоип< ра^е предоставляется выбор возможности входа с использованием сканера отпечатков пальцев или физического ключа (рис. 7.25). Регистрируем один из этих вариантов и выходим из системы. При следующем входе в систему будет сразу же предложено выполнить вход при помощи ранее зарегистрированного физического ключа (рис. 7.26). 1_од1п Раде Уои леей ю 1од 1п. С15егпате:[ггес1а Раввууогаф*»»» 1од1п | Ассоип* раде Ред!51ег пеж Нагбмаге кеу Рис. 7.25. Выбор физического ключа для регистрации Рис. 7.24. Выполняем вход в систему, введя соответствующие учетные данные осаЖм* *1п1в № тЛНепСгсаи уои и«пд • гедп№»<1 псип1у кеу Той сап соппас* ап«1 ашЬопм опа по*, ог сапса) © С Ф кхга1По51.3000/рНуаГе2 1_од!п Раде Уои леей 1о 1од 1п. С*псЫ ! 1 Арр изетате: РазшюгФ 1_одт Рис. 7.26. После регистрации физического ключа последующие входы в систему можно выполнять посредством этого ключа Если теперь активировать физический ключ, то вход в систему будет выполнен без предоставления учетных данных. Обсуждение Важно отметить, что однофакторная аутентификация повышает уровень удобства, а не безопасности. Особенно удобны в этом отношении сканеры отпечатков пальцев, поскольку с ними для входа нужно буквально лишь шевельнуть одним пальцем. Но при этом всегда необходимо обеспечить возможность запасного входа посред- ством ввода учетных данных. Это не понизит уровень безопасности вашего прило-
жения, поскольку какой-либо злоумышленник может удалить файл соокле и ис- пользовать форму ввода учетных данных в любом случае. Исходный код данного рецепта можно загрузить на веб-сайте бйНиЬ по адресу ЬИр$://оге11.1у/421)Ь6. 7.7. Проверка приложения на устройстве Ап<1го1с1 ЗАДАЧА Многие операции тестирования мобильных приложений можно выполнять в брау- зере персонального компьютера, эмулирующего внешний вид мобильного устрой- ства (рис. 7.27). Рис. 7.27. Приложения для мобильных устройств можно тестировать в браузере персональных компьютеров Но иногда тестирование приложений Кеас( лучше всего выполнять на физическом мобильном устройстве. Обычно это не представляет никаких проблем, поскольку мобильное устройство может получить удаленный доступ к приложению Кеас1 через 1Р-адрес компьютера разработки. Но такое тестирование может быть невозможным при некоторых обстоятельствах: ♦ мобильное устройство не может подключиться к сети компьютера разработки; ♦ используется технология, например ХУеЬАиШп, требующая применения прото- кола НТТР8 для всех доменов, кроме 1оса1Ио$1.
Можно ли настроить мобильное устройство для доступа к приложению КеасТ, как будто бы оно исполнялось на 1оса1ко$1, даже если оно исполняется на отдельном компьютере? РЕШЕНИЕ В этом рецепте мы рассмотрим, как организовать прокси-сервис на устройстве АпдгоШ, чтобы подключения к 1оса1ко$1 направлялись на сервер компьютера разра- ботки. Первое, что нам потребуется, — это устройство Апс1гок1, на котором разрешена от- ладка через 1)8В (ЬПр8://огей.1у/Гс5Гу). Также нужно установить на своем компью- тере набор разработчика АпйгоИ 8ЭК (Ьйр8://огей.1у/ВЕеХг), чтобы получить дос- туп к инструменту Апс!го1с1 ОеЬи§ Впд^е (АОВ). Инструмент АОВ открывает канал связи между компьютером разработки и устройством АпйгоИ. Далее при помощи кабеля О8В нужно подключить устройство АпйгоИ к компью- теру разработки и проверить, что команда абь доступна в терминале командной строки14. Затем с помощью этой команды можно просмотреть устройства Апдгок!, подключенные к компьютеру разработки, как показано в листинге 7.32. Листинг 7.32. Перечисление подключенных к компьютеру устройств Алексий $ ас1Ь йеутсез * йаешоп поё гипптпд; зЁаг!:1пд пои а 11 11ср:5037 * даетоп з1;аг11ес1 зиссеззГиИу Ь1з11 о! йеутсез аТТасйед 25РК1ГГЕ^ИИОГИО йеугсе $ В данном случае к компьютеру подключено только одно устройство АпйгоШ с идентификатором 25РВ1ЕГЕ^жоеио. Далее, опять же посредством команды асПэ, выполняем настройку прокси-сервера на устройстве Апс1го1с1, который будет перенаправлять весь трафик НТТР на внутрен- ний порт устройства 3000: $ ас1Ь зЪе11 зе!:Ъ1пдз ри11 д1оЬа1 Ы:1:р_ргоху 1оса1ИозЁ:3000 Если к компьютеру подключено больше одного устройства Апс1го1б, требуемое уст- ройство нужно выбрать, указав его идентификатор посредством опции команды ас1Ь---з<идентификатор-устройства>. Далее запускаем на устройстве АпйгоИ прокси-службу, которая будет перенаправ- лять весь трафик с порта 3000 устройства на порт 3000 компьютера разработки: $ ас!Ь ^еVе^зе Ёср:3000 Ёср:3000 14 Для этого нужно найти на вашем компьютере папку, в которой установлен набор разработчика Апдплд 8ОК. Команда айЬ будет находиться в одной из подпапок этой папки.
Если теперь на устройстве Апс!го1с1 открыть браузер и ввести в строку адреса адрес ЬИр://1оса1Ьо$<:3000, то в браузере отобразится приложение, исполняющееся на компьютере разработки, как будто бы оно исполнялось на данном устройстве Апдгспб (рис. 7.28). 20:30 О * О 1осаШоз1 Рис. 7.28. В браузере устройства Апс1го1С1 по адресу 1оса11то81 отображается приложение Реас1, исполняющееся на компьютере разработки Закончив работать с приложением, нужно отключить настройки прокси-сервера на устройстве Апс!го1с1, задав для прокси-сервера порт :0: $ ас!Ь зке11 зе^Мпдз риТ д1оЬа1 ЬТ1:р_ргоху :0 Если этого не сделать, то устройство не будет иметь доступа к сети. Обсуждение Для этого рецепта нужно выполнить большой объем подготовительной работы, поскольку он требует установки на компьютере разработки всего набора разработ- чика Апс1го1с1 $Е)К. Но затем подключение и отключение физических устройств АпскоИ к компьютеру не будет представлять никаких проблем. 7.8. Проверка безопасности посредством Е5Нп1 ЗАДАЧА В коде .1ауа$спр1 угрозу безопасности чаще всего представляют лишь несколько распространенных ошибок кодирования. С этой угрозой можно бороться, разрабо- тав набор стандартов кодирования, чтобы не допускать этих ошибок. Но, чтобы
поддерживать соответствие данных стандартов последним изменениям в техноло- гии, их придется часто пересматривать. Кроме того, потребуются также трудоемкие и дорогостоящие экспертизы. Можно ли каким-либо образом выполнять проверку на несоблюдение должных приемов безопасного кодирования, не замедляя при этом процесс разработки? РЕШЕНИЕ Одним из подходов к внедрению проверок безопасности может быть их автомати- зация. Для этого можно использовать один из соответствующих инструментов, на- пример линтер (Нп1ег — средство контроля качества кода) Е81т(. Если вы создали свое приложение при помощи средства сгеаге-геаг-арр, то, скорее всего, линтер Е81ш1 уже установлен на вашем компьютере. Более того, средство сгеаге-геасг-арр исполняет Е811Ш при каждом запуске своего сервера для разработки. Если вам ко- гда-либо приходилось видеть в терминале сообщения об ошибках в коде, то эти сообщения были сгенерированы средством Е81т1. В листинге 7.33 приведен при- мер таких сообщений. Листинг 7.33. Пример сообщений от Е81!п1 | СотрНеб И1ГЬ иагпГпдз. (Скомпилировано с предупреждениями.) згс/Арр.]з Ыпе 5:9: ’х’ 13 аззгдпед а уаХие ЬиГ пеуег изед по-ипизед-уагз (Строка 5:9: Переменной 'х' присвоено значение, но переменная никогда не использовалась. Неиспользуемые переменные не допускаются.) ЗеагсЪ Гог ГЬе кеуиогбз Го 1еагп тоге аЬоиГ еасЬ иагпХпд. (Дополнительную информацию по каждому предупреждению можно получить, выполнив поиск по ключевым словам.) То хдпоге, ас!с1 // езИпГ-дХзаЫе-пехГ-Ипе Го ГНе Ипе ЬеГоге. Чтобы игнорировать предупреждение, добавьте // езПпГ-сИзаЫе-пехГ-Нпе в предшествующую строку Если средство Е8Нп1 еще не установлено на вашем компьютере, его можно устано- вить, выполнив следующую команду прт: $ прт 1пзГа11 —зауе-беу езИпГ После установки средства, его нужно инициализировать, как показано в листин- ге 7.34. Листинг 7.34. Инициализация средства ЕЗШ $ пойе_тос1и1ез/.Ып/ез1ХпГ —Хп1Г - Нои иоиХП уои Ике Го изе ЕЗЫпГ? • ргоЫетз (Как вы хотите использовать ЕЗИпГ? • Обнаружение проблем)
- КЬаЁ Ёуре оё тодиГез боез уоиг рго^есЁ изе? • езт - (Какие типы модулей используются в вашем проекте? • езт) - КЫсЬ Егатеиогк доез уоиг ргс^есЁ изе? • геасЁ (Какой фреймворк используется в вашем проекте? • геасЁ) - Ооез уоиг рго^есЁ изе ТуреЗсгЁрЁ? • Ыо / Уез (Используется ли вашем проекте ТуреЗсгЁрЁ? Ко / Уез) - ИИеге доез уоиг собе гип? • Ьгонзег (Где исполняется ваш код? • В браузере) - ИЪаЁ ГоппаЁ до уои мапЁ уоиг сопГЁд Ё11е ёо Ье 1п? ^аVаЗс^^рЁ (Какой формат файла конфигурации вы хотите использовать? • бауаЗсгЁрЁ) Ьоса! ЕЗЫпё 1пзЁа11аЁ1оп поё Гоипб. (Локальная установка ЕЗПпё не обнаружена.) ТЬе сопНд ЁЬаЁ уои'уе зеГесЁеб гедиГгез ЁЬе Го11ок1пд дерепдепсхез: (Для выбранной вами конфигурации требуются следующие зависимости:) ез11ПЁ-р1ид1п-геасЁ@1аЁезЁ езИпЁ@1аЁезЁ - Кои1д уои Ике ёо ЁпзЁаИ Ёкеш пои мЁЁк прт? • Ко / Уез (Хотите ли вы установить их сейчас посредством прт? • Ко / Уез) $ Но при этом следует иметь в виду, что если вы используете средство сгеаЁе-геасЁ- арр, то инициализировать Е81т1 не требуется, поскольку инициализация уже была выполнена. На данном этапе можно создать собственный набор правил для Е8Нп1, чтобы про- верять на несоблюдение любых правил безопасности. Но будет намного легче ус- тановить подключаемый модуль Е81т1, уже содержащий готовый набор правил. Давайте, например, установим пакет езНЩ-рШ^т-геаЩ-зесигйу, созданный и под- держиваемый компанией 81ук (Ьйр8://81укло): $ прт ЁпзЁаИ —зауе-деу езПпЁ-рГидЁп-геасЁ-зесигЁЁу После установки модуля его нужно активировать, отредактировав раздел езИпЁСопНд файла раскате.]80п (если используется средство сгеаЁе-геасЁ-арр) или файла езИп1гс* в папке приложения. Подлежащий редактированию код приведен в листинге 7.35. Листинг 7.35. Код, подлежащий редактированию, для активирования модуля "езИпЁСопНд": ( "ехЁепдз": [ "геасЁ-арр", ”геасЁ-арр/]езЁ" ] }, Этот фрагмент кода нужно заменить кодом, приведенным в листинге 7.36.
Листинг 7.36. Новый код для активирования модуля "езИпЁСопМд": { "ехЁепйз": [ "геасЁ-арр", "геасЁ-арр/]езЁ" ], "р1идд.пз": [ "геасЁ-зесигЁЁу" ], "ги1ез": { "геасЁ-зесиг1Ёу/по-зауазсг1рЁ-иг1з": "нагп", "геасЁ-5есиг1Ёу/по-бапдегоиз1у-зеЁ-1ппегЪЁт1": "магп", ’'геасЁ-зесиг1Ёу/по-Ё1пс1-с1от-пос1е": "иагп", "геасЁ-зесиг1Ёу/по-ге1з": "ыагп" } }, В результате этих изменений будут активированы четыре правила из модуля Кеас1 8есигйу. Чтобы проверить их в работе, добавим в приложение немного кода, который нару- шает правило по-йапдегоизТу-зеЁ-ЁппегкЁт! (ЛИСТИНГ 7.37). Листинг 7.37. Код, нарушающий правило по-бапдегоиз1у-зеЪ-хппегЪЬп1 йпрогЁ 1одо Ёгогс './Ёодо.зуд' йпрогЁ '. /Арр. сзз' ГипсЁЁоп Арр() { геЁигп ( <сНу с1аззЫате="Арр"> <Ъеас1ег с1аззЫате="Арр-11еабег"> <1тд згс={1одо} с1аззМате=”Арр-1одо" а1Ё="1одо" /> <р> Е61Ё <сос!е>згс/Арр.]з</сос!е> апб зауе ёо ге1оаб. </р> <сИу с1апдегоиз1у8еЁ1ппегНТМЬ={ { _____Ьёш1: '<р>ТЫз 13 а Ьай 1с1еа</р>', }} /> <а с1аззМате="Арр-11пк" ЪгеГ="кЁЁрз://геасЁдз.огд" ЁагдеЁ="_Ыапк" ге1="поорепег погеГеггег"
Ьеагп Веас!: </а> </Неас1ег> </сНу> ) ехрогЁ деГаи1Ё Арр Если средство Е8Нп( было установлено вручную, этот файл можно отсканировать следующей командой: $ пос!е_1пос1и1е5/.Ып/езИгй: згс/Арр.дз А если используется средство сгеа^е-геас^-сот. то нужно перезапустить сервер, что- бы перезагрузить конфигурацию Е8Пп1. Результаты сканирования приведены в лис- тинге 7.38. Листинг 7.38. Предупреждение, выдаваемое средством Е8Пп1 СстрНеб иНИ нагпгпдз. згс/Арр.]3 Ыпе 12:16: аапдгоиз1у8еЫппегНТМЬ ргор изаде дебес^ес! геасЁ-зесиг1Гу/по-6апдегоиз1у-зеп-1ппегЬГт1 8еагсГ> 1ог Шс кеумогбз 6 о 1еагп тоге аЬоик еасК иагпшд. То хдпоге, аск! // езИпГ-сНзаЫе-пехГ-Ипе 6о Ипе ЬеГоге. Обсуждение Проверки Е8ЙП1 с помощью хука рге-сотпи± Сй полезно выполнять и в случае рабо- ты с командой разработчиков, чтобы не допустить сдачи разработчиками кода, который не способен пройти проверку. Это позволит быстрее получать оценку ра- боты и не допустит вывода из строя всей сборки из-за ошибки одного разработчика. Для настройки хуков рге-соттП: посредством файла раска§е.]8оп рассмотрите воз- можность установки хуков кода Низку (11Нр$://оге11.1у/иЕрх). Еще одно преимущество, предоставляемое автоматизацией проверок безопасности, состоит в том, что их можно добавить в используемый вами конвейер сборки и раз- вертывания приложений. Если выполнять проверки в начале конвейера, то комми- ты можно отклонять сразу же и сообщать об этом разработчикам. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬПр8://огеП.1у/куВс8,
7.9. Удобные для браузера формы входа в систему ЗАДАЧА Многие решения безопасности подразумевают наличие формы для ввода имени пользователя и пароля, но при создании таких форм можно легко допустить не- сколько ошибок, получив результат, неудобный для пользователя. На некоторых устройствах автоматическое исправление ошибок правописания и исправление первой буквы предложения в заглавную может оказать медвежью услугу и исказить имя пользователя и/или пароль. Некоторые браузеры пытаются автоматически за- вершать вводимое имя пользователя, но часто неясно, согласно каким правилам это делается, в итоге на одних сайтах все работает должным образом, а на других воз- никают ошибки. Каких правил нужно придерживаться при создании форм входа в систему, чтобы они органически работали совместно с браузером, не создавая никаких проблем? РЕШЕНИЕ Удобство работы с формами входа в систему можно значительно улучшить при помощи нескольких атрибутов НТМЬ. Первым делом может быть полезным отключить исправление ошибок правописа- ния для полей ввода имени пользователя. Эта возможность часто используется в мобильных устройствах, чтобы компенсировать малый размер клавиатуры и не- избежные вследствие этого ошибки правописания. Но подобное поведение малопо- лезно при вводе имени пользователя. Возможность автоматического исправления ошибок правописания можно отключить при помощи атрибута аи^оСоггесС. Следую- щим образом: <1при1; аи1:оСоггес11=,,о?Г"/> Далее, если в качестве имени пользователя задан адрес электронной почты, рас- смотрите возможность присвоения этому полю типа етап: <1прШ: Ёуре=’'етаН"/> Это может вызвать запуск специальной клавиатуры для работы с электронной по- чтой на мобильных устройствах. А некоторые браузеры могут даже отображать список недавних адресов электронной почты в окне автозавершения или в заголов- ке клавиатуры для электронной почты. Также рекомендуем рассмотреть возможность указания 3 изегпате в качестве иден- тификатора и названия поля ввода имени пользователя. <1при1: 1с1="п_изегпате" пате="]_изегпате"/> Почему? Потому что поле с названием ^ изегпате часто используется в приложени- ях на основе 1ауа$спр1, и поэтому существует вероятность, что пользователь уже
предоставлял значение для такого поля в прошлом. Это, в свою очередь, повышает вероятность того, что браузер может включить адрес электронной почты в окне автозавершения. Можно также явно указать, что поле является полем ввода имени пользователя, в результате чего повышается вероятность активизации автозавершения в браузере: <1при1 аи^оСотр1е1:е="изегпа1пе"/> Теперь разберемся с паролями. Первым делом всегда задавайте тип поля для ввода пароля как раззмога: <1приЪ 1:уре="раззыогсГ/> Никогда не поддавайтесь искушению воспроизвести внешний вид поля пароля ка- ким-либо иным образом, например через специальное стилистическое С88- оформление. Это не позволит браузеру применять к полю пароля стандартные средства безопасности, например отключение в нем функции копирования. Кроме того, если не задать полю тип раззмога, то браузер не будет предлагать сохранить пароль в своем диспетчере паролей. Существуют два вида поля пароля: для текущего пароля (при входе в систему) и для нового пароля (при создании или смене пароля). Почему это важно? Потому что НТМЬ-атрибут аи^оСотрТеЁе может указывать брау- зеру предполагаемое назначение поля пароля. Если оно служит для входа в систему, то нужно указать, что это сиггегл-раззиога: <1при1: Ёуре="раззиогс1" аи1:оСотр1е1:е=,’сиггеп€-ра53когсГ/> А если предназначено для создания пароля при регистрации или для изменения па- роля, то нужно указать, что это пеи-раззмогё: <1прий Ёуре="раззиогсГ аиЁоСогпр1еСе="пем-раззиогсГ’/> Это значение будет стимулировать браузер к автозавершению поля пароля сохра- ненным значением пароля, а также активирует любые встроенные или посторонние средства генерирования паролей. Наконец, избегайте использования форм входа в систему в стиле мастера, т. е. в виде многошагового окна. Пример такого окна (для сайта газеты УУазЫп^оп Ро§1) показан на рис. 7.29. Это значительно снижает вероятность распознавания браузером назначения одного поля для ввода пароля, в результате чего уменьшается вероятность, что он предло- жит завершить его сохраненным значением. Обсуждение Атрибуту аиЁоСотр1есе можно присваивать много других редко используемых зна- чений для разных типов полей, от подробностей адреса и номера телефона до номера кредитной карты. Дополнительная информация по этому вопросу предос- тавляется на веб-сайте разработчиков МогШа (ЬНр5://огеИ.1у/ТЬНЬГ).
Рис. 7.29. Многошаговая форма входа в систему может помешать браузеру использовать возможность автозавершения
Тестирование В этой главе мы рассмотрим разные методы тестирования приложений Кеаск В общем, мы пришли к выводу, что нецелесообразно давать слишком жесткие ре- комендации касательно точного состава тестов, которые нужно иметь. Хорошим руководящим принципом будет следовать двум правилам: ♦ Никогда не приступайте к разработке кода, если у вас нет для него теста, кото- рый заведомо обнаружит какую-либо ошибку. ♦ Если первое исполнение теста оказывается успешным, избавьтесь от него. Эти два правила помогут вам создавать работающий код, в то же самое время позволяя избежать создания избыточных малоценных тестов. Мы пришли к выводу, что на ранних этапах проекта лучше создавать тесты на ос- нове браузера. Такие тесты, как правило, более высокого уровня и способствуют определению основных бизнес-требований к приложению. Позже, когда архитек- тура приложения начинает проявляться и стабилизироваться, становится легче соз- давать дополнительные модульные тесты для отдельных компонентов. Такие тесты требуют меньше времени для разработки и исполняются быстрее. Кроме того, когда у нас имеется стабильная структура кода, постоянное обновление тестов больше не требуется. Иногда полезно уточнить само понятие, что именно является тестом. При работе с кодом компоновки страницы, главное значение которого визуальное, в качестве "теста" можно рассматривать рассказ средства ЗЮгуЬоок. Проверка выполняется визуально разработчиком, просматривая компонент в процессе его создания. Ко- нечно же, этот вид теста не будет автоматически обнаруживать регрессивные ошибки, но в этом рецепте предоставляется метод, который позволит вам преобра- зовать такие визуальные тесты в автоматические. Если создавать тесты, прежде чем приступать к написанию кода программы, то вы обнаружите, что тесты тоже являются инструментами для разработки. Такие тесты станут исполняемыми примерами желаемого поведения приложения. И наоборот, если разрабатывать тесты после написания кода программы, то они будут просто искусственными объектами, фрагментами кода, которые вы должны слепо создавать, потому что это из разряда вещей, которые должен делать профес- сиональный разработчик. В данной главе мы будем концентрироваться на четырех инструментах: библиотеке для тестирования Кеас1 Тезйп§ ЫЬгагу, средстве 81огуЬоок, библиотеке 8е1епшт и фреймворке для тестирования Сургезз.
Библиотека Кеас1 ТезИп^ ЫЬгагу— замечательное средство для создания очень подробных модульных тестов. Средство для работы с галереями изображений 81огуЬоок уже было рассмотрено ранее в этой книге. Мы включили его в настоящую главу по той причине, что гале- рея представляет собой набор примеров кода, которыми также являются и тесты. Поэтому вы сможете найти рекомендации, как использовать 81огуЬоок в процессе разработки и тестирования. Библиотека 8е1ешиш — одна из наиболее устоявшихся библиотек для тестирования приложений в реальном браузере. Наконец, фреймворк для тестирования Сургезз быстро завоевывает у нас популяр- ность. Этот фреймворк похож на библиотеку 8е1епшт в том отношении, что он исполняется в браузере. Но он содержит большое множество дополнительных воз- можностей: повторное воспроизведение тестов, генерированные видеопрогонов тестов, а также значительно более простую модель программирования. Если вам придется выбрать только один из этих четырех инструментов, выбирайте фрейм- ворк Сурге§8. 8.1. Работа с библиотекой КеасЛ Теебпд ЫЬгагу ЗАДАЧА Существует много способов протестировать приложение Кеас1. На ранних стадиях проекта, когда определяется основное назначение и главная функция приложения, желательно создавать тесты очень высокого уровня, например тесты СиситЬег (ЬИр$://сиситЬегло). При исследовании какой-либо изолированной части системы (например, создания и обслуживание элемента данных) целесообразно создать функциональные тесты, используя инструмент наподобие фреймворка Сурге88. Но при уточнении подробностей создания отдельного элемента будет, наверное, желательным прибегнуть к модульным тестам. Название модульные тесты отра- жает их назначение — тестирование отдельного фрагмента кода как изолированно- го модуля. Хотя можно спорить, действительно ли название "модульный тест" пра- вильное в случае тестирования компонентов (которые часто содержат подкомпо- ненты и, таким образом, не изолированы), это название часто употребляется с тестами компонентов, которые можно протестировать вне браузера. Но как выполнить модульное тестирование компонентов Кеас1? В разные периоды времени для этого применялось несколько подходов. Первые модульные тесты по- лагались на отрисовку компонента в строку НТМЬ. Для этого требовалась мини- мальная инфраструктура тестирования, но одновременно присутствовали многие недостатки: ♦ Обработка повторных отрисовок при изменении состояния компонента. ♦ Создание утверждений на элементах НТМЬ, что тест должен выделять элементы посредством парсинга строки.
♦ Для тестирования взаимодействий пользовательского интерфейса требовалось эмулировать модель событий. Разработчики очень быстро создали библиотеки для обработки подробностей каж- дой из этих проблем. Но в созданных таким образом тестах не хватало реальности, получаемой в браузе- ре. Таким образом, теряются тонкости взаимодействия между виртуальной моде- лью ВОМ и моделью браузера ООМ. Часто с целью уменьшения уровня сложности тестов отрисовка подкомпонентов не выполнялась. В результате всего этого для приложений Кеас1 часто было доступно лишь неболь- шое количество тестов. Поэтому разработчики переписывали код, перемещая сложную логику в функции Дауа8спр1, которые легко поддавались тестированию. Весь более-менее сложный код требовалось тестировать в настоящем браузере, что замедляло процесс тестирования. В результате разработчики предпочитали тести- ровать лишь небольшое количество сценариев. Так как же можно подвергнуть компоненты Кеас1 реалистичному модульному тес- тированию, избегая при этом накладных расходов в виде исполнения всего прило- жения и тестирования его в настоящем браузере? РЕШЕНИЕ В библиотеке Кеас! ТезИп^ ЫЬгагу (разработчик Кент С. Доддс — КеШ С. Водск) предпринимается попытка избежать проблем с предыдущими библиотеками мо- дульного тестирования, предоставляя автономную реализацию модели ВОМ. В ре- зультате тесты способны выполнять отрисовку компонентов ВОМ в виртуальной модели ВОМ, которую затем можно синхронизировать с моделью ООМ библиоте- ки тестирования и создать дерево элементов НТМЬ, которые ведут себя так, будто бы они функционируют в настоящем браузере. Эти элементы можно проверять таким же самым образом, как и в настоящем брау- зере. Они обладают такими же атрибутами и свойствами. Можно даже передавать нажатия клавиш в поля ввода и заставить их вести себя аналогично полям ввода в настоящем браузере. Если вы создали свое приложение при помощи средства сгеа^е-геан-арр, то, библио- тека Кеас! Те§11 п§ ЫЬгагу уже должна быть установлена на вашем компьютере. В противном случае ее можно установить, выполнив следующие команды: $ прт 1пз1:а11 —зауе-деу "@^езМпд-ИЬгагу/геас±" $ прт ±пз1:а11 —8аVе-с^еV "ЙЁезйгпд-НЬгагу/зез^-дот" $ прт тпзЁаИ —зауе-беу "ЙЪезОпд-ИЬгагу/изег-еуеп^" Эти три раздела библиотеки позволят выполнять модульное тестирование компо- нентов. Библиотека Тезбп^ ЫЬгагу позволит выполнять отрисовку компонентов, используя реализацию модели ВОМ в сопутствующей библиотеке 1е$1: Вот (б^езЫпд- ИЬгагу/^езИ-бот). А сопутствующая библиотека Ызег ЕуепГ (йИезМпд-ИЬгагу/изег-
еуепб) упрощает взаимодействие с элементами модели ООМ. Эта библиотека осу- ществляет поддержку щелчков мыши и ввод с клавиатуры в поля ввода компонен- тов. Для демонстрации модульного тестирования компонентов нам потребуется тесто- вое приложение, которое мы будем использовать в большей части этой главы. При запуске приложение дает указание пользователю выполнить простое вычисление, а затем информирует его о правильности ответа (рис. 8.1). Рис. 8.1. Тестируемое приложение Основной компонент библиотеки называется Арр. Для этого компонента создадим модульный тест, поместив его в файле Арр.1е$1.)$ (листинг 8.1). | Листинг 8.1. Модульный тест для компонента Арр безсгдЬе('Арр', () => { (' 5кои1с1 Те11 уои мйеп уои мгп', () ^> { // Тест должен вывести сообщение о выигрыше // О^еп ме'уе гепбегес! ГЬе арр // (При условии, что приложение было отрисовано) // ИЪеп ие епбег ДЬе соггесб апзмег // (Когда мы вводим правильный ответ) // ТЬеп ме аге Го1с! бЬаб ые^е иоп // (Выводится сообщение, что мы выиграли) Код в листинге 8.1 реализует тест из библиотеки Зез! и содержит один сценарий, который тестирует, что компонент Арр сообщает пользователю, что он выиграл, если тот предоставит правильный ответ. В данном случае собственно тест пред- ставлен заставочными комментариями. Тест начинается с отрисовки компонента Арр. Это можно сделать, импортировав компонент и передав его функции гепбег библиотеке Тезйп§ ЫЬгагу (листинг 8.2).
! Листинг 8.2. Код отрисовки компонента Арр бтрогб { гепдег } бгот 1 йбезббпд-НЬгагу/геасб1 бтрогб Арр бгот ’./Арр' «безсгбЬе ('Арр', О => { бб('з1юи1с1 1:е11 уои ыИеп уои ибп', () => { // Тест должен вывести сообщение о выигрыше // При условии, что приложение было отрисовано гепс!ег(<Арр />) // Когда мы вводим правильный ответ // Выводится сообщение, что мы выиграли }) }) Обратите внимание на то, что мы передаем функции отрисовки гепбег настоящий код ХМЬ 5ауа8спр1, что означает, что при желании мы могли бы протестировать поведение элемента при передаче других наборов свойств. Для следующей части теста нам необходимо ввести правильный ответ. Для этого нам сначала нужно знать, что является правильным ответом. Предлагаемое пользо- вателю вычисление всегда представляет собой умножение произвольных чисел, поэтому мы можем выполнить захват чисел со страницы, а затем ввести их произ- ведение В поле Сиезз1. Нам нужно будет исследовать элементы, создаваемые компонентом Арр. Функция гепбег возвращает объект, содержащий эти элементы и набор функций для их фильтрации. Это возвращаемое значение мы заменяем объектом зсгееп библиотеки Те§йп§ ЫЬгагу. Объект зсгееп можно рассматривать, как содержимое окна браузера. Он позволяет нам находить элементы в странице, чтобы можно было взаимодействовать с ними. Например, найти поле ввода сиезз можно следующим образом: сопзб бприб = зсгееп.дебВуЬаЬебТехб(/диезз:/б) Методы фильтрации в объекте зсгееп обычно начинаются с: ♦ дебВу... — если известно, что модель ВОМ содержит единственный экземпляр совпадающего элемента; ♦ диегуву... — если известно что не совпадает ни один или один элемент; ♦ дебАПВу... — если известно, что совпадает один или больше элементов (возвра- щается массив); ♦ диегуАПВу... — возвращает массив всех совпадающих элементов или пустой массив при отсутствии совпадений. 1 В других рецептах в этой главе мы увидим, что из теста можно динамически убрать составляющую произ- вольности и зафиксировать правильный ответ, не захватывая ответ со страницы.
Если эти методы обнаруживают большее или меньшее количество элементов, чем ожидаемое, то они создают исключение. Существуют также асинхронные версии методов детву... и детАИВу... — методы Г1пс1ву... и ПпбАИВу... соответственно, которые возвращают обещания. Каждый из этих методов фильтрации может выполнять определенные поиски (табл. 8.1). Таблица 8.1. Разновидности методов фильтрации Суффикс метода Описание вида поиска ...ВуЬаЬеГГехЁ Находит поле по метке ... ВуР1асеНо1с1егТехТ Возвращает поле с подстановочным текстом ...ВуТехс Возвращает поле с совпадающим текстом ...ВуВ1зр1ауУа1ие Возвращает элементы, содержащие совпадающее значение ...ВуА1ТТехТ Возвращает элементы с совпадающим атрибутом а1г ...ВуТ1Г1е Возвращает элементы с совпадающим атрибутом тхтХе ...ВуВоХе Выполняет поиск по роли АК1А ... ВуТезЫё Выполняет поиск по атрибуту даТа-тезМё Существует почти 50 способов поиска элементов в странице. Но, вы, наверное, за- метили, что ни один из рассмотренных способов не использует С 8 8-селектор для отыскания элементов. Это преднамеренный подход. Библиотека Те8(т§ ЫЬгагу ограничивает количество способов для нахождения элементов в модели ВОМ. На- пример, нельзя выполнять поиск элементов по имени класса, чтобы не ослаблять тест. Имена классов часто служат для косметического стилевого оформления и нередко подвергаются изменениям. Но для поиска элементов все же можно задействовать селекторы, применяя кон- тейнер, возвращаемый методом гепбег: сопзТ: { сопТагпег } = гепдег(<Арр />) сопзТ ТкеХприТ = сопЁа!пег.диегу8е1ес±ог ('йдиезз') Но такой подход считается плохой практикой. При работе с библиотекой Тезбп^ ЫЬгагу, наверное, лучше всего будет следовать стандартному подходу и искать элементы по их содержимому или роли. Одно небольшое отступление от этого подхода — использование методов фильтра- ции .. .вуТезъхд. При отсутствии практических способов отыскания элемента по его содержимому к релевантному тегу всегда можно добавить атрибут баъа-сезсга. Это будет полезно для теста, который мы сейчас разрабатываем, т. к. нам нужно найти два числа, отображаемые на странице. Поскольку эти числа генерируются случай- ным образом, то мы не знаем их значений (рис. 8.2). Таким образом, мы слегка модифицируем код, добавив в него тестовые идентифи- каторы (листинг 8.3).
7x4? Оиеза I 5/хивг кт* Рис. 8.2. Мы не можем искать числа по их содержимому, поскольку не знаем их значений Листинг 8.3. Добавление тестовых идентификаторов в код «Ну с1аззМате="0иезГ1оп-6еГаИ"> <с11у даСа-ЁезТЫ^пигпЬег!" с1аззМате="питЬег1"> (ра1г && рагг[0]} </сНу> &Г1тез; <сИу баЁа-^езЁЫ^питЬе^" с1аззКате=’’питЬег2"> (рагг && рагг[1]} </с!1у> 9 </с11у> Это позволит нам реализовать следующую часть нашего теста (листинг 8.4). Листинг 8.4. Добавление в код следующей части теста ыпрогГ { гепс!ег, зсгееп } Ггот ' ЙгезГГпд-ИЬгагу/геасГ' ипрогГ Арр Ггот './Арр' дезсг1Ье('Арр', 0 => { 1Г (' з1тои1с1 Ге11 уои кИеп уои ш.п', О => { // Тест должен вывести сообщение о выигрыше // При условии, что приложение было отрисовано гепс!ег(<Арр />) // Когда мы вводим правильный ответ сопзГ питЬег! = зсгееп. деГВуТезПсЦ'питЬег11).ГехГСопГепГ сопзГ питЬег2 = зсгееп.деГВуТезГ16(1питЬег2').ГехГСопГепГ сопзГ 1приГ = зсгееп.деГВуЬаЬе1ТехГ(/диезз:/1) сопзГ зиЬтгГВиГГоп = зсгееп.деГВуТехГ('5иЬт1Г') И Егг... // Выводится сообщение, что мы выиграли }) }) Теперь у нас есть тест для каждого числа, а также есть элемент ввода 1приг. Далее нам нужно ввести правильное число в поле ввода и отправить ответ. Это мы делаем
посредством библиотеки @безббпд-ббЬгагу/изег-еуепб (листинг 8.5). Библиотека Ьзег Еуеп1 упрощает процесс создания событий Дауа8сг1рб для элементов НТМЬ. Биб- лиотека Ызег Еуепб часто импортируется под псевдонимом изег. Это объясняется тем, что вызовы данной библиотеки можно рассматривать, как выполняемые поль- зователем действия. Листинг 8.5. Импортирование и использование библиотеки Цвет Еуеп1 Л -* бтрогб { гепдег, зсгееп } бгот '@безббпд-ббЬгагу/геасб' бтрогб изег бгот '@безббпд-ббЬгагу/изег-еуепб' бтрогб Арр ^гот './Арр1 безсгбЬе ('Арр', О => { бб('зЪоибс1 бебб уои мЪеп уои ибп', () => { // Тест должен вывести сообщение о выигрыше // При условии, что приложение было отрисовано геп<бег(<Арр />) // При вводе правильного ответа сопзб питЬегб = зсгееп.дебВуТезббсЦ'питЬегб').бехбСопбепб сопзб питЬег2 = зсгееп.дебВуТезббд('питЬег2').бехбСопбепб сопзб бприб = зсгееп.дебВуЬаЬебТехб(/диезз:/б) сопзб зиЬшббВиббоп = зсгееп.дебВуТехб('ЗиЬтбб') изег.буре(бприб, 11 + рагзеГбоаб(питЬегб) * рагзеЕбоаб(питЬег2)) изег.сббск(зиЬтббВиббоп) // Выводится сообщение, что мы выиграли }) }) Наконец, нам нужно выдать утверждение, что мы выиграли, т. е. ввели правильный ответ. Это можно сделать, просто выполнив поиск любого элемента, содержащего СЛОВО иоп2! // Тогда выводится сообщение, что мы выиграли зсгееп.дебВуТехб(/моп/б) Это утверждение работает, поскольку метод дебВуТехб создает исключение, если он не найдет ровно один совпадающий элемент. Если на каком-либо этапе теста вы не уверены в текущем состоянии НТМЬ, попро- буйте добавить в код метод зсгееп.дебВуТезббй('ЫОЫЕХбЗТЕМТ'). Создаваемое ис- ключение покажет текущий НТМЬ. Но медленное исполнение приложения может вызвать сбой теста. Это объясняется тем, что методы деб... и диегу... считывают текущее состояние модели БОМ. Если для появления результата требуется пара секунд, то утверждение будет неуспеш- 2 Обратите внимание на то, что во многих тестах сравнение текста осуществляется посредством регулярных выражений, что позволяет, как в данном примере, выполнять поиск подстрок без различения регистра. Регулярные выражения могут предотвратить частые сбои тестов.
ным. Поэтому желательно обеспечить, чтобы утверждения были асинхронными. Это немного усложнит код, но повысит стабильность теста при работе медленно исполняющейся программы. МеТОДЫ Г1ПС1... ЯВЛЯЮТСЯ аСИНХрОННЫМИ ВерСИЯМИ МеТОДОВ деЁ..., а МеТОД иаЁЁГог библиотеки Тезйп§ ГлЬгагу обеспечит повторное исполнение кода в течение неко- торого периода времени. Объединив эти два метода, можно создать окончательную версию нашего теста (листинг 8.6). ! Листинг 8.6. Окончательная версия теста ЁтрогЁ { гепсЗег, зсгееп, маЁЁГог } Ёгого ' @ЁезЁ1пд-11Ьгагу/геасЁ1 ЁгпрогЁ изег Ёгот ' @ЁезЁ1пд-11Ьгагу/изег-еуепЁ' иърогЁ Арр Ёгот './Арр' дезсгхЬе('Арр', 0 => { дХ('зЬои1с1 Ёе11 уои ыЬеп уои ю.п', азупс () => { // При условии, что приложение было отрисовано гепс!ег(<Арр />) // При вводе правильного ответа сопзё пигоЬег! = зсгееп. деЁВуТезЁ1с!( 'пишЬег!').ЁехЁСопЁепЁ сопзё пишЬег2 = зсгееп.деЁВуТезЁТсЦ 'питЬег2').ЁехЁСопЁепЁ сопзё 1приЁ = зсгееп.деЁВуЬаЬе1ТехЁ(/диезз:/1) сопзё зиЬпйЁВиЁЁоп = зсгееп.деЁВуТехЁ(' ВиЬппЁ') изег.Ёуре (ЁприЁ, 11 + рагзеНоаЁ (пишЬег 1) * рагзеПоаЁ (питЬег2)) изег. сНск (зиЬтЁЁВиЁЁоп) // Выводится сообщение, что мы выиграли анаЁЁ ыаЁЁГогЦ) => зсгееп.ЁЁпбВуТехЁ(/коп/1), { ЁЁшеоиЁ: 4000 }) }) }) Модульные тесты должны исполняться быстро, но если по какой-либо причине тест длится свыше пяти секунд, функции 1ё нужно будет передать другое значение ЁЁтеоиЁ в миллисекундах. Обсуждение Работая с разными командами, мы обнаружили, что на ранних стадиях проекта раз- работчики пишут модульные тесты для каждого компонента, но со временем число таких тестов начинает постепенно уменьшаться. В конечном итоге они могут даже совсем убрать модульные тесты, если требуют слишком большого объема обслу- живания. Это происходит частично по той причине, что модульные тесты более абстрактные, чем браузерные. Они выполняют то же самое, что и браузерные тесты, но делают это незаметно. Мы не видим их взаимодействия с компонентами. Вторая причина заключается в том, что разработчики часто воспринимают тесты, просто как подлежащие сдаче составляющие проекта. Команда может даже созда-
вать сборки, которые не будут работать должным образом, без входящих в состав программ модульных тестов. Эти проблемы обычно устраняются, если разработчики создают тесты, прежде чем приступать к разработке кода. Если сначала создавать тесты, постепенно по одной строке кода за раз, то у вас будет намного лучшее понимание текущего состояния кода НТМЬ. Если перестать рассматривать тесты как составляющие разрабатывае- мого и начать смотреть на них как на средства для проектирования кода, то из тру- доемкого бремени они превратятся в инструменты, облегчающие вашу работу. При разработке программы важно начинать с теста, гарантированно выявляющего ошибки. На ранних стадиях проекта этим тестом может быть браузерный тест. По мере повышения степени готовности проекта и стабилизации его архитектуры сле- дует создавать все больше модульных тестов. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬПр8://огеИ.1у/Р1Т щ. 8.2. Использование ЗДогуЬоок для тестирования отрисовки ЗАДАЧА Тест — это просто пример, который можно исполнить. Соответственно тесты име- ют много общего с системами галерей компонентов, например 81огуЬоок. Как тес- ты, так и галереи являются примерами компонентов, исполняющихся в определен- ных обстоятельствах. Тогда как тест проверяет корректность кода, разработчик делает выводы, взяв библиотечный пример, исследуя его и удостоверяясь, что он выглядит ожидаемым образом. Как в галереях, так и в тестах исключения можно легко увидеть. Но есть и отличия. Тесты могут автоматически взаимодействовать с компонентами, тогда как галерейные компоненты требуют посредничества пользователя для нажа- тия кнопок и ввода текста. Тесты можно исполнять одной командой, а галереи нужно просматривать вручную, по одному примеру за раз. Галерейные компоненты визуальные и легко понимаемы, а тесты абстрактны и их создание не доставляет большого удовольствия. Можно ли каким-либо образом объединить галереи наподобие 81огуЬоок с автома- тизированными тестами, чтобы в результате получить преимущества одного и дру- гого? РЕШЕНИЕ В качестве решения мы рассмотрим, как можно многократно использовать расска- зы 81огуЬоок в тестах. Установить $1огуЬоок в приложение можно следующей командой: $ прх зЬ 1П11:
Пример приложения в этой главе прежний— это простая математическая игра, в которой пользователю нужно вычислить ответ на задачу умножения (рис. 8.3). Один из компонентов игры называется оиезНоп, он отображает произвольно сгене- рированные перемножаемые числа (рис. 8.4). 7x4? Рис. 8.4. Компонент Оиезйоп Предположим, что мы не будем слишком заботиться о тестировании этого компо- нента. Давайте просто соберем его, создав несколько рассказов ЗЮгуЬоок. Для начала создадим файл Риезбоп.зЮпез^ (листинг 8.7). Листинг 8.7. Файл Оиеа11оп.81опе8.]8 для компонента зъэгхез нпрогЁ ОиезИоп Ггош ' ./ОиезЁхоп' сопзё 1п1о = { Ё1Ё1е: 'ОиезЁЁоп1, } ехрогЁ деЁаиХЁ 1пёо ехрогЁ сопзё ВазЁс = () => <0иезЁ1оп /> Затем создадим начальную версию компонента, который мы можем просмотреть в 8ЮгуЬоок и признать его удовлетворительным. ! Листинг 8.8. Код компонента 0шэзЬ.оп ЁгсрогЁ { изеЕЁЁесЁ, изеЗЁаЁе } Ёгот 'геасЁ' ипрогЁ './0иезЁ1оп.сзз'
сопзё КАЛСЕ =10 ЁипсЁЁоп гапд() ( геЁигп МаЁЬ.Поог (МаЁк.гапбошО * НАНСЕ + 1) } сопзё 0иезЁ1оп = ({ геЁгезЪИте }) => { сопзё [рагг, зеЁРахг] = изеЗЁаЁеО сопзё геЁгезЪ = () => { зеЁРахг((ра!г) => { геЁигп [гапд(), гапд()] }) } изеЕГЁесЁ (геЁгезк, [геЁгезЪИте]) геЁигп ( <сИу с1аззНате="0иезЁ1оп”> <с^ с1аззЫате="0иезЁ1оп-деЁа11"> <сНу с^аЁа-ЁезЁ^с^=,’питЬе^1,’ с1аззНате="питЬег1"> {рагг && рагг[0]} </<±ГУ> &Ёгтез; <сИ^/ Ё1аЁа-ЁезЁ1с1="пи1пЬег2" с1аззНате=,,питЬег2"> {рагг && рагг[1]} </сИу> р </б1У> <ЬиЁЁоп опС11ск={ге1гезИ}>КеЁгез1а</ЬиЁЁоп> </с!г\^> ) } ехрогЁ беЁаиХЁ ОиезЁгоп Данный компонент отображает вопрос с произвольными перемножаемыми числа- ми, когда пользователь нажимает кнопку КеГгезЬ или когда родительский компо- нент передает ему новое значение геГгезкТгте. Мы отображаем компонент в ЗкиуЬоок, и похоже, что он работает должным обра- зом. При нажатии кнопки КеГгезЬ компонент обновляет вопрос. Поэтому на дан- ном этапе мы начинаем использовать этот компонент в основном приложении. Через некоторое время мы добавляем в компонент несколько дополнительных воз- можностей, но поскольку ни одна из них не вносит визуальных изменений, то мы не проверяем компонент в 81огуЬоок снова. Ведь, в конце концов, он будет выгля- деть так же, как и раньше. Правильно? В листинге 8.9 приведена модифицированная версия компонента ОиезЁюп в том ви- де, как он вставляется в основное приложение.
Листинг 8.9. Модифицированная версия компонента гтрогЁ { изеЕЁГесЁ, изеЗЁаЁе } Ёгот 'геасЁ' ЁтрогЁ '. /ОиезМоп. сзз' сопзё КАМСЕ =10 ёппсёёоп гапсЦ) { геЁигп МаЁЬ.Ё1оог(МаЁИ.гап<3от() * КАМСЕ + 1) } сопзё ОиезЁЁоп = ({ опАпзмег, геЁгезЬТЁте }) => { сопзё [рахг, зеЁРаЁг] = изеЗЁаЁе() сопзё гези1Ё = раЁг && раЁг[0] * раЁг[1] изеЕЁЁесЁ(() => { опАпзнег(гези1Ё) }, [опАпзыег, гези1Ё]) сопзё геЁгезЪ = () => { зеЁРаЁг((раЁг) => { геЁигп [гапсЦ), гапсЦ)] }) } изеЕЁЁесЁ(геЁгезЬ, [геЁгезЬТЁте]) геЁигп ( <сНу с1аззМате="СиезЁ1оп"> <с!гу с1аззМате="<2иезЁ1оп-с1еЁа11"> <сИу с1аЁа-ЁезЁ1с1="пи1пЬег1" с1аззМате="питЬег1"> {раЁг && раЁг[0]} </с11у> &ЁЁтез; «Ну баЁа-ЁезЁ1д="пип1Ьег2" с1аззМате="питЬег2"> {раЁг && ра!г[1]} </с11у> 9 </ЙГУ> <ЬиЁЁоп опС11ск={геЁгезЬ}>КеЁгезЬ</ЬиЁЁоп> </сИу> ) } ехрогЁ с1еЁаи1Ё СиезЁЁоп Размер этого фрагмента кода немного больше размера предыдущего. Мы добавили в него функцию обратного вызова опАпзиег, которая возвращает правильный ответ родительскому компоненту всякий раз, когда приложение генерирует новый во- прос. Поначалу кажется, что новый компонент работает в приложении должным образом, но затем происходит странная вещь. Когда кто-то решает просмотреть модифици-
рованный компонент в 81огуЬоок, он обнаруживает ошибку, как показано на рис. 8.5. Что здесь происходит? Мы добавили в код неявное допущение, что родительский компонент всегда будет передавать функцию обратного вызова опАпзмег в наш ком- понент. Поскольку рассказы 81огуЬоок отрисовали рассказ вазхс без функции опАпзиег, то мы получили ошибку: <0иез!11оп/> 0 5*огуЬоок О нпй сотрапегав - в Аптгаг Д Вайс П ОкаЫи) * а 0иез1юп В Ш ОзаЫей в ЯеэиП Сапиаз Оосз ф ф 3 [0 Ж Я ХЙО ТуреЕггог. олАпзиег 1з по! а ЮпсНоп. (1п 'опАпз*ег(геэий)', х ’опАпзшег1 Сз ипдеНпеб) (апопувоиз 1ипс»1оп) . /згс/0ие»1 1оп/0*«11оп. ] з: 1? 14 | ж» { 1! | // Ие пн4 сЬеск 1о амои ап еггог и | // И (олМвнег Ы гезиИ) < и 17 | <ИкАл(и*г{ге*и11>: 1» | * // } 19 | 1, ТопАпппг, гезиН]); И I Соп1го1$ АсНопв [В ® ТЫв аюгу № по! сспйдигей Н> 1гаги31е сотга!з. Ьеагп Гюм 1о мШ соШгеЯб * № 1приГ5 Тоипб №г №к сотропеп! Реай 1Ке Роев > Рис. 8.5. При просмотре новой версии компонента в ЫогуЬоок обнаруживается ошибка Важно ли это? Для такого простого компонента, как этот, нет, не важно. В конце концов, само приложение продолжало работать должным образом. Но неспособ- ность справиться с проблемой недостающих свойств, таких как отсутствующая функция обратного вызова в данном случае, или, что случается чаще, отсутствую- щие данные, является одной из самых типичных причин ошибок в Кеас1. Приложения часто генерируют свойства Кеас!, используя сетевые данные, а это означает, что первоначальные свойства, передаваемые компонентам, часто будут неопределенными или иметь значения пин. Обычно, чтобы избежать подобных проблем, желательно использовать безопасный по отношению к типам язык, на- пример Туре8спр1, или создавать тесты, которые проверяют способность компо- нентов справляться с проблемой недостающих свойств. Мы создали этот компонент без каких бы то ни было тестов, но с рассказом 81огуЬоок, который и уловил эту проблему. Так существует ли какой-либо способ создания теста, который бы автоматически проверял, что 81огуЬоок может отрисо- вывать все рассказы? Теперь создадим тест для этого компонента, который сохраним в файле рие81юп.1е$1.]$.
Рассмотрите возможность создания папки для каждого компонента. Вместо того чтобы просто поместить файл 0иез1юп.]з в папке зге, создайте папку згс/Оиезйоп и уже в этой папке сохраните файлы 0иез1юп.]з, Оиезиоп.з(опез.]з и ОиезИопЛез^з. Если затем добавить файл згс/Оиез1юп/|Пс1ех.]8, который выполняет экспорт по умолчанию компонента СиезЁЁоп, это не повлияет на остальной код, а также будет уменьшено число файлов, с которыми нужно работать другим разработчикам3. В файле теста мы можем создать тест 1ез1, который загружает все рассказы, а затем передает их функции гепйег в библиотеку ТезИп^ ЫЬгагу (листинг 8.10)4. Листинг 8.10. Код теста Зез1 йпрогЁ { гепбег } Ёгот '@ЁезЁ1пд-11Ьгагу/геасЁ' йпрогЁ ОиезЁЁоп Ёгош './ОиезЁЁоп' сопзё зЁогЁез = гедиЁге('./ОиезЁхоп.зЁогЁез') безсггЬе('ОиезЁЁоп', () => { 1Ё('зЬои1с1 гепбег а11 зЁогуЬоок зЁогтез мЁЁкоиЁ еггог’, О => { Гог (1еЁ зЁогу 1п зЁогЁез) { 1Г (зЁогу !== ’беГаиЁЁ’) { 1еЁ С = зЁогЁез[зЁогу] гепбег(<С />) ) } }) }) Если в ваших рассказах есть декораторы для предоставления таких элементов, как маршрутизаторы и стилистическое оформление, они не будут автоматически обнаружены этим методом. Поэтому их нужно добавить в метод гепбег в тесте. Исполнение этого теста выдаст ошибку: опАпзиег Ёз поё а ГипсЁЁоп ТуреЕггог: опАпзиег ёз поё а ЁипсЁЁоп Эту ошибку можно исправить, выполняя проверку на наличие функции обратного вызова перед ее вызовом (листинг 8.11). Листинг 8.11. Проверка на наличие функции обратного вызова изеЕЁ1есЁ(() => { // Нужно выполнить проверку, чтобы избежать ошибки И (опАпзиег && гези1Ё) { опАпзмег(гези1Ё) } }, [опАпзиег, гези1Ё]) 3 Подробности о структуре кода примера приложения можно найти в исходном коде, который можно загру- зить с репозитория СИНиЬ по адресу йПр5://огеП.1у/Р1Тщ. 4 Если у вас еще не установлена библиотека Тез1т§ ЫЬгагу, инструкции по ее установке см. в разделе 8.1.
Описанный метод позволяет создавать простые тесты компонентов с минимальны- ми усилиями. Целесообразно сначала создать рассказ для компонента, который не содержит вообще никаких свойств. Затем, прежде чем добавлять новое свойство, создайте рассказ, который использует его, и продумайте, каким будет ожидаемое поведение компонента. Хотя этот тест будет выполнять только простую отрисовку каждого рассказа, нет никаких оснований, препятствующих возможности импортировать один рассказ и создать тест на основе этого рассказа (листинг 8.12). Листинг 8.12. Код теста на основе рассказа 81огуЬоок гтрогЁ { гепбег, зсгееп } Ёгот ' @ЁезЁгпд-1гЬгагу/геасЁ' гтрогЁ изег Ёгот ' @ЁезЁгпд-1гЬгагу/изег-еуепЁ' гтрогЁ ОиезЁгоп Ёгот './ОиезЁгоп' гтрогЁ { Вазгс, КгЁЬЬгзаЫед } Ёгот '. /ОиезЁгоп. зЁоггез' гЁ (' зЬоиЁс! сНзаЫе Ёке ЬиЁЁоп мЬеп азкес!', 0 => { гепйег (СЙгЁЮгзаЫес! />) сопзё геЁгезкВиЁЁоп = зсгееп.деЁВуКоЁе('ЬиЁЁоп') ехресЁ (геЁгезЬВиЁЁоп.сИзаЫес!) .ЁоЕдиа! (Ёгие) }) Обсуждение Тесты отрисовки 81огуЬоок позволяют внедрить в приложение простое модульное тестирование, которое может обнаружить на удивление большое количество оши- бок регрессии. Значит, можно рассматривать тесты как примеры, которые помога- ют разработчику с проектированием кода, а не как искусственные продукты коди- рования, которые необходимо создавать просто, чтобы угодить руководителю группы. Создание тестов отрисовки для рассказов также полезно при работе в ко- манде, не имеющей большого опыта модульного тестирования. Создание визуаль- ных примеров позволяет избежать проблем, которые могут возникнуть вследствие восприятия разработчиками невизуальных тестов как слишком абстрактных. Это также может помочь разработчикам выработать привычку создавать тестовый файл для каждого компонента системы. Тогда при необходимости внести незначитель- ные изменения в компонент будет существенно легче добавить небольшую функ- цию модульного тестирования, прежде чем модифицировать что-либо. Исходный код данного рецепта можно загрузить на веб-сайте СгйНиЬ по адресу Ы1р8 ://оге11.1у/Р1Тсу.
8.3. Тестирование без сервера посредством Сургеев ЗАДАЧА Одна из главных особенностей высококачественного кода — способ его реагирова- ния на ошибки. Первое из списка восьми заблуждений, составленного Питером Дойчем (РеТег Пеи(8сЬ, ТЬе ГаПашез оГ П18<пЬи<ес1 СотриПп^, Ьирз://огеН.1у/ еШКС), — сеть является надежной. Ненадежна не только сама сеть, но также и подключаемые к ней серверы и базы данных. Рано или поздно вашему приложению придется иметь дело с какой-либо сетевой неисправностью. Это может быть потеря телефоном сетевого подключения, или выход из строя сервера, или сбой базы дан- ных, или отсутствие данных, которые вы пытаетесь обновить, поскольку их кто-то удалил. Какой бы ни была причина сетевой неисправности, вам потребуется ре- шить, что будет делать ваше приложение, когда эта неисправность возникнет. Эмулирование сетевых проблем в тестовой среде может оказаться трудной задачей. Код, который переключает сервер в состояние какой-либо ошибки, скорее всего будет создавать проблемы для других тестов или подключающихся к этому серверу пользователей. Так как же можно создать автоматизированные тесты для случаев сетевых сбоев? РЕШЕНИЕ В предлагаемом далее рецепте воспользуемся фреймворком для тестирования Сургезз, который мы рассмотрели в главе 1. Это по-настоящему выдающаяся тесто- вая система, которая стремительно становится нашим незаменимым инструментом во многих разработках. Чтобы установить Сургезз в свой проект, выполните следующую команду: $ прт 1пзГ:а11 —зауе-деу сургезз Работа Сургезз основана на автоматизации веб-браузера. В этом отношении фрейм- ворк похож на другие системы тестирования, например 8е1епшт. Но все же Сургезз отличается тем, что для него не нужно устанавливать отдельный драйвер, и он мо- жет как удаленно управлять браузером, так и внедрять себя в движок обработки 1ауа8спр{ браузера. Поэтому Сургезз может активно заменять основные части инфраструктуры Зауа8сг1р1 эмулированными версиями, которыми он может управлять. Например, Сургезз может заменить функцию 1ауа8спр1, которая предназначена для осу- ществления сетевых вызовов к серверу5. Поэтому тесты Сургезз могут имитировать поведение сетевого сервера и позволить разработчику клиентских приложений соз- давать искусственные ответы сервера. 5 Напрямую или косвенно посредством таких библиотек, как Ахюз.
Для этого рецепта мы будем пользоваться тем же приложением, что и для других примеров в этой главе. В него мы добавим сетевой вызов для сохранения результа- тов каждого ответа пользователя на вопрос. Это можно сделать, не создавая настоящего серверного кода, а эмулируя ответ в Сургезз. Чтобы продемонстрировать, как все это работает, мы сначала создадим тест, кото- рый эмулирует правильный ответ сервера, а затем тест, эмулирующий сбой сер- вера. После установки Сургезз создайте в папке сургезз/1п1е§габоп файл 0001-Ьаз1с-§ате- йтсНопз.]з и вставьте в него содержимое, приведенное в листинге 8.136. Листинг 8.13. Содержимое файла теста 0001-Ьа51с-дате4ипс1юпз.|8 йезсггЬе(’Вазгс дате ГипсМопз', О => { 1Ё(’зИои1й поЁНу 1Ье зегуег I 1озе', 0 => { // При условии, что приложение запущено // Когда вводится неправильный ответ // Сервер сообщает о проигрыше }) На данном этапе фрагменты кода, которые нам нужно будет написать, заменены комментариями-заполнителями. В Сургезз каждая команда и утверждение начинается с префикса су. Отобразить в браузере страницу Ы1р://1оса1Иоз1:3000 можно при помощи кода, приведенного в листинге 8.14. Листинг 8.14. Код для отображения в браузере страницы ЪНр://1оса1Ьоз1:3000 безсггЬе('Вазгс дате Типс^гопз', О => { 1С('зЬои1б побИу бЬе зегиег 1Г I 1озе', () => { // При условии, что приложение запущено су.У131С('Ьббр://1оса1Ьозб:3000') // Когда вводится неправильный ответ // Сервер сообщает о проигрыше }) }) Тест запускается на исполнение следующей командой: $ прх сургезз гип 6 Название файла не имеет значения, но мы следуем соглашению, согласно которому названия подобных тестов начинаются с номера рассказа. Это уменьшает вероятность конфликтов совмещающихся тестов и значительно облегчает отслеживание цели отдельных изменений.
Эта команда исполнит все тесты, не открывая браузера7. Исполнять тесты можно и другим образом, посредством следующей команды: $ прх сургезз ореп Данная команда отрывает окно приложения Сургезз, как показано на рис. 8.6. Рис. 8.6. Отображение теста в окне приложения Сургезз Если теперь выполнить двойной щелчок по файлу теста, то он откроется в браузе- ре, как показано на рис. 8.7. Приложение дает указание пользователю умножить два произвольных числа (рис. 8.8). Предлагаемые числа всегда будут в диапазоне от 1 до 10, и если ввести значение 101, то можно быть уверенным, что это будет неправильный ответ. Фреймворк Сургезз не обеспечивает прямого захвата текстового содержимого с экрана. Поэтому мы не можем просто считать значения этих двух чисел и сохра- уфк нить их в переменных, поскольку команды в Сургезз не выполняют действия в браузере сразу же. Вместо этого Сургезз добавляет введенную команду к цепочке команд, которые он исполняет в конце теста. Такой подход может показаться не- сколько странным, но он позволяет Сургезз справляться с большинством проблем, создаваемых асинхронными интерфейсами8. Отрицательным аспектом является 7 Этот способ повышает скорость выполнения тестов и записывает видео для каждого из них, что полезно при исполнении тестов на сервере интеграции. 8 Команды Сургезз во многом похожи на обещания, хотя таковыми не являются. Их можно рассматривать как "полу-обещания".
то, что никакая команда не может возвратить содержимое страницы, поскольку при исполнении команды страницы уже не будет. В других местах этой главы мы рассмотрим, как можно убрать случайность из тес- товых сценариев и сделать этот тест детерминированным, что устранит необходи- мость захвата данных со страницы. Найти поле ввода по селектору С88 можно при помощи команды су.де!:. А кнопку $иЬшк можно найти посредством команды су.соптахпз. Рис. 8.7. Тест Сургезз, исполняющийся в браузере 7x4? Рис. 8.8. Приложение дает указание пользователю вычислить произведение двух произвольных чисел
Листинг 8.15. Использование команд су.деб ису.сопбахпа дезсггЬе('Вазбс дате ХипсбХопз', () => { 1б('зЬои1с1 поб1Гу бЬе зегуег 1Г I 1озе', О => { // При условии, что приложение запущено су.У1511('Ьббр://1оса1ЬозТ:3000') // Когда вводится неправильный ответ су.деб('1приТ').буре('1011) су. сопбагпз (' ЗиЬпйб') . сИск () // Сервер сообщает о проигрыше }) }) Теперь нам нужно только протестировать, что приложение обращается к серверу с результатом ответа. Для этого мы используем команду су.бпбегсербО. Эта команда изменяет поведение сетевых запросов в приложении таким образом, чтобы можно было сымитировать ответ на данный запрос. Если результат будет отправляться методом розт в конце- вую точку /ар1/гези11, то сымитированный ответ создается, как показано в листин- ге 8.16. Листинг 8.16. Создание сымитированного ответа, су.бпбегсерб('РО5Т', '/ар1/гези1б', { збабизСоде: 200, Ьобу: }) Когда эта команда возымеет действие, сетевые запросы к концевой точке /ар1/ге$ик будут получать сымитированный ответ. Это означает, что прежде чем отправлять сетевой запрос, нам нужно исполнить команду су.гпбегсерк). Мы будем делать это в начале теста (листинг 8.17). Листинг 8.17. Исполнение команды су.1пг»гсерб безсггЬе('Вазгс дате ХипсбХопз', О => { 1Ё('зЬои1с1 поШу бЬе зегуег 1Г I 1озе', () => { // При условии, что приложение запущено су.Хпбегсерб(1Р05Т', '/ар!/гези1б', { збабизСобе: 200, Ьоду: '', }) су.У131б('Ьббр://1оса1Ьозб:3000') // Когда вводится неправильный ответ су.деб('Хприб').буре('101') су. сопбагпз (' ЗиЬтгб') . сИск ()
// Сервер сообщает о проигрыше }) }) Итак, мы подробно определили ответ сервера. Но как нам подтвердить, что прило- жение осуществило сетевой вызов, и как мы узнаем, что оно отправило правильные данные в концевую точку /ар1/ге$и11? Нам нужно присвоить сетевому вызову псевдоним (листинг 8.18). Это также позво- лит нам обращаться к запросу далее в тесте9 *. Листинг 8.18. Присвоение псевдонима сетевому вызову су.1пбегсерб('Р08Т', '/ар1/гези1б1, { збабизСобе: 200, Ьобу: 11, }) . аз (' розбКезиИ') Затем в конце теста мы можем создать утверждение, которое будет ожидать подачи сетевого вызова и проверять содержимое данных, отправляемых в теле запроса (листинг 8.19). Листинг 8Л9. Ожидание сетевого запроса и проверка содержимого данных I <1езсг1Ье (1 Вазгс дате ГипсМопз', О => { 1б('зЬои1с1 побИу бЬе зегуег И I 1озе', () => { // При условии, что приложение запущено су.гпбегсерЁ('РО5Т', '/ар!/гези1б', { збабизСойе: 200, Ьобу: }).аз('розРВези!!') су.у1з1б('Иббр://1оса1Ьозб:3000,) // Когда вводится неправильный ответ су.де!('хприб1).буре('101') су. сопбагпз (1 ЗиЬпйЛ') . сИск () // Сервер сообщает о проигрыше су.иаИ (' брозбВези!!’) .бкеп((хЬг) => { ехресб(хЬг.гедиезб.Ьойу.диезз).едиа!(101) ехресб (хбг.гедиезб.Ьоду.гезиИ) .едиа! ('ЬОЗЕ') }) }) }) 9 Команда су. гпбегсерб не может просто возвратить ссылку на сымитированный сетевой запрос вследст- вие помещения команд Сургезз в цепочку.
Данное утверждение проверяет два атрибута тела запроса на наличие ожидаемых значений. Если теперь исполнить наш тест, то он будет успешно выполнен, как показано на рис. 8.9. Рис. 8.9. Успешное завершение теста Теперь, когда у нас есть тест для успешного случая, можно приступать к написа- нию теста для неуспешного случая. В частности, при сетевом сбое приложение должно отобразить на экране соответствующее сообщение. По сути, для нас не важны подробности, отправляемые на сервер в этом тесте, но нам все равно нужно ожидать завершения сетевого запроса, прежде чем выполнять проверку на наличие сообщения об ошибке. Код теста приведен в листинге 8.20. Листинг 8.20. Код теста для сетевых сбоев 1Ё('зИои1с1 с!1зр1ау а теззаде I Саппов роз!: <±е гезиЗЛ1, 0 => { // При условии, что приложение запущено су.1пЁегсерЪ('РОЗТ', '/ар1/гези1Ё', { з^аЁизСойе: 500, ЬосЗу: { теззаде: 'Вас! ЁЫпд Ъаррепед! ’ }, )).аз('розЁВези1Ё') су.У131Ь (,^11:1:р://1оса1ИозЁ:3000,) // Когда вводится ответ су. деЬ (' 1при1:') . Ёуре ('16')
су.согЛахпз (’Ие аге ипаЫе 1:о зауе кЪе гези11:')-8Ьои1с1('по^.еххз!:') су. соп1:а1пз (' ЗиЬпиЛ') . сИск () // Выводится сообщение об ошибке су.нахб('@розбВези1б') су.сопбатпз ('Ке аге ипаЫе ёо зауе <±е гези1б') }) Обратите внимание на то, что проверка на отсутствие сообщения об ошибке вы- полняется до осуществления сетевого вызова, чтобы убедиться в том, что причиной ошибки является именно сетевой вызов. Кроме генерирования фиксированных кодов состояния и ответов команда суЛпбегсерб может выполнять и другие операции, такие как замедление времени ответа, дросселирование скорости сети или генерирование ответов от тестовых функций. Дополнительную информацию по этой команде можно найти в ее доку- ментации, доступной по адресу Ьйр8://огеП.1уЛс7Л<8. Обсуждение Тестирование при помощи фреймворка Сургезз может изменить к лучшему работу команды разработки, в частности придав ей способность имитировать сетевые вы- зовы. Программисты часто разрабатывают АР1-интерфейсы другим темпом, чем код фронтенда. Кроме того, некоторые команды разделяются на разработчиков, которые специализируются или по фронтенду, или по бэкенду. Фреймворк Сургезз может быть полезен в таких ситуациях, т. к. он позволяет разработчикам фронтенда создавать код для работы с еще несуществующими в настоящее время конечными точками. Он также может эмулировать все случаи патологических сбоев. Производительность сети может вносить в работу приложения несистематические ошибки. В средах разработки используются локальные серверы с небольшими объ- емами или вообще отсутствующими данными. Это означает, что производитель- ность АР1-интерфейса будет намного лучшей при разработке, чем в рабочей среде. Написать код, предполагающий немедленную доступность данных, очень легко, но в рабочей среде, где для прихода данных может потребоваться секунда или около этого, такой код окажется неработоспособным. Поэтому целесообразно иметь, по крайней мере, один тест для каждого вызова АР1- интерфейса, где ответ прибывает с опозданием в секунду или около этого (лис- тинг 8.21). Листинг 8.21. Тест с задержкой доступности данных су.гпкегсерЁ('СЕТ', '/арг/мхддекз', { зЁаЁизСойе: 200, Ьойу: [{ 1с1: 1, пате: 'Г1апде' }], с!е1ау: 1000, }) . аз (' деЁИМдеЁз')
Эмулирование медленных сетевых ответов часто выявляет целую кучу асинхрон- ных ошибок, которые в противном случае могут прокрасться в код. Очень важно, что создание искусственно замедленных сетевых ответов позволит вам ощутить общее воздействие на производительность каждого вызова АР1-ин- терфейса. Исходный код данного рецепта можно загрузить на веб-сайте СгйНиЬ по адресу Ьирз://огеН.1у/Р1Тд]. 8.4. Использование Сургезз для офлайнового тестирования ЗАДАЧА В этом рецепте используется специализированная команда Сургезз, разработанная Этьеном Бруинесом (Ебеппе Вгшпез) (Ьирз://огеН.1у/оОМНР). Приложения должны быть способными справляться с потерей подключения к сети. Мы уже рассмотрели в разделе 3.5, как создать хук для определения состояния под- ключения к сети. Но как протестировать офлайновое поведение? РЕШЕНИЕ Работу в офлайне можно эмулировать при помощи фреймворка Сургезз. Тесты Сургезз могут внедрять код, который модифицирует внутреннее поведение тести- руемого браузера. Поэтому мы должны быть в силах модифицировать сетевой код, чтобы эмулировать офлайновые условия. Для этого рецепта в приложение необходимо установить фреймворк Сургезз. Если фреймворк еще не установлен, установите его, выполнив следующую команду в папке приложения: $ прт гпзбаИ —заVе-с^еV сургезз Затем в папке сургезз/т1е§габоп создайте файл 0002-оГП1пе-\УОгк1п^.]з и вставьте в него код из листинга 8.22. Листинг 8.22. Содержимое файла 0002-оЯМле-шогктд.]8 дезсНЬе ('ОГТИпе иогкгпд', 0 => { 1Л ( ' зЬои1с1 бе!1 из нЬеп ме аге оШхпе1, // Тест должен сообщить нам, когда мы в офлайне { Ьгонзег: 1’ТггеТох' }, О => { // При условии, что приложение запущено // Когда приложение в офлайне, // выводится соответствующее предупреждение
II Когда приложение возвращается в онлайн, // предупреждение не выводится ] Этот тест игнорируется в браузере Яге(ох. Код эмулирования режима офлайн по- , Айк ' лагается на протокол удаленной отладки СНготе ОеуТоо18, который в настоящее время не поддерживается браузером ЯгеТох. Структура теста обозначена последовательностью комментариев. Все команды Сургезз начинаются с префикса су, поэтому приложение можно запустить, как показано в листинге 8.23. Листинг 8.23. Запуск приложений с Сургеев дезсгхЬе(’06611пе могкбпд', О => { 16 ( ’зИоиХд 6е11 из мИеп не аге оббИпе', // Тест должен сообщить нам, когда мы в офлайне ( Ьгомзег: ' !61ге6ох' }, О => ( // При условии, что приложение запущено су. (1 : //1оса11ю8Ъ: 3000') // Когда приложение в офлайне, // выводится соответствующее предупреждение // Когда приложение возвращается в онлайн, // предупреждение не выводится } ) }) Вопрос в том, как нам заставить браузер эмулировать работу в офлайне? Это можно сделать благодаря расширяемости фреймворка Сургезз. Добавление специализированной команды Сургезз су.пебмогк позволит нам переключаться меж- ду офлайновым и онлайновым режимами: су.пебмогк ({ ойИпе: бгие )) су.пебногк({ оббИпе: 6а1зе }) Добавление этой команды осуществляется вставкой кода, приведенного в листин- ге 8.24, в файл сургезз/зиррог^/соттапсЦз. Листинг 8.24. Код для добавления команды су .пеЬгогк | .. ................—.... -.... ....... - ...................... _д Сургезз.Соттапдз.асИ('пебмогк', (орМопз = {}) => { Сургезз.аиботаб1оп( 'гетобе^еЬиддегсргобосо!', {
соттапб: ' МеЁмогк. епаЫе', }) Сургезз. агЛотаМоп (' гетобе: беЬиддег: ргобосо!', { соттапс!: 1 Ыебыогк. ети1абеЫебногкСопсШ:1опз', рагатз: { оГ^Ипе: орбхопз.о^Шпе, 1абепсу: О, (ЗоипХоадТкгоидЬриР: 0, ирХоасГГЪгоидЪриб: 0, соппес!:1опТуре: ' попе1, }, }) )) Команда су.пеныогк эмулирует офлайновые сетевые условия посредством протокола удаленной отладки ОсуТоо18. Сохранив этот файл, можно приступать к реализации остальной части теста (листинг 8.25). Листинг 8.25. Полный код теста для работы в офлайне дезсгхЬе ('ОГ^Ипе иогкгпд', 0 => { И ( 'зИои1д беИ из ыкеп не аге оГГИпе', // Тест должен сообщить нам, когда мы в офлайне { Ьгоизег: ' !НгеГох' }, О => { // При условии, что приложение запущено су.угзлЛ (1 Ьббр^/ТосаНюзбгЗООО') су.сопбагпз(/уои аге сиггепбХу оГГИпе/1) .зЪои!д( 'поб.ехгзб') // Когда приложение в офлайне су.пебыогкЦ оГГИпе: бгие }) // Выводится соответствующее предупреждение су.сопбагпз (/уои аге сиггепбЗу о^ЕИпе/1) .зЬои!с!('Ье^131Ые') // Когда приложение возвращается в онлайн су.пеЬиогк( { оТГИпе: Га1зе }) // Предупреждение не выводится су.сопбахпз (/уои аге сиггепМу оГ^Ипе/1) .зкоиЗсЦ ’поЁ.ех1зб') } ) }) Если теперь исполнить наш тест, то он будет успешно выполнен, как показано на рис. 8.10.
Рис. 8.10. Исполнение теста работы в онлайне и офлайне можно просматривать, щелкая по левой панели Обсуждение Подобные команды можно создать для эмулирования различных сетевых условий и скоростей. Подробная информация о работе команды су.петиогк предоставляется в блоге ком- пании Сурге$8.ю по адресу Ьир8://огеН.1у/РВ4гО. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ь«р8://огеН.1у/Р1Тд]. 8.5. Использование $е1епшт для тестирования в браузере ЗАДАЧА Нет лучшего способа тестирования кода, чем его исполнение в настоящем браузе- ре, а применение веб-драйвера— наиболее распространенный способ создания автоматизированных тестов для исполнения в браузере. Большинством браузеров можно управлять, посылая команду на сетевой порт. Для разных браузеров сущест- вуют разные команды, и инструмент командной строки в виде веб-драйвера упро- щает управление браузером.
Но как можно создать тест для приложения К.еас1, который использует веб- драйвер? РЕШЕНИЕ Для решения этой задачи мы воспользуемся фреймворком 8е1епшт, который пре- доставляет единообразный АР1-интерфейс для целого ряда разных веб-драйверов. Это означает, что код теста для браузера ГпеГох должен работать также с браузера- ми СЬготе, 8аГап и Ес1§е10 11. Для этого рецепта мы возьмем то же самое приложение, что и для остальных рецептов в этой главе. Это математическая игра, в которой пользователь должен решить простую задачу на умножение. Версии библиотеки 8е1епшт доступны для целого ряда разных языков программи- рования, например РуДюп, Лауа и С#. Мы выбрали версию для Дауа8спр1: 8е1ешшп \УеЬЭпуег. Начнем с установки этого фреймворка: $ прт 1пзба11 —заVе-с^еV зе1еп1ит-меЬс1г1уег Также нам нужно установить, по крайней мере, один веб-драйвер. Веб-драйверы можно устанавливать глобально, но работа с ними будет более управляемой при установке в разрабатываемое приложение. Мы установим веб-драйвер сЬготебгпгег для браузера СЬготе (для браузера Нге Гох нужно установить драйвер дескобггуег): $ прт 1пз1:а11 —зауе-деу сЪготес!г1уег Теперь можно приступать к созданию теста. Целесообразно разместить тесты 8е1епшт в папке зге приложения, т. к. это облегчит использование среды разработ- ки для ручного исполнения тестов. Далее, создадим в папке зге подпапку зе1епшт, а в ней файл 0001-Ьаз1с-§ате-Гипс1оп8.зрес^8 и вставим в этот файл код из листин- га 8.26". Листинг 8.26. Содержимое файла 0001-Ьа81с-датеТипсйоп8.5рес.]5 дезсгхЬе ('Вазгс дате ГипсЫопз', () => ( И: ('зЬои1с1 бе11 те 1Г I ыоп', 0 => ( // Тест должен сообщить нам о выигрыше // При условии, что приложение запущено // При вводе правильного ответа // Отображается сообщение о выигрыше }) }) 10 Это не означает, что тесты будут работать с каждым браузером, а гарантирует только то, что они будут исполняться на всех браузерах. 11 Мы просто следуем соглашению, согласно которому название каждого теста начинается со связанного номера истории. Но для 5>е1еп1ит это не обязательно.
Содержимое теста изложено в общих чертах в комментариях. [- 4^-| Несмотря на то что размещение тестов 8е1егнит в дереве папок зге делает удоб- ^2 н°й работу с ними, это может вызывать их исполнение такими инструментами, как Зез1, как будто бы они были модульными тестами. Может возникнуть проблема, если вы непрерывно исполняете модульные тесты в фоновом режиме. Например, если вы создали приложение с помощью средства сгеа^е-геасъ-сот и оставили исполняющейся команду прт тип тезе, то обнаружите, что при каждом сохранении теста 8е1еп1ит на экране будет внезапно открываться окно браузера. Этого можно избежать, приняв какой-либо определенный способ именования файлов, чтобы различать между тестами 8е1еп1ит и модульными тестами. Например, если все файлы тестов 8е1еп1ит именовать в формате *.8рес.]в, то тестовый сценарий мож- но модифицировать, чтобы избегать их, указав в нем тесты гвас1-зспр(з ,*Лез1]з'. Фреймворк 8е1ешшп автоматизирует веб-браузер при помощи веб-драйвера. Эк- земпляр веб-драйвера можно создавать в начале каждого теста, как показано в лис- тинге 8.27. Листинг 8.27. Создание экземпляра веб-драйвера йпрогЁ { ВиИйег } Ггот ' зе1еп1ит-иеЬс1г1уег ’ 1е^ йгхуег йезсггЬе('Вазхс дате {ипсМопз', () => { Ье^огеЕасЬ(О => { йг^ег = пей ВиНйегО ЛогВгоызег('сЬготе') .ЬиНсЦ) }) а Л:егЕасЪ(О => { дгплег. диз± () }) 1Т (’зЪои1с1 Ье11 те И I моп', О => { // Тест должен сообщить нам о выигрыше // При условии, что приложение запущено // При вводе правильного ответа // Отображается сообщение о выигрыше }) }) В данном примере создается веб-драйвер для браузера СЬготе. Создавая веб-драйвер для каждого теста, мы также создаем новый экземпляр браузера для каждого теста, тем самым гарантируя, что между тестами нет ника- ких промежуточных состояний браузера. Это позволит нам исполнять тесты в лю- бом порядке, что было бы невозможно при наличии общих состояний браузера. Если тесты полагаются, например, на информацию из базы данных, необходимо обеспечить правильную инициализацию сервера тестом при его запуске. Кроме того, чтобы 8е1епшш мог создать экземпляр драйвера, нам нужно явно затребовать драйвер, как показано в листинге 8.28.
Листинг 8.28. Вызовдрайвейа нпрогТ { ВиНдег } Ггот ’зе1еп1ит-неЬдг1чег' гшцап (' сЬгапвс1г1ч*г') 1еГ дггчег дезсг1Ье('Вазгс дате ГипсМоиз', () => { ЬеГогеЕасЬ(() => { дггчег = пен ВиИдег (). ГогВгомзег (’ скготе *). ЬиНд () }) аГГегЕасЬ(О => { дгГчег.дизХ () }) ПГзЬоиМ Се11 те 1Г I моп', () => { // Должно появиться сообщение о выигрыше // При условии, что приложение запущено // При вводе правильного ответа // Отображается сообщение о выигрыше }) }) Теперь можно приступать к созданию остального кода теста. Версия 8е1епшт для 5ауа8спр{ имеет высокую степень асинхронности. Практически все команды воз- вращают обещания, что означает высокую эффективность, но также создает очень благоприятные условия для возникновения ошибок тестирования. Начнем наш тест с открытия приложения, как показано в листинге 8.29. ' Листинг 8.29. Открытие приложения нпрогГ { ВиНдег } Ггот ' зе1еп1ит-ыеЬс1г1Уег' гедиГге('сЬготедгхчег’) 1еГ бгхчег дезсггЬе(’Вазхс дате ГипсГГопз', аяупс () => { ЬеГогеЕасМ О => < дгГчег = пен Ви11дег(). ГогВгочзег (' сЬготе') .ЬиПд() }) аГГегЕасЬ(() => { бгГчег. дигГ () }) 1Г('зЬои1б Ге11 те 1Г I коп’, 0 => ( // Тест должен сообщить нам о выигрыше // При условии, что приложение запущено амахЪ <1г1у«г.даЪ('ИГГр://1оса1ИовЪ:3000') // При вводе правильного ответа // Отображается сообщение о выигрыше }, 60000)
Команда Щгуег.деЁ дает указание браузеру открыть данный 1ЖЕ-адрес. Но чтобы эта команда могла работать, нам нужно было внести еще две модификации. Первое, мы пометили функцию тестирования ключевым словом азупс, что позволит нам ожидать обещание, возвращаемое методом аггуег.деЁ. Второе, мы добавили в тест значение тайм-аута величиной в 60 000 миллисекунд, заменив неявный пятисекундный предел тестов ЗезЕ Если не увеличить значение тайм-аута, то тест завершится неуспешно прежде, чем запустится браузер. В дан- ном случае значение 60 000 миллисекунд было выбрано, чтобы обеспечить работу теста на любой машине. Это значение можно откорректировать в соответствии с имеющимся оборудованием. Чтобы ввести правильный ответ на вопрос, нам нужно прочитать два числа этого вопроса (рис. 8.11). Рис. 8.11. В игре нужно вычислить произведение двух произвольных чисел Эти два числа, а также кнопки 1прщ и зиЬпих на странице можно найти при помощи команды ЛпдЕТетеш:, как показано в листинге 8.30. Листинг 8.30. Использование команды Г1паЕ1етепъ сопзё пигоЬег! = аыаЁЁ дгхуег . Ё1пс1Е1етспЁ (Ву.сзз (1 .питЬег1')) .деЁТехЁ () сопзё пшпЬег2 = аиаЁЁ с!ггуег. Е1пс1Е1етепЁ (Ву.сзз (' .питЬег2')) .деЁТехЁ () сопзё ЁприЁ = аыаЁЁ бгл^ег. Ё1пс1Е1етепЁ (Ву. сзз (' ЁприЁ')) сопзё зиЬтЁЁ = амаЁЁ бгЁуег. Ё1пс!Е1етепЁ ( Ву.храЁЬ("//ЬиЁЁоп[ЁехЁ()=’5иЬт1Ё']") ) Если при считывании со страницы набора элементов точный порядок их обработки не имеет значения, то при помощи функции Ргопйзе.аП все эти элементы можно объединить в одно обещание, которое потом можно ожидать (листинг 8.31). Листинг 8.31. Объединение нескольких элементов в одно обещание сопзё [пшпЬег1, питЬег2, ЁприЁ, зиЬтЁЁ] = амаЁЁ РготЁзе.аН ([ скЁуег. Ё1пс1Е1етепЁ (Ву.сзз (' .питЬег! ') ) . деЁТехЁ () ,
бггуег. Е1пс1Е1етепЕ (Ву.сзз (' .питЬег2')) .деЕТехЕ (), дгтуег.ЕГпдЕЕетепЕ (Ву.сзз ('ЕприЕ')), дггуег. Е1пдЕ1етепЕ (Ву.храЕЬ("//ЬиЕЕоп[ЕехЕ () ='ЗиЬпиЛ' ] ") ), ]) В нашем примере приложения данная оптимизация практически не экономит нам время, но если страница отрисовывает разные компоненты в неопределенном порядке, то объединение обещаний может улучшить производительность теста. Теперь мы можем реализовать следующую часть нашего теста (листинг 8.32). Листинг 8.32. Использование команды ГхпаЕ1етепЬ в тесте ГтрогЕ { ВиПс1ег, Ву } Егот ' зеЕептт-меЬбгГ^ег' гедиЕге('сЬготебгЕуег') 1еЕ дгГ-уег безсггЬе(1ВазЕс дате ЕипсЕЕопз', азупс () => { ЬеЕогеЕасЬ(() => { дгхуег = пен ВиНбег () .ЕогВгомзег( 'сИготе') .ЬиНсЦ) }) аЕЕегЕасЬЦ) => ( дгНег .ди1Е() }) 1Е('зЬои1д Ее11 те 1Е I иоп', () => { // Тест должен сообщить нам о выигрыше // При условии, что приложение запущено аиаЕЕ дги/ег .деЕ (1ЬЕЕр: //1оса1ЬозЕ:30001) // При вводе правильного ответа сопзб [питЬегТ, питЬег2, ЕприЕ, зиЬтЕЕ] = аиа1Е РготЕзе.аН([ бгГл/ег. Е1пс1Е1етепЕ (Ву.сзз (' .питЬег! ')) .деЕТехЕ (), с1г1уег. Е1пс1Е1етепЕ (Ву.сзз (' .питЬег2')) .деЕТехЕ (), бггуег. Е1пс1Е1етепЕ (Ву.сзз (' ЕприЕ1)), сГггуег. Е1пс1Е1етепЕ (Ву.храЕЬ("//ЬиЕЕоп [ЕехЕ () = 'ЗиЬтГЕ' ] ")), ]) анахЕ ЕприЕ.зепбКеуз('1 + питЬег1 * питЬег2) анагЕ зиЬпи_Е.сНск() // Отображается сообщение о выигрыше }, 60000) }) Обратите внимание на то, что мы объединяем обещания, возвращаемые методами ЕприЕ.зепсЖеуз и зиЬтьЕ.сИск, поскольку хотим, чтобы тест ввел ответ в поле ввода прежде, чем мы его отправим. Наконец, мы хотим выполнить утверждение, что сообщение "Уои Ьа\е \уоп!" будет отображено на экране (рис. 8.12). Это утверждение можно написать, как показано в листинге 8.33.
6x6? бис в Уои Ьауе \Л/оп! аге ипаЫе (о хауе (Ье гезиГ аюивт» Рис. 8.12. Приложение сообщает пользователю о вводе им правильного ответа Листинг 8.33. Один из вариантов написания утверждения о выигрыше сопзГ гези1ГТехГ = аиахГ бггиег .Г1пс1Е1етепГ (Ву.сзз (' .Кези1Г')) .деГТехГ() ехресГ(гези1ГТехГ).ГоМаГсИ(/иоп/1) Этот код почти наверняка будет работоспособным, поскольку результат отобража- ется почти сразу после предоставления ответа пользователем. Приложения Кеас( часто медлят с отображением динамических результатов, особенно если они пола- гаются на сетевые данные. Но если модифицировать код приложения, чтобы эму- лировать двухсекундную задержку перед отображением результата12, то наш тест выдаст сообщение об ошибке, приведенное в листинге 8.34. Листинг 8.34. Сообщение об ошибке при двухсекундной задержке отображения результата по зисЪ е1етепГ: ПпаЫе Го 1осаГе е!етепГ: {"теГЬос!": "сзз зе1есГог", "зе1есГог":".Вези1Г"} (8езз1оп 1пГо: сЪготе=88.0.4324.192) ЫоЗисЬЕХетепГЕггог: по зисЬ е1етепГ: ПпаЫе Го 1осаГе е1етепГ: { "теГЬосГ:"сзз зе1есГог","зе1есГог":".Вези1Г") (Зеззхоп 1пГо: сйготе=88.0.4324.192) Этой проблемы можно избежать, ожидая до тех пор, пока элемент не появится на экране, а затем подождав еще некоторое время, пока текст не совпадет с ожидае- 12 Соответствующий код находится в исходном коде для этой главы, который можно загрузить с репозито- рия СйНиЬ по адресу 1Шр5://оге||.1у/Р1Т(ц.
мым результатом. Оба интервала ожидания можно реализовать при помощи метода с1г5^ег.иа1х, как показано в следующем фрагменте кода: аыа1б бг1^ег.ыа!б(ипбИ.е1етепбЬосабед (Ву.сзз(' .Вези1Р'))) сопзб гези!бЕ1етепб = с!г1Уег. б!.пс1Е1етепб (Ву.сзз (' .Кези1б1)) аыагб дггчег.ыагб (ипбН.еТетепбТехбМабсЪез (гези1бЕ1етепб, /ыоп/1)) Эго позволяет нам создать окончательную версию нашего теста, приведенную в листинге 8.35. Листинг 8.35* Окончательная версия теста йпрогб ( ВиНбег, Ву ) бгот ' зе1еп1ит-меЬдг1уег' гедигге('сЬготедггуег1) 1еб дггуег дезсггЬе('Вазгс дате бипсбхопз', азупс () => { ЬебогеЕасЪ(() => { дггуег = пен ВиИбег (). богВгоызег (' сЬготе1). ЬиИд () }) аГбегЕасЬ(() => { дггуег.дигб() }) 16 (’зЬои1с! бе!1 те 16 I коп', () => { II Тест должен сообщить нам о выигрыше // При условии, что приложение запущено анабб дгпгег.деб (,Ьббр://1оса1Ьозб:3000,) // При вводе правильного ответа сопзб [питЬег1, питЬег2, гприб, зиЬпйб] = акабб Ргопйзе.аН ([ дггуег.б1пдЕ1етепб (Ву.сзз (' .питЬег1')).дебТехб (), <1г1уег.б1п<1Е1етепб(Ву.сзз(' .питЬег2')) .дебТехбО, дгп/ег.€1п<1Е1етепб (Ву.сзз (' бприб')), бгхуег . 61пс1Е1етепб (Ву.храбЬ ("//Ьиббоп[бехб () =' ЗиЬтгб']")), ]) акабб бприб.зепдКеуз('' + питЬег1 * питЬег2) акабб зиЬтбб.с1бск() // Отображается сообщение о выигрыше аиабб бгбуег.иабб(ипбб1.е!етепбЬосабес1 (Ву.сзз('.Везибб'))) сопзб гези1бЕ1етепб = с1гбуег.ббпс1Е1етепб(Ву.сзз(' .Везибб')) аиабб йгбуег.кабб(ипббб.ебетепбТехбМабсЬез(гезиббЕбетепб, /коп/б)) }, 60000) }) Обсуждение На основе своего опыта мы можем сказать, что веб-драйверы являются наиболее часто используемой формой автоматизированных тестов для веб-приложений. Эти тесты неизбежно зависят от наличия совпадающих версий браузера и веб-драйвера, а также имеют репутацию склонных к спорадическим сбоям. Такие ошибки обычно
вызваны проблемами с таймингом, которые чаще всего происходят в одностранич- ных приложениях, способных к асинхронному обновлению своего содержимого. Указанных проблем можно избежать, тщательно добавляя в код временные за- держки и повторные попытки, но это может сделать тесты чувствительными к из- менениям в среде исполнения, например при исполнении приложения на другом сервере для тестирования. В качестве альтернативного решения этой проблемы, в случае большого количества спорадических сбоев, можно переместить большую часть тестов на систему наподобие Сургезз, которая менее чувствительна к ошиб- кам с таймингом (Ьйр8://огеП.1у/12Л2Т). Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ьйрз://оге11.1у/Р1Т д]. 8.6. Тестирование внешнего вида на разных браузерах посредством 1тадеМад!ск ЗАДАЧА Внешний вид приложений может существенно отличаться при просмотре в разных браузерах. Более того, они могут выглядеть по-другому даже на том же самом браузере, но на другой операционной системе. Например, браузер Сйготе обычно скрывает полосы прокрутки при исполнении на компьютерах Мас, но отображает их на компьютерах ХУшбоууз. К счастью, старые браузеры наподобие 1п(егпе( Ехр1огег наконец-то начинают исчезать, но даже современные браузеры могут при- менять С88 слегка по-другому, коренным образом изменяя внешний вид страницы. Но постоянная проверка внешнего вида приложения вручную на ряде браузеров и платформ может оказаться довольно трудоемкой задачей. Можно ли каким-либо образом автоматизировать этот процесс проверки совмести- мости? РЕШЕНИЕ Да, можно. И в этом рецепте мы продемонстрируем, как это делается совместными усилиями трех инструментов: ♦ 81огуЬоок— предоставляет нам базовую галерею всех компонентов во всех релевантных конфигурациях, которые нужно проверить. ♦ 8е1ешит — позволяет выполнять захват внешнего вида всех компонентов 81огуЬоок. А прокси-сервер 8е1епшт Опб даст возможность выполнять удален- ное подключение к браузерам, функционирующим на разных операционных системах, чтобы сравнить внешний вид приложений в каждом случае. ♦ 1ша§еМа^1ск— в частности, мы воспользуемся инструментом сошраге этого комплекта программ для выявления различий между двумя снимками экрана и предоставления числового значения величины этих различий.
Начнем с установки среды разработки 81огуЬоок, исполнив в папке приложения следующую команду: $ прх зЬ 1П1Л Далее нам необходимо создать рассказы для каждого из компонентов и конфигура- ций, которые нужно отслеживать. Информацию о том, как это делается, можно найти в других рецептах этой книги или в учебных пособиях по 81огуЬоок (ЬИр$://огеП.1у/ак7У\У). Затем нам нужно установить фреймворк 8е1епшт, чтобы автоматизировать процесс захвата снимков экрана. Эта установка выполняется следующей командой: $ прт 1пз11а11 —заVе-с^еV зе1еп1ит-меЬбг^ег Также потребуется установить соответствующие веб-драйверы для тестируемых браузеров. Например, чтобы автоматизировать браузеры РиеГох и СЬготе, нужно установить следующие веб-драйверы: $ прт хпзГаИ —зауе-деу дескойгхуег $ прт гпзЁаП —зауе-с!еу сЪготейггуег Наконец, необходимо установить пакет инструментов командной строки для мани- пулирования изображениями 1та§еМа§1ск. Информация по установке этого пакета доступна на странице для его загрузки (ЬЦр$://огей.1у/МО0А). Для данного примера вновь воспользуемся приложением математической игры, с которым мы работали ранее в этой главе. На рис. 8.13 показано отображение ком- понентов этого приложения в 81огуЬоок. Сервер 8(огуЬоок для приложения запускается следующей командой: $ прт тип зЁогуЬоок 0ЗДогуЬоок С Рии) сотропетз * ® Алзуюг П Ввз'с I ШПЬОкаЫей - ев 0 Вм1с П №№ ОюаЫей - № ЛезиЙ Сапуаз Оосз ® €( С1 Е 88 В I' Й б Той кауе АУоп! Р1ау аоат Ва51с О ИЛШОмевв й 1ЛЛЙ1 АпБ^ег й 1Лй1й Птей 0<л й ИйЬ Ма1сЬе<1 биезз Апй Ал$\мег О ШКН М|зта1сЬе<1 Оиезз Ап<1 Апзмег И ® СОП1ГО15 Асгк>лв ТЪйз 8(огу « по1 сопйдигеЛ (о Кап<№ соп1го1з Сеагп Ко» ю «1д соп1го15 » 1Чо 1приК Гоипй (ог №1$ сотролепТ. Йеай №е Йосз *
Теперь создадим тест в виде простого сценария для захвата снимков экрана каждо- го из компонентов в 81огуЬоок. Для этого в папке 8гс/8е1епшт создайте файл зЬо^з.зрес^з и вставьте в него код из листинга 8.3613. Листинг 8.36. Код сценария еЬоГз.зрес.]» ттрогЁ { ВиПбег, Ву, ипЁ11 } Ггот 1 зеХепхит-иеЬбгдл/ег' гедитге(’сЬготебгЁуег') 1еЁ Гз = геди1ге(’Гз') безсгЁЬе('зёюёз', () => { 1Ё('зкои1б Гаке зсгеепзЪоГз оГ зГогуЬоок сотропепЁз', азупс () => { // Тест должен сделать снимки экрана компонентов ЗГогуЬоок сопзГ ЬгоизегЕпу = р^осезз.епV.ЗЕ^Е^I^М_ВкОИЗЕВ I| 1скготе' сопзё иг! = ргосезз.епу.ЗТАКТ-ШЬ || 1ЬГГр://1оса1ЬозГ:6006' сопзё дгзлгег = пен ВиНбег () .ГогВгомзег( 'сЬготе') .ЬиИб() дг1уег .тападе () .ыГпбом (). зеГВесГ ({ М16ГЬ: 1200, ЬехдЬГ: 900, х: 0, у: 0, }) сопзё оиГриГБтг = './зсгеепзЪоГз/' + ЬгоызегЕпу Гз.тксЦгЗупс(оиГриГВЁг, { гесигзЁуе: Ёгие }) аиатЁ бгадгег.дег(иг1) аматГ дггуег.ыа1Ё( ипГ11.е1етепГЬосаГес1(Ву.с1аззЫате (' з1беЬаг-1Гет')), 60000 ) 1еЁ е1етепЁз = аыатГ бг1уег.Г1пбЕ1етепГз( Ву.сзз ('ЬиГГоп.зГбеЬаг-ГГет') ) Гог (1еГ е оГ е1етепГз) { сопзё ехрапбеб = ама1Г е.деГАГГгьЬиГе('агГа-ехрапбеб') 1Г (ехрапбеб !== 'Ёгие') { амабг е.сПск() } } 1еЁ Ипкз = аиа1Ё бггуег.Г1пбЕ1етепГз (Ву.сзз ('а.з1беЬаг-1Гет')) Гог (1еЁ Ипк оГ Ипкз) { аиа1Г Ипк.сИскО сопзё з = амагГ Ипк.деГАГГгГЬиГе (' 16') 1еЁ епсобебЗГгГпд = амаЁГ бгГуег .Г1пбЕ1етепГ(Ву.сзз('йзЁогуЬоок-ргеухек-мгаррег')) .ЁакеЗсгеепзкоЁ() 13 В принципе, этот сценарий можно было бы сохранить в любой папке, но мы использовали данную папку в коде примера на веб-сайте ОйНиЬ.
аиа1Л Гз.ыг1ЛеЕНе8упс( '${ои^ри!:01г}/${з} .рпд', епсос!ес18Рг1Г1д, 'Ьазе64’ ) с1г1уег.диИ() }, 60000) }) Данный сценарий запускает браузер для сервера 81огуЬоок, открывает каждый из компонентов и делает снимок экрана каждой истории, которые он сохраняет в под- папке в зсгеепзЬо^з. Для захвата снимков экрана компонентов можно было бы использовать другую систему тестирования, например Сургезз. Но преимущество 8е1етшп состоит в том, что мы можем удаленно открыть браузерную сессию на компьютере. По умолчанию тест зйо^з.зресЗз будет делать снимки экрана компонентов 8(огуЬоок по адресу ЬПр://1оса1Ьоз1:6006 в браузере СЬготе. Предположим, что это приложе- ние тестирования исполняется на компьютере Мас. Если у нас в этой же сети есть компьютер \Утс1о\\з, мы можем установить прокси-сервер 8е1епшт Сг1д (Ы1рз:// огеН.1у/§ТЬдз), который позволит удаленным компьютерам запускать сессию веб- драйвера. Если компьютер ХМтсклУЗ имеет адрес 192.168.1.16, то, прежде чем запускать на исполнение тест зЬоТз.зрес^з, мы можем присвоить значение соответствующей пе- ременной среды из командной строки: $ ехрог! 8ЕЬЕЩЦМ_ВЕМ0ТЕ_ЦВЬ=ЪЫ;р://192.168.1.16:4444/ид/ЬиЬ Поскольку компьютер ^Утдо^з будет обращаться к серверу 8<огуЬоок на компью- тере Мас с 1Р-адресом, например, 192.168.1.14, нам также нужно присвоить значе- ние переменной среды и для этого компьютера: $ ехрогС 8ТАКТ_ЦВЬ=Ь«р://192.168.1.14:6006 Также можно указать, какой браузер использовать на компьютере \У1пс1оууз14: $ ехрогЁ 8ЕЬЕМШМ_ВКОИ8ЕК=Е1ге^ох Далее в файле раскате.) зоп создадим сценарий для запуска теста зЬо18.8рес.]з (лис- тинг 8.37). Листинг 8.37. Сценарий для запуска теста вЬоШ.ерес.|е "зсгхр^з": { 14 Чтобы все это работало, на удаленном компьютере должны быть установлены соответствующий браузер и веб-драйвер.
"1:е51:511о1:5": "С1^1:гие геас1:-5сг1р1:5 1:е51: —с!е1:ес1:0репНапс11е5 \ 15е1еп1ит/5Ьо1:5.5рес .35'" Теперь мы можем запустить тест на исполнение и делать снимки экрана каждого компонента: $ прт гип 1:651:51101:5 Используя созданные нами ранее переменные среды, тест установит контакт с про- кси-сервером 8е1епшш бпд на удаленном компьютере. В частности, тест даст запрос прокси-серверу открыть браузер РиеРох с подключением к локальному сер- веру 81огуЬоок. Затем тест отправит по сети на удаленный компьютер снимки экра- на каждого компонента 8(огуЬоок, сохраняя их в папке зсгеепзЬо^/йгеГох. После исполнения теста для браузера РиеРох исполняем его для браузера СЬготе: $ ехрогр 5ЕЬЕЫ1ЦМ_ВК0К5ЕЙ=сЪготе $ прт гип Ре5Р5ЪоГ5 Снимки экрана этого теста сохраняются в папке 8сгееп8Ьо18/сЬготе. Более полная реализация этого метода может также сохранять данные об исполь- зуемой операционной системе и типе клиента (например, размере экрана). Теперь нужно проверить визуальные различия между снимками экрана для СЬготе и Р1геГох, для чего нам пригодится пакет 1та§еМа§1ск. Инструмент сотраге этого пакета может генерировать изображение, в котором выделяются визуальные разли- чия между двумя сравниваемыми изображениями. Для примера рассмотрим два снимка экрана одного и того компонента с ПгеГох и СЬготе, показанные на рис. 8.14. НйгевЬ
На первый взгляд эти два изображения выглядят идентичными. В папке приложе- ния выполним следующую команду: $ сотраге -Еигг 15% зсгеепзИоЬз/^хге^ох/диезЁхоп—Ьазхс.рпд \ зсгеепзИоЬз/сЬготе/диезЬгоп—Ьазхс.рпд сНПегепсе.рпд Эта команда создает новое изображение (рис. 8.15), показывающее различия между двумя исходными изображениями. Рис. 8.15. Изображение, показывающее различия между двумя исходными изображениями снимков экрана Созданное командой сотраге изображение состоит из пикселов двух исходных изо- бражений, которые визуально различаются больше чем на 15%. При этом на глаз эти изображения снимков экрана кажутся практически идентичными. Такая информация полезна, но все равно требует исследования изображений чело- веком, чтобы установить, насколько значительны различия. Какие еще сравнения можно выполнить? Инструмент сотраге также может выдавать число, соответствующее различию меж- ду двумя исходными изображениями: $ сотраге -теТгхс АЕ -Гигг 15% зсгеепзИо^з/ЕхгеГох/диезЬхоп—Ьазхс.рпд зсгеепзЬоЬз/сЬготе/диезЫоп—Ьазхс. рпд сНЕЕегепсе. рпд 6774 Цифра 6774 представляет числовое значение (на основании подсчета абсолютных ошибок) визуальной разницы между двумя исходными изображениями. Для при- мера сравним два снимка экрана компонента Апзиег с включенным свойством ЬхзаЫес! (рис. 8.16). Для этих двух изображений команда сотраге возвращает намного большее числовое значение: $ сотраге -теЁгхс АЕ -1игг 15% зсгеепзЬоЬз/ЛгеГох/апзмег—ихЬЬ-сНзаЫесЬрпд зсгеепзЬо^з/сИготе/апзмег—чхЬЬ-с!хзаЫес1.рпд сПГГегепсеЗ.рпд 28713 Рис. 8.16. Компонент Апзууег с включенным свойством сйзаЫеб, отображенный в СЬготе и НгеГох
А на изображении визуальных различий этих двух снимков экрана (рис. 8.17) мож- но точно увидеть, где находятся эти различия: в отключенном поле ввода. На рис. 8.18 приводится аналогичное значительное числовое значение различия (21 131) для компонента, шрифты которого отображаются по-разному в разных браузерах в результате некоторых С88 атрибутов, специфичных для браузера МогШа. Шеи: Я 5сэ1П 11 Рис. 8.17. Визуальные различия между формами СЬготе и ЕиеГох Рис. 8.18. Текст компонента отображается по-разному в СЬготе и Е(ге(ох Собственно говоря, можно создать сценарий оболочки для обработки каждого изо- бражения и формирования небольшого отчета, содержащего визуальные различия между изображениями вместе с числовыми значениями этих различий. Такой сце- нарий приведен в листинге 8.38. Листинг 8.38. Сценарий для создания отчета о различиях в изображениях #! /Ып/ЬазЬ тксИг -р зсгеепзЪоЕз/сНЕЕ ехрогЁ НТМЬ=зсгеепзЬоЕз /сошраге. есЬо > $НТМЬ Еог Ше 1п зсгеепзкогз/сЪготе/*.рпд до ЕК0М-$П1е Т0=$ (есЬо $Ше | зед 'з/сЬготе/ЕТгеЕох/') С1ЕГ=$(есИо $ЕНе | зед ' з/скгогпе/дЕЕЕ/') есЬо "ЕНОМ $ЕР.ОМ ТО $Т0"
15 -1 $ЕКОМ 15 -1 $Т0 МЕТК1С=$(сошраге -теЁГ1С АЕ -1ихг 15% $ЕкОМ $Т0 $01ЕЕ 2>&1) есЬо "<И>$ЕКОМ $МЕТК1С<Ьг/Х1тд 5гс=../$Б1ЕЕ/></И>" » $НТМЬ допе есЬо "</Их/Ьос1у>" » $НТМЬ Создаваемый этим сценарием отчет (рис. 8.19) сохраняется в файле зсгеепзйо^з/ сотраге.Ыт!. Рис. 8.19. Доклад о сравнении нескольких изображений Обсуждение Чтобы сэкономить место, мы показали только упрощенную реализацию этого ме- тода. Но также можно создать ранжированный отчет, отображающий визуальные различия между парами изображений в порядке от наибольших до наименьших. Такой отчет покажет наиболее важные визуальные отличия между разными плат- формами.
Можно также использовать автоматизированные визуальные тесты, чтобы предот- вратить регрессии. Здесь нужно не допускать ошибочных выявлений различий, вызываемых незначительными вариациями, такими как устранение контурных неровностей. С помощью непрерывной операции интегрирования можно задавать пороговое значение визуального различия между изображениями и генерировать сбой в случае, если величина различия между компонентами превышает величину этого порогового значения. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу ЬНря ://огеП.1у/Р 1 Тц]. 8.7. Добавление консоли в браузер мобильного устройства ЗАДАЧА Приведенный далее рецепт немного отличается от других рецептов в этой главе тем, что в нем рассматривается не автоматизированное тестирование, а ручное, в частности тестирование кода на мобильных устройствах. При тестировании приложения на мобильном устройстве можно столкнуться с ошибкой, которая не проявляется в среде настольных компьютеров. Обычно, при возникновении ошибки при исполнении приложения на персональном компьютере в консоли 1ауа$спр1 браузера можно отображать отладочные сообщения. Но брау- зеры мобильных устройств, как правило, не оснащены консолью .1ауа8спр(. Хотя в случае браузера МоЫ1е СЬготе отладку на нем можно выполнять удаленно с по- мощью настольной версии СЬготе. Но что делать, если проблема возникнет в дру- гом браузере? Или если просто не хочется тратить время и силы на организацию сессии удаленной отладки? Нет ли какого-либо способа доступа к консоли 5а\а8спр1, а также к другим инст- рументам разработки из браузера мобильного устройства? РЕШЕНИЕ Данную задачу можно решить при помощи программы Егис1а (Ьйр8://огеП.1у/ ]СЕ8п). Это облегченная версия реализации панели инструментов разработки, которая по- зволяет просматривать консоль }ауа8спр1, структуру страницы, а также целую кучу других модулей и расширений (Ь<1р$://огеП.1у/2ЕрНи). Чтобы разрешить работу с Егиба, нужно установить небольшой объем достаточно простого кода Зауа8спр( в разделе Неаб приложения. Загрузить Егиба на устройство можно из сети распространения контента. Однако размер этого инструмента довольно велик, поэтому его следует подключать только в том случае, если пользо- ватель мобильного браузера указал, что хочет использовать его.
Один из способов, как это сделать, подключать Егис!а только тогда, когда 1ЖЕ- адрес содержит строку егийа=Ёгие. В листинге 8.39 приведен код сценария, который можно вставить в контейнер страницы15. Листинг 8.39. Код сценария для подключения Егийа <5СГ1рЁ> (ГипсЫоп () { уаг зге = 1 //сйп.ззйеНуг.пеЁ/прт/егиба'; 1Г (! /егийа=Ёгие/. РезР (итпйом. 1осаЁ1оп) && 1оса15Ёогаде.деР1Ёет('асРтуе-егийа') != 'Ёгие') геЁигп; йоситепЁ.ыгтЁе('<зсг' + 11рЁ згс="' + зге + "'х/зег' + '1рЁ>'); йоситепЁ.мгтЁе('<зсг' + '1рЁ>'); йоситепЁ. мгЁЁе (' иёпйом . аййЕуепЁЫзЁепег (' + '"1оай", ' + 'ЁипсЁЁоп () {' + ' уаг сопЁа1пег=йоситепЁ.сгеаЁеЕ1етепЁ("йёу"); ' + ' ЙоситепЁ.Ьойу.аррепйСИИй(сопЁа1пег);' + 1 егийа.ёпёё ({' + ’ сопЁатпег: сопЁагпег,' + ' Ёоо1: ["сопзоТе", "еЙетепЁз"]1 + ' });' + '})’); ЙоситепЁ.ыг1Ёе('</зсг' + 'хрЁ>’); }) О; </зСГ1рЁ> Если теперь открыть приложение в браузере по адресу Ы1р://1раддге88/?егийа= 1гие или Ьйр://1раддге$$/ #егида=Сгие, то в его интерфейсе будет дополнительная кнопка, как показано на рис. 8.20. Если вы используете пример приложения для этой главы, попробуйте ввести не- сколько ответов16, а затем нажмите кнопку Егш1а. В результате откроется консоль, как показано на рис. 8.21. Поскольку конечная точка вызовов приложения отсутствует, то в консоли должны быть записаны некоторые ошибки и другие журналы. Консоль даже поддерживает очень малораспространенную функцию сопзо1е. иаЫе, которая позволяет отобра- жать массивы объектов в табличном формате. Вкладка Е1ешеп<$ панели Егида предоставляет базовое представление модели □ОМ (рис. 8.22). 15 Для приложений, созданных при помощи средства сгеаЁе-геасЁ-арр, этот код нужно вставить в файл риЬНс/1пдех.Ь1т1. 16 Код этого приложения можно загрузить с репозитория исходного кода для этой книги по адресу ййр5://огеП.1у/Р1То].
0 168 1 14:3000Регийа=1гие : 7x2? Сиезз ЗиЬгпЙ Уои Пауе \Л/оп! Рис. 8.20. Дополнительная кнопка на странице (справа внизу) приложения 0 .1б8.1.14:3000/’еги(1а=1г11е /V О 0 ,168.1.14;3000/?егис)а=1гие (Т) • Рекезк Сиезз: 7x2? 7x2? Сиезз' Сопво1е Е1етети 8еК1пдз 0 А11 Еггог №гтпд 1п/о < Сотд То ваче СЫз с1а*а (|п<1ех) апзшег диезв газик 0 9 9 ТЛЛкГ О ТЬеге «аз а ргоЫет Са1ктд Со СЬе зегчег РеайаЫеЗСгеат 1оске<± 1а1зе, сапсе1: деСКеадег. /, р!реГЬгоидЬ: /, р реТо: (...) Со1пд (о ааче СЫб йаСа (Ыех) апБчгег диезв гевик О 18 17 ХОЗЕ" О ТЬеге мае а ргоЫет Са1кГпд Со СЬе вегчег ЯеайаЫеЗСгеат 1оске<): Ыве сапсеР / десЯеайег /. р1реТЬгоидК: р<реТо: 6о>пд Со заче сЫз йаСа (|пйех) апзччег диеза газик 0 14 14 'МЫ О ТЬеге жав а ргоЫет Са1ктд Со сЬе зегчег РеайаЫеЗСгеат 1оске<± Ызе, сапсек /, деСРеайег. | р(реТЬгоидЬ.}, р<реТо: (...) Рис. 8.22. Вкладка Е1етеп1з панели Егиба Рис. 8.21. Консоль инструментов Егийа
А вкладка 8еШп^8 (рис. 8.23) предоставляет доступ к обширному набору возмож- ностей Дауа8спр1, которые можно включать и отключать в процессе взаимодейст- вия с веб-страницей. ® „168.1.14:3000/?егис1а=1гие Оиеез: Обсуждение Егида— это чудесный инструмент, предоставляющий целый ряд возможностей, требующий очень небольшого объема работы со стороны разработчика. Кроме ба- зовых возможностей он также поддерживает подключаемые модули, позволяющие отслеживать производительность, задавать скорость обновления экрана, генериро- вать ложные данные геолокации и даже исполнять 1ауа8спр1 из браузера. Начав работать с ним, вы, скорее всего, обнаружите, что он быстро станет стандартной составляющей вашего процесса ручного тестирования. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ьйр8://огей.1у/Р1Тц].
8.8. Удаление произвольности из тестов ЗАДАЧА В идеальном мире тесты всегда исполнялись бы в полностью искусственной среде. Тесты являются примерами работы нашего приложения в явно определенных усло- виях. Но тестам часто приходится иметь дело с неопределенными факторами. Например, они могут исполняться в разное время дня. В примере приложения, которое мы рассматриваем в этой главе, тоже есть случайные факторы. Это приложение представляет собой математическую игру, в которой пользователь должен умножить два произвольно генерируемых числа (рис. 8.24). 7x4? Рис. 8.24. В игре нужно вычислить произведение двух произвольных чисел Произвольность также может присутствовать в генерировании идентификаторов в коде или наборов случайных данных. Также при создании нового имени пользо- вателя приложение может предложить в качестве этого имени произвольно сгене- рированную строку. Но случайность создает проблемы для тестов. В листинге 8.40 приведен код теста, который мы реализовали ранее в этой главе. Листинг 8.40. Код одного из предыдущих тестов главы безсгхЬе('Вазгс дате ^ипсбгопз', 0 => { 16 (' зкои1с1 по61Гу бИе зегуег 1Г I 1озе', () => { // Сообщает серверу о проигрыше // При условии, что приложение запущено // Когда вводится неправильный ответ // Серверу сообщается о проигрыше }) Неспроста этот тест был разработан для случая ввода пользователем неверного от- вета. Для решения задачи всегда нужно вычислить произведение двух чисел от 1 до 10. Поэтому придумать и ввести неправильный ответ совсем не сложно: 101. Этот
ответ всегда будет неправильным. Но если мы хотим разработать тест, чтобы пока- зать, что происходит, когда пользователь вводит правильный ответ, то сталкиваем- ся с проблемой. Правильный ответ зависит от произвольно генерируемых данных. Мы могли бы считывать выводимые на экран произвольные числа, написав для этого какой-либо код, как, например, в листинге 8.41. ! Листинг 8.41. Код для считывания произвольных чисел с экрана сопзё [питЬег1, пишЬег2, бприЁ, зиЬшИ:] = аиабк Ргопйзе.а11 ([ бгбуег. ИпбЕХегпепЁ(Ву.сзз('.питЬег1')).деЁТехР (), бггуег. Ё1пбЕ1етепП (Ву. сзз (' . питЬег2') ) . деЁТехЁ (), бгхуег. Е1пбЕ1етеп1: (Ву.сзз ('шрик')), бггуег. Е1пбЕ1етепЁ (Ву.храЁк("//ЬиЁЁоп [1сех1г () =' ЗиЬпиЛ']")), ]) амаИ ЁприЁ.зепбКеуз('' + гштЬег! * питЬег2) акахЁ зиЬпйХ. сИск () Но иногда подобный подход невозможен. Например, фреймворк Сургезз не обеспе- чивает прямой захват текстового содержимого со страницы. Поэтому разработка теста Сургезз на ввод правильного ответа на поставленную задачу умножения будет сопряжена с большими трудностями. Это объясняется тем, что Сургезз не позволяет выполнять захват данных со страницы и передавать их другим состав- ляющим теста. Намного лучше, если на время теста можно было бы отключить произвольность. Но возможно ли это? РЕШЕНИЕ Данную задачу можно решить, используя библиотеку 8 шоп, чтобы временно заме- нить метод Ма1±.гапс1от созданной нами имитацией. Сначала рассмотрим, как это можно сделать в модульном тесте. Создадим новый тест для высокоуровневого компонента Арр, который будет проверять, что ввод правильного значения вызывает отображение сообщения, сообщающего о выиг- рыше. В тесте мы создадим функцию, которая будет заменять значение, возвращаемое методом мани. гапбот каким-либо фиксированным значением (листинг 8.42). ' Листинг 8.42. Функция для замены случайного значения постоянным сопзё. 51поп = геди1.ге('згпоп') ЁипсЁгоп такеВапботА1мауз(гези1Ё) { ёё (МэёЬ.гапбот.гезЁоге) { МаЁк.гапбот.гезЁоге() } З1поп.зЁиЬ(МаЁк, 'гапбот').геЁигпз(гезиЁЁ) }
Эта функция заменяет метод гапйош объекта мабЬ методом-заглушкой, который все- гда возвращает одно и то же значение. Теперь эту функцию можно использовать в нашем тесте. Компонент Оиезббоп, отображаемый на странице, всегда генерирует произвольные числа в диапазоне от 1 до 10 на основании значения: Мабк.гапдошО * 10 + 1 Если откорректировать метод маьь.гапбош таким образом, чтобы он всегда выдавал значение 0,5, то "произвольное" число всегда будет равно 6. В результате мы смо- жем создать модульный тест, как показано в листинге 8.43. Листинг 8.43. Модульный тест с модифицированным методом Маън.гапсЬп 16 (' З1юи1с1 бе11 уои бкаб уои епбегед бПе гбдкб апзиег', азупс () => { // Тест должен сообщить, что введено правильное значение // При условии, что приложение было отрисовано такеКапдотА1иауз(0.5) гепс!ег(<Арр />) // При вводе правильного ответа сопзё 1приб = зсгееп.дебВуЪаЬеХТехб(/диезз:/1) сопзб зиЬтббВиббоп = зсгееп.дебВуТехб('ЗиЬпй.6') изег.буре(гприб, '36') изег. сИск (зиЬтббВиббоп) // Выводится сообщение, что мы выиграли аыабб иа1бГог(() => зсгееп.ПпбВуТехб(/иоп/1), { ббтеоиб: 4000 }) }) Этот тест окажется заведомо успешным, поскольку приложение всегда будет зада- вать вопрос: "Чему равно 6 х 6?" Настоящая ценность откорректированного метода мабИ.гапйот проявляется при ис- пользовании фреймворка для тестирования, который явно предотвращает захват с экрана произвольных значений, как мы видели ранее в случае с фреймворком Сургезз. Но фреймворк Сургезз позволяет добавлять пользовательские команды, вставляя их в сценарий сургезз/зиррогГ/сотшапдз^з. Отредактируем этот файл, вставив в него код из листинга 8.44. Листинг 8.44. Код для вставки в файл сургезв/виррогйсогптапсЦз Сургезз.Сои1тапдз.ас1с1('гапдот', (гези1б) => { су.ге1оас1() .бб1еп( (ш.п) => { 16 (ибп.Мабк.гапдот.гезбоге) { М1П.МабЬ.гапйот.гезбоге() } зЁпоп.збиЬ(мбп.Мабк, ’гапскж1).гебигпз(гези1б) }) })
В результате будет создана новая команда Сургезз су.гапйот. Посредством этой команды можно создать тест для выигрышного случая игры, который мы обсужда- ли во введении этого раздела17. Такой тест приведен в листинге 8.45. Листинг 8.45. Тест для выигрышного случая игры безсггЬе('Вазгс дате бипсбгопз', 0 => { 1б('з1юи1с1 побИу бке зегуег 16 I ш.п', () => { // Сообщает серверу о выигрыше // При условии, что приложение запущено су.хпбегсерб('Р05Т', '/ар1/гези1б1, { збабизСобе: 200, ЬосЗу: '', }).аз('розбКези1б') су.убзбб(' Ъббр: //ЗосаПюзб^ООО') су.гапсклп(0.5) су.сопбабпз ('Кебгезк') .сИск() // При вводе правильного ответа су.дек('бприб').буре(’36') су.сопбагпз ('ЗиЬтбб 1) . сИск() // Сервер сообщает о выигрыше су.иа1б('@розбКези1б')-бкеп((хкг) => { аззегб.с!еерЕдиа1 (хкг.гедиезб.ЬосЗу, { диезз: 36, апзмег: 36, гези!б: 'МПТ, }) }) }) }) После вызова команды су.гапсЬт нужно обновить страницу на случай, если прило- жение создало произвольные числа до замены метода Мабк.гапбот. Обсуждение Полностью удалить из теста все элементы произвольности невозможно. Например, время и частота перерисовки компонентов может в значительной степени зависеть от технических характеристик компьютера. Но устранение как можно большего объема неопределенности в тесте обычно является желаемым результатом. Чем больше внешних зависимостей можно удалить из теста, тем лучше. Вопрос удаления внешних зависимостей также рассматривается в разделе 8.9. 17 Дополнительная информация об этом тесте приводится в разделе 8.3.
Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ьир*»://огеП.1у/Р 1 Тц]. 8.9. Путешествие во времени ЗАДАЧА Время может быть источником огромного количества ошибок. Если бы время было просто предметом научного исследования, тогда работа с ним была бы сравнитель- но несложной. Но оно таковым не является. Представление времени зависит от государственных границ и местного законодательства. В некоторых странах нет своих собственных часовых поясов, тогда как в других есть несколько таких поясов. Один обнадеживающий фактор в этой мешанине— часовые пояса всех стран смещены от скоординированного всемирного времени ПТС на целое число часов. Исключение составляет Индия, часовой пояс которой смещен на +05:30. По этой причине будет целесообразным использовать в тестах одно постоянное время. Но как это можно сделать? РЕШЕНИЕ Рассмотрим, как можно зафиксировать время при тестировании приложения КеасТ При тестировании зависимого от времени кода необходимо учитывать некоторые моменты. Первым делом, скорее всего, не следует изменять время на сервере. В большинстве случаев лучше всего установить на сервере время ЕТС и больше не менять его. Это означает, что если в браузере нужно эмулировать дату и время18, то как только браузер установит контакт с сервером, у нас возникнут проблемы. В результате нам придется или модифицировать АР1-интерфейс сервера, позволяя ему принимать фактическую дату, или же тестировать зависящий от времени код изолированно от сервера. Для описанного далее рецепта мы примем второй подход: будем применять систе- му тестирования Сургезз, чтобы эмулировать все подключения к серверу. Для этого рецепта мы будем пользоваться тем же самым приложением, что и для других примеров в этой главе. Это простая математическая игра, в которой нужно вычислить произведение двух произвольных чисел. Мы будем тестировать воз- можность игры, которая дает пользователю 30 секунд для ввода ответа. Если в те- чение 30 секунд пользователь не предоставит никакого ответа, выводится сообще- ние об истечении времени для ответа (рис. 8.25). Мы могли бы попробовать создать тест, который каким-либо образом становится на паузу в течение 30 секунд, но с подобным подходом есть две проблемы. Пер- 18 То есть позволить браузеру сказать серверу: "Давай будем считать, что сегодня вторник, 14 апреля".
вая, — это замедлит тест. Даже несколько 30-секундных пауз сделают процесс тестирования невыносимым. Вторая причина заключается в том, что добавление паузы — это не очень точный способ тестирования возможности. Пауза перед про- смотром сообщения с запланированной длительностью 30 секунд может растянуть- ся до 30,5 секунды. биевз Уои аге оЩ оТ Нте! Н4» йм йтм Й8<е/Ъте: 2021-03-14103:00:00000 Рис. 8.25. Сообщение об истечении времени для предоставления ответа Чтобы обеспечить точность, нам нужно управлять временем в браузере. Как мы видели в предыдущем разделе, Сургезз может внедрять код в браузер, заменяя кри- тические фрагменты кода методами-заглушками, которыми мы можем управлять. В фреймворке Сургезз есть встроенная команда су.с1оск, которая позволяет зада- вать текущее время. Рассмотрим, как задействовать эту команду, создав тест для проверки возможности тайм-аута нашего приложения. Структура теста приведена в листинге 8.46. : Листинг 8.46. Структура теста для проверки возможности тайм-аута с1езсг1Ье(’Вазгс дате ТипсМопз', 0 => { Ц (' зЬои!с1 зау И I бйпес! оиб', () => { // Тест должен сообщить о тайм-ауте // При условии, что начата новая игра //По истечении 29 секунд // Сообщение о тайм-ауте не выводится //По истечении еще одной секунды // Выводится сообщение о тайм-ауте //И игра завершается }) }) Процесс тестирования можно начать с запуска приложения и нажатия кнопки об- новления страницы (листинг 8.47).
Листинг 8.47. Код для запуска процесса тестирования дезсггЬе('Вазгс дате ^ипсНопз’, () => { 1Т ('зкоиХс! зау I Ытей оиН, () => { // Тест должен сообщить о тайм-ауте // При условии, что начата новая игра су.У131б(’Мбр: //1оса1Иозб:3000') су.сопбатпз('Ке^гезЪ') .сИск() //По истечении 29 секунд // Сообщение о тайм-ауте не выводится //По истечении еще одной секунды // Выводится сообщение о тайм-ауте //И игра завершается }) }) Теперь нам нужно эмулировать отсчет 29 секунд времени. Это можно реализовать при помощи команд су.с1оск и су.Иск. Команда су.с1оск позволяет задать требуе- мую новую дату и время; при вызове этой команды без параметров она устанавли- вает дату 1 января 1970 г. А команда су.Иск позволяет добавлять к текущей дате и времени заданное число миллисекунд. Соответствующий код приведен в лис- тинге 8.48. Листинг 8.48. Эмулирование интервала в 29 секунд с!езсг1Ье ('Ваз1с дате ЬтсНопз', О => { 1б('зЬои1с1 зау И I Нтед ои1', О => { // Тест должен сообщить о тайм-ауте // При условии, что начата новая игра су.с1оск() су. Ч7131Х (' Ъббр: //1оса1Ьозб:3000’) су. сопбатпз (’ КеГгезИ') . сИск () //По истечении 29 секунд су. Нек (29000) // Сообщение о тайм-ауте не выводится //По истечении еще одной секунды // Выводится сообщение о тайм-ауте //И игра завершается }) }) Теперь можно создать другие составляющие теста, полный код которого приведен в листинге 8.49. Подробная информация по другим используемым в тесте командам Сургезз доступна в документации по этому фреймворку по адресу Ьйрз:// огеНЛу/уаЬМА.
Листинг 8.49. Полный тест проверки тайм-аута с!езсг1Ье ('Вазбс дате бипсбгопз', О => { 1б('зПои1с1 зау 1Г 1 ббтес! оиб', О => { // Тест должен сообщить о тайм-ауте // При условии, что начата новая игра су.с!оск() су.У151б ('Ьббр: //1оса1Ьозб:3000') су. сопбагпз (1 ВеГгезк') . сИск () //По истечении 29 секунд су.Г.1ск(29000) // Сообщение о тайм-ауте не выводится су.сопбад-пз (/оиб об бйпе/х) .зкоиХсЦ 'поб.ехбзб') //По истечении еще одной секунды су.Ё1ск(1000) // Выводится сообщение о тайм-ауте су.сопбабпз(/оиб об ббте/б) .зЬоибсЦ 'Ье.убзбЫе') //И игра завершается су.деб (' бприб') . зЬои1<3 ('Ье.сИзаЫес!') су.сопбагпз (’ВиЬпйЛ') .зЬоибсЦ 'Ье.сНзаЫес!') }) }) При исполнении этого теста в Сургезз он успешно выполняется, как можно видеть на рис. 8.26. 5x9? Уои аге ои! оТ йте! НН|*М Ожгепеба№Д1те: 2С21-03-14ТОЗ’.О0:ОО.Ов0 Рис. 8.26. Управляя временем, можно принудительно создать тайм-аут в тесте Это был сравнительно простой тест с управлением временем. А как быть, если мы хотим протестировать что-либо намного более сложное, например летнее время? Ошибки, связанные с этим временем, являются бичом большинства разработчиков. Они "молча" сидят в коде в течение долгих месяцев, а потом внезапно врываются в жизнь ранним весенним или осенним утром.
Возникновение ошибок, связанных с летним временем, зависит от конкретного ча- сового пояса. С ними особенно трудно бороться в клиентском коде, поскольку даты в Лауа8спр1 не поддерживают часовых поясов. Они, конечно же, могут справиться со смещениями. Например, в браузере СЬготе можно создать объект ване со значе- нием на пять часов раньше времени по Гринвичу19: пем БаСеС 2021-03-14 01:59:30 СМТ-0500') Но даты 1ауа8спр! неявно находятся в часовом поясе используемого браузера. При создании даты, содержащей название часового пояса, движок 5ауа8спр1 просто сместит ее значение в часовой пояс браузера. А часовой пояс браузера фиксируется при запуске браузера, и не существует ника- кого способа сказать: "Давай будем считать, что с этого момента мы будем в Нью- Йорке". Может случиться так, что тесты для летнего времени будут работать только в ча- сом поясе разработчика, а при исполнении на сервере интеграции, для которого установлено скоординированное всемирное время, будут завершаться сбоем. Но существует способ обойти эту проблему. На компьютерах Ыпих и Мас (но не ХУшсклуз) при запуске браузера можно указать требуемый часовой пояс, присвоив соответствующее значение переменной среды тг. Если запустить фреймворк Сургезз с установленным значением этой переменной, то любой запускаемый в Сургезз браузер унаследует это значение. В итоге, несмотря на то, что мы не можем задать часовой пояс для отдельного теста, можно это сделать для всего тес- тового прогона. Запустим Сургезз с часовым поясом, заданным для Нью-Йорка: $ Т2='Атег1са/Ыеи_Уогк' прх сургезз ореп В окне нашего приложения есть кнопка, которая позволят отображать или скрывать текущее время (рис. 8.27). Давайте создадим тест, проверяющий правильность перехода времени на странице на летнее время. Код теста приведен в листинге 8.50. 10x5? Сиезз ИИИМИИМНИНМИДМВИММИДМВИШВИМНИЮ1 I НМе*п«]Сиггеп( бай/Яте 2О21-ОЗ-14ТОТ59:30.0О0 Рис. 8.27. Отображение текущего времени в окне приложения 19 Браузер Е1геГох обычно не принимает этот формат.
Листинг 8.50. Код теста для проверки перехода на летнее время дезсгхЬе ('Тцтйпд', 0 => { аЛ (' зЬои1с! 1е11 из ТИе сиггепб Типе', () => { // Тест должен сообщить текущее время су.с1оск(пем бабе ('2021-03-14 01:59:30').дебТхше()) су.У181б (,ЬбТр://1оса1Иозб:3000,) су.сопбахпз('ЗИом Ыте') . с11ск () су.сопбатпз ('2021-03-14Т01:59:30.ООО') .зЬои1с!( 'Ье.убзбЫе') су.бхск(30000) су.сопОагпз ('2021-03-14Т03:00:00.ООО') . зЬои1д('Ье.ухзхЫе') }) }) В тесте мы передаем команде су.с1оск явную дату. Значение этой даты нужно пре- образовать в миллисекунды, вызывая метод деь.Типе, поскольку су.с1оск может принимать только числовые значения. Затем мы проверяем начальное время, а по истечении 30 секунд проверяем переход времени на 03:00 вместо 02:00, как показа- но на рис. 8.28. 10x5? Сиеэз Уои агеои! оН!те! | ни« й™ Юцтеп! йа1еД1те: 2021-03-14103:00'00 000 Рис. 8.28. По истечении 30 секунд время правильно меняется с 01 59 на 03 00 Обсуждение Тесты, зависящие от текущего часового пояса, целесообразно поместить в подпап- ку, чтобы их можно было исполнять отдельно. Для форматирования дат в разные часовые пояса можно использовать метод ьоЪосаХеЗЫхпд: пей бабе О .ЬоЬоса1е31:г1пд ('еп-68', { Ыте2опе: 'Азха/Токуо' }) Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу ЬИр8://огеН.1у/Р1 Тд].
Доступность специальных возможностей Написание этой главы было сопряжено с определенными трудностями, поскольку кроме очков или контактных линз никто из нас для работы с компьютером не нуж- дается в каком-либо специальном оборудовании или программном обеспечении. В этой главе мы попытались собрать коллекцию инструментов и методов, которые в идеале помогут вам найти некоторые из наиболее очевидных проблем с реализа- цией специальных возможностей в вашем коде. Мы рассмотрим, как можно использовать ориентиры и роли АК1А, добавляющие значение и структуру в страницы приложения, которые в противном случае имели бы лишь визуальную группировку. Затем в нескольких рецептах мы продемонстри- руем, как исполнять ручные и автоматизированные аудиты приложения, искать глюки в коде посредством статического анализа, а также находить ошибки времени исполнения, автоматизируя браузеры. Далее мы рассмотрим некоторые технические моменты, связанные с созданием пользовательских диалоговых окон (подсказка: старайтесь применять готовые диа- логовые окна, предоставляемые библиотеками) и, наконец, создадим простую про- грамму для чтения текста с экрана. Для более подробного рассмотрения темы доступности специальных возможностей обязательно посетите веб-сайт XV С АО (\УеЬ Соп1еп1 Ассе881ЫН1у 6ий1е1те8 — нор- мы доступности к веб-содержимому, ЬНр8://огеП.1у/1е0аТ), на котором определя- ются три уровня соответствия этим нормам: А, АА и ААА. Самый высший уровень соответствия — ААА. Для разработчиков профессионального программного обеспечения эти рецепты будут, скорее всего, полезными. Но ничто не сможет заменить опыт тех, кому каж- дый день приходится преодолевать препятствия, создаваемые недоступным про- граммным обеспечением. Доступное программное обеспечение — это просто хорошее программное обеспечение. Оно максимизирует вашу рыночную долю и заставляет вас уделять больше внимания дизайну. Мы бы порекомендовали как минимум выполнять аудит доступности своего кода. Необходимые для этого ин- струменты можно получить, связавшись с организацией наподобие АЬПйуНеТ в Великобритании (йИр8://огеП.1у/М7ХкН) или просто поискав программное обес- печение для тестирования доступности по своему местонахождению. Вы увидите, что это наиболее эффективный способ для обнаружения проблем доступности в разрабатываемом коде.
9.1. Использование ориентиров ЗАДАЧА Рассмотрим простое приложение для создания и управления задачами, показанное на рис. 9.1. Рис. 9.1. Окно примера приложения для создания и управления задачами Пользователь, который увидит окно этого приложения, с легкостью различит основное содержимое (задачи) и все прочие элементы по краям: ссылки на другие страницы, заголовки, заявление об авторском праве и т. п. Давайте рассмотрим код основного компонента Арр этого приложения (листинг 9.1). Листинг 9.1. Код компонента Арр примера приложения сопз<: Арр =()=>( геСигп ( <И1>Мападе Тазкз</Ы> < а ИгеГ=1/сопСас^з'>СопСасРз</а>&пЬзр;|&пЬзр; < а Ьге1='/еVеп115 ' >ЕуепСз</а>&пЬзр; | &пЬзр; Тазкз&пЬзр;|&пЬзр; < а кгеГ='/поЁез ' >Ыо1:ез</а>&пЬзр; |&пЬзр; < а ИгеГ='/Ё1те'>Т1теКес</а>&пЬзр;|&пЬзр; < а ЬгеГ='/сИагу'>Р1агу</а>&пЬзр;|&пЬзр;
<а ИгеГ='/ехрепзез'>Ехрепзез</а>&пЬзр;|&пЬзр; <а Ъге^-'/1ПУО1сез'>1пуо1сез</а> <ЬиТТоп с1аз$Ыате=’аск}ВиССоп' опС11ск={() => зеЕГогтОреп(Егие)}>+</Ьиккоп> <ТазкСопкехЕз .../> &#169;2029, Ата1датакес1 СопзиТТапкз Согр. АН КгдИкз Незегуес!. сТазкГогт .../> <Мода10иезкюп ...> Аге уои зиге уои мапТ То йе1еТе Миз казк? </Мода1<2иезЁ1ОП> </> ) } Проблема с этим кодом состоит в том, что при чтении страницы посредством како- го-либо устройства определить ее структуру трудно. Какие из ее составляющих являются навигационными ссылками? Где основное содержимое страницы? Такой парсинг страницы, который с легкостью осуществляет человеческий глаз (рис. 9.2), невозможно осуществить без доступа к пространственному группированию интер- фейса. Так как же решить эту проблему? Что можно использовать вместо визуального группирования, чтобы сделать структуру страницы более понятной? Оота1п пате | Ета11 | Ьи ®та! Саг Виа!певе Тоск «г «Н» ср-агагкюд фа» СосНпд С1 2ОГ7СГ ВцШОжпг Ро(1а1 | СгеаСа цмг реп Апйгок! | Сгн1« дгккоИ арр ЕшаН СоигввБ В Ноте ЫисМо Ви<4 я1кк11С ОЖсе Нед1в(ег сотрапу Сштф1еса бмай» сп&па УоиТиЬе ассоип) I РедКШ пм*г ассодес Вапк ассоипг I Орел ЬдеДлеи Ь«лк ассошМ Э«к Рец-иег *Ьаск асссчл* РаушепТб В Сг**и рартчм* есеоипт РЪопе Са!1 ассоимап* ДА аЬ<мХ к1<а«паШ)па1 кы пМм 5а1еа 1ак гед|$*га1)оп | А1Х ассРипил! ЯваеагсК СЬоом сотрапу В пате СЬеск Рог патст 5Ьорр)пд Мсх1в1 агис1ев | РигсЬаве алД ггмхкТу 1ГКМШ Виу сатега йц^а! Агги1ап ВиуМдпи I АпдкдМ Атамл Не1ув№а | ле* гуре1»са 5И* а Вцу Ъишлаи тЪ вНа СНаПз В Виу Кдач* Рис. 9.2. Обычные пользователи могут с легкостью определить пространственное местонахождение разделов страницы
РЕШЕНИЕ Для решения этой задачи мы будем использовать ориентиры в нашем коде. Ориен- тиры — это элементы НТМЬ, посредством которых можно структурно сгруппиро- вать части интерфейса, чтобы отобразить их визуальную группировку. Ориентиры также полезны при разработке страницы, поскольку они заставляют разработчика думать о назначении разных типов содержимого. Начнем с выделения заголовка. Эта часть страницы определяет ее содержимое. Обычно используется заголовок ы, но можно также включить широко приме- няемые инструменты или, возможно, логотип. Идентифицировать заголовок можно с ПОМОЩЬЮ тега <Ьеадег>: <Ьеабег> <И1>Мападе Тазкз</Ь1> </Иеас1ег> Страница всегда должна содержать заголовок ы, а заголовки более низких уров- ней должны обеспечивать структурирование содержимого остальной части стра- ницы, не пропуская при этом никаких уровней. Например, никогда нельзя указы- вать заголовки И1 и ьз без заголовка Ь2 где-либо между этими двумя заголовками. Ориентиры являются удобным навигационным методом для людей, пользующихся средствами чтения с экрана, включая функции, которые позволяют пользователям переходить вперед и назад между заголовками. Далее нам нужно подумать о средствах навигации по странице. Они могут быть разной формы, например список ссылок (как в данном случае), или последователь- ность меню или боковая колонка. Средства навигации — это блок компонентов, которые позволяют посещать основные части веб-сайта. Почти наверняка страница будет содержать другие ссылки, не относящиеся к средствам навигации. Для идентификации средств навигации на нашей странице подойдет ориентир в виде тега <пау>, как показано в листинге 9.2. Листинг 9.2. Выделение средств навигации при помощи тега <пэт> <паV> < а ЬгеГ=' /сопНасНз ' >СопИас±з</а>&пЬ5р; | &пЬзр; < а ИгеЕ='/еуепПз'>ЕуепПз</а>&пЬзр; |&пЬзр; Тазкз&пЬзр;|&пЬзр; < а кгеГ='/покез'>МоГез</а>&пЬзр;|&пЬзр; < а ИгеГ='/каше'>Т1теКес</а>&пЬзр;|&пЬзр; < а ЪгеЕ='/сЦагу'>Б1агу</а>&пЬзр;|&пЬзр; < а ЪгеГ='/ехрепзез'>Ехрепзез</а>&пЬзр;|&пЬзр; < а Иге{= '/11ГУО1сез ' >1пуо1сез</а> </паV> Содержимое страницы является ее основной частью. В нашем приложении по соз- данию и управлению задачами содержимое состоит из коллекции задач. Основным
содержимым будет то, что пользователь хочет читать и с чем он хочет взаимодей- ствовать на странице в первую очередь. Иногда основное содержимое может также содержать инструменты, например плавающую кнопку для добавления задач. Но они не обязательно должны находиться в основном содержимом, и их можно пере- местить куда-либо в заголовок. Основное содержимое страницы можно сгруппировать при помощи тега <таХп>, как показано в листинге 9.3. Листинг 9.3. Группирование содержимого посредством тега <та1п> <шаХп> <ЬиХХоп сХаззНате='ас1с1Ви(:Хоп' опСИск={() => зеХЕогтОреп (Хгие) }>+</ЬиХ1:оп> <ТазкСопХехХз сопХехХз={сопХехХз} Хазкз=(Хазкз} опВеХеХе={зеХТазкТоКетоуе} опЕсИХ={Хазк => { зеХЕсНХТазк (Хазк) зеХЕогтОреп(Хгие) }) /> </та!п> Наконец, есть метаданные веб-страницы: данные о данных. Примером метаданных является сообщение о защите авторских прав в нашем приложении. Метаданные часто помещаются внизу страницы, сгруппированные посредством тега <Хоохег>: <ХооХег> &#Х69;2029, АтаХдатаХед СопзиХХапХз Согр. АН КХдЪХз Кезегуес!. </ЕооХег> Осталось рассмотреть еще два раздела исходного компонента Арр: тазкгогт и мобаХОиезхХоп. В листинге 9.4 приведен код этих разделов. Листинг 9.4. Код разделов ТавкЕога и Мода10иезХл.оп <ТазкЕогш .../> <МодаХ0иезХ1оп ...> Аге уои зиге уои мапХ Хо деХеХе 1Ыз Хазк? </МодаХфиезX1оп> Раздел тазкЕогт— это модальное окно, которое открывается, когда пользователь хочет создать или редактировать задачу (рис. 9.3). А раздел мобаХОиезхХоп представляет собой модальное окно подтверждающего со- общения, отображающегося, когда пользователь пытается удалить задачу (рис. 9.4).
Сапсв! 5а«е Рис. 9.3. Модальное окно ТазкЕогт отображается поверх всего другого содержимого окна приложения Аге уои виге уои жале го <1е1е1е №1$ 1азк? Гм № Рис. 9.4. Модальное окно МойаЮиееИоп открывается, чтобы подтвердить удаление задачи пользователем Эти два компонента отображаются только тогда, когда они требуются. При нор- мальном состоянии страницы эти модальные разделы не входят в ее структуру, по- этому ориентиры для них не требуются. Далее в этой главе мы рассмотрим другие подходы к работе с динамическим содержимым, таким как модальные окна, кото- рые делают их более доступными для пользователей приложения. Конечная модифицированная версия компонента Арр приведена в листинге 9.5. Листинг 9.5. Конечная модифицированная версия компонента Арр СОПЗЁ Арр = ()=>{ геЁигп ( <Ъеа<1ег> <Ъ1>Мападе Тазкз</Ь1> </Иеас1ег> <пау> <а ЪгеЁ=' /сопЁасЁз ’ >СопЁасЁз</а>&пЬзр; | &пЬзр; <а ИгеГ='/еуепЁз'>ЕуепЁз</а>&пЬзр; |&пЬзр; Тазкз&пЬзр;|&пЬзр; < а ЬгеЁ=' /поЁез ' >ЫоЁез</а>&пЬзр; | &пЬзр; < а ИгеЁ='/Ё1те’>Т1теКес</а>&пЬзр; |&пЬзр; < а кгеГ='/сИагу1>01агу</а>&пЬзр;|&пЬзр;
<а ЬгеГ='/ехрепзез'>Ехрепзе5</а>&пЬзр;|&пЬзр; <а 11геЕ= '/1пуо1сез ' >1г^01сез</а> </пау> <та1п> <ЬиЕЕоп с1аззЫате='абдВиЕЕоп' опС11ск={() => зеЕЕогтОреп(Егие)} >-к/ЬиЕЕоп> <ТазкСопЕехЕз .../> </та!п> <Еоокег> &#169;2029, Ата1датакес1 Сопзи1ЕапЕз Согр. АН КТдЬЕз Везе^ес!. </ЕсюЕеО <ТазкЕопп . . . /> <Мода10иезЕ1оп ...> .Аге уои зиге уои иапЕ Ео с!е1еЕ.е ЕЫз Еазк? </Мода10иезЕ1оп> </> ) } Обсуждение Ориентиры являются частью НТМЬ5, поэтому их поддержка встроена в браузеры. Это означает, что приступать к их использованию можно без установки каких бы то ни было специальных инструментов или библиотек поддержки. Некоторым автоматизированным средствам доступности могут "не нравиться" ори- ентиры, отрисованные приложениями КеасЕ Согласно стандартным нормам все содержимое тела веб-страницы должно быть заключено в тег ориентира. Но боль- шинство приложений Кеас! отрисовывают свое содержимое (включая ориентиры) внутри одного тега «Ну>, что сразу же нарушает все правила. Скорее всего, эту проблему можно игнорировать, не опасаясь каких-либо отрица- тельных последствий. Если все имеющиеся ориентиры одного уровня, то факт их заключения в дополнительный тег <сИу> не должен играть никакой роли. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬИрвсУ/огеИЛу/ОСГ^А. 9.2. Применение ролей ЗАДАЧА В приложениях часто встречаются компоненты, которые ведут себя как кнопки, хотя и не являются кнопками. Подобным образом в приложениях могут использо- ваться компоненты, которые выглядят как всплывающие диалоговые окна, не являясь в действительности таковыми. Также приложения могут содержать кол- лекции данных, структура которых похожа на списки, но без наличия тегов <о1> ИЛИ <и!>.
Создание компонентов, которые ведут себя подобно стандартным элементам поль- зовательского интерфейса, не представляет никаких проблем, если пользователи могут видеть визуальное оформление компонентов. Если какой-либо элемент вы- глядит похожим на кнопку, пользователь будет обращаться с ним, как с кнопкой, независимо от способа его реализации. Но с этим возникает проблема, если пользователи не могут увидеть визуального оформления компонента. Для этих пользователей нужно описывать назначение таких компонентов. РЕШЕНИЕ Данная задача решается посредством использования в приложении ролей. Роль описывает значение компонента, сообщая пользователю о его назначении. Роли входят в состав семантики веб-страницы и поэтому похожи на семантические ори- ентиры, рассматриваемые в разделе 9.1. В табл. 9.1 приведен список типичных ролей, которые можно применять к отрисо- ванным элементам НТМЬ. Таблица 9.1. Список типичных ролей и их краткое описание Название роли Назначение а1егТ Сообщает пользователю, что что-то произошло агЁ1с1е Большой блок текстового содержимого, наподобие новостной заметки Ьи^оп Объект, по которому можно щелкнуть, чтобы выполнить какое-либо действие сЪескЬох Переключатель со значением истина/ложь, устанавливаемым пользователем соготегЛ Наподобие отправляемого пользователем комментария или отзыва сотр1етепТагу Дополнительная информация, возможно, в боковой панели согЛепЧпСо Сообщения о защите авторских прав, имена авторов, даты издания Ща1од Какой-либо объект, всплывающий поверх другого содержимого. Часто модальный ^ееб Список статей. Широко используется в блогах Ндиге Иллюстрация 11зТ Список объектов НзМТет Элемент списка зеагсЪ Поле поиска теш Набор опций меню, обычно используемых для навигации тепшТет Элемент меню Роли применяются к элементам при помощи атрибута го1е. Рассмотрим компонент тазк из примера приложения для этой главы. Данный компонент отрисовывает каж-
дую задачу в виде небольшой панельки, содержащей кнопку удаления в виде кор- зины. В листинге 9.6 приведен код этого компонента. Листинг 9.6. Код компонента Тазк хтрогр Ве1еЁе1соп Егот ' . /с1е1еЁе-24рх. зуд' ЕтрогЕ './Тазк.сзз' сопзё Тазк = ({ Ёазк, опБеЁеЁе, опЕсЦё }) => ( геЕигп ( <сНу с1аззМате="Тазк"> «Ну с1аззКате="Тазк-сопЁепЁз" <сНу с1аззМате="Тазк-с1еЁа11з"> «Ну с1аззМате="Тазк-Ё1Ё1е">{Ёазк.Ё1Ё1е}</с11у> <с11у с1аззЫате="Тазк-дезсг1рЁ1оп"> (Ёазк .с1езсг1рЁ1оп} </сНу> </сИу> <сНу с1аззМате="Тазк-сопЁго!з"> <Ётд згс={Бе1еЁе1соп} и1(1Ёк= {24} Ё1е1дкЁ={24} Ё1Ё1е="0е1еЁе" опСИск={ (еуЁ) => { еУЁ.зЁорРгорадаЁЁоп() опБеЕеЁе() Н а1Ё="Ре!еЁе Ёсоп" /> </<Иу> </сНу> </сНу> Задачи группируются на странице под заголовками, описывающими контекст, в котором они выполняются. Например, несколько задач могут быть сгруппирова- ны под заголовком РЬопе (рис. 9.5). Таким образом, похоже, что задачам соответствует роль элемента списка ИзЕЕЕеш. Это объекты, которые находятся внутри упорядоченной коллекции. Поэтому пер- вому разделу «Ну> можно присвоить эту роль: геЁигп <сНу го!е='ЕЕзЕЕЕет' с!аззЫате=' Тазк' > «Ну с1аззМате=' Тазк-йеЁаИз ' > Но если остановиться на этом, то у нас возникнет проблема. Роли должны подчи- няться определенным правилам. В частности, роль ЫзЕхЕет можно присвоить толь-
ко компонентам, которые находятся внутри объекта с ролью Изё. Поэтому, чтобы компонентам Тазк можно было присвоить роль ИзЁхЕеш, их родительскому компо- ненту тазкЫзЁ необходимо присвоить роль Из!:. Соответствующий код приведен в листинге 9.7. Мападе Тазкз Стирает* | Еугпя [ Тик. | ьшна | ПтеЙас 1 Рйгу I Ёхошивз I кшоксз Саг ВисЬемоЖс* В Ё&Ок ( Ша фаса СсхЛлд С1мгумг О С ааглт Роти! 1 Сгаата шаг рола) Ап * Стам* АпакНй «до Ета11 Сот»« ( Ркемпгмсоипм Нот* 3№По ВкЯИ.ЬхПо ОН1са Нед1в»г сотрапу УсяЛЪЬа ассошп ЙЯфИя? (ЧИН ВМОКК Вапкассоип! Орал 4шкм«а Ьапк ассоий Шек Н«дЫаг4ас± ассокИ Раутета Сг««1* |»угтчл'> кстмя РКопо Са1 •ссоип1ао1 В Азк Фот *аж лЛм дым и* годМгайоп | АЛ ксйигёвгй ПевеагсЪ С1хюм сотрапу ГМИТМ СМск <сг мим* ботаИ пмпав 5Ьорр1пд М<хШ аггкЛм РигсКам апд лчи^у пммМН Оота)ппэтм Й Виу дотай! Ет»И | ЗйМ> ЬиПпма алий Виус«п*га I ЕЯдЙа1 ЯЛ Атах» Виу ПдМа В РйфЙдМ Агмхм НЫувйсо 1 Неал» о** *ура<вса 8Ке | Виу ЬиЫгхм »аЬ Фа СПаги В СПаги В 02О2Я Лли*датя^к1 Сся яЖапЪь Соп> А1 ЙдНв Рис. 9.5. Каждая группа содержит список задач ' Листинг 9.7. Присвоение роли 1хзЪ компоненту ТазкЫзЪ 1трог1: Тазк Егот ' ../Тазк' ттрогЕ ' ./ТазкЫзЕ. сзз ' ЕипсЕгоп ТазкЫзЕ (( Еазкз, опОе!еЕе, опЕсИЕ }) { геЕигп ( <61У го1е="ИзЁ" с1аззНате="ТазкЫзЕ"> {Разкз.тар((Е) => ( <Тазк кеу={Е. 1(1} Еазк={Е} оп0е1еЕе={() => опБе1еЁе(Е)} опЕд1Е={() => ОПЕС11Ё(Ё)} /> )) } </дд^> ) } ехрогЕ беЕаиЁЁ ТазкЫзЁ
Применение ролей Изе и ИзПЕет полностью допустимо. Но, наверное, если наш НТМЬ ведет себя подобно списку, на практике будет намного лучше изменить раз- метку и использовать настоящие теги <и1> и <и>. С точки зрения доступности это, скорее всего, не будет иметь никакого значения. Но всегда лучше избегать запол- нения кода НТМЬ бесконечными тегами «11у>. В общем, если роль можно заменить обычным тегом НТМЬ, то, видимо, лучше всего так и сделать. Поэтому давайте удалим из компонента тазкьгзг роль ПзЕ и добавим настоящий тег <и1>, как показано в листинге 9.8. Листинг 9.8. Замена роли Изе тегом <и1> ЕтрогЕ Тазк Ггот '../Тазк' хтрогЕ ' ./ТазкЫзЕ. сзз ' ЕипсПоп ТазкЬ1зЕ({ Еазкз, опЕеТеЕе, опЕсНЕ }) { геГигп ( <и! с1аззМате="ТазкЬ1зЕ"> {Еазкз.тар((Г) => ( <Тазк кеу={Е. 1с1} Еазк=(Г} опЕе1еЕе={() => опЕе1еЕе(Е)} опЕс11Г={() => опЕсНЕ (Е)} /> ) ) } </и!> ) } ехрогЕ деЕаи1Е ТазкЫзЕ А затем в компоненте тазк заменим роль ИзсПет тегом <и>, как показано в лис- тинге 9.9. Листинг 9.9. Замена роли ИзЪхЪет тегом <и> ЕтрогЕ './Тазк.сзз' сопзЕ Тазк - ({ Еазк, опЕе1еЕе, опЕсНЕ }) => { геЕигп ( <11 с1аззМате="Тазк"> «Ну с1аззМате="Тазк-сопЕепЕз" <сНу с1аззЫате="Тазк-с1еГа11з">. . .</с!1У> <с11у с1аззМате="Тазк-сопЕго1з">. . ,</сНу> </сНу>
) } ехрогр беГаиХГ Тазк Использование тегов <и> означает, что нам нужно изменить несколько стилей С88, чтобы удалить маркеры элементов списка, но такой код будет легче читать любому разработчику (которым можете оказаться вы), которому придется работать с ним в будущем. Далее рассмотрим раздел навигации нашего примера приложения. Он содержит ряд ссылок, которые можно рассматривать, как меню опций (листинг 9.10). I Листинг 9.10. Раздел навигации примера приложения <паV> < а ЬгеГ=1/сопРасГз'>СопРасРз</а>&пЬзр;|&пЬзр; < а ИгеГ=' /еVепРз ' >ЕуепРз</а>&пЬзр; |&пЬзр; Тазкз&пЬзр;I&пЬзр; < а ИгеГ='/поГез'>МоГез</а>&пЬзр;|&пЬзр; < а ИгеР='/Г1те'>Т1шеКес</а>&пЬзр;|&пЬзр; < а ЬгеГ='/сНагу'>01агу</а>&пЬзр;|&пЬзр; < а 1хгеГ=1/ехрепзез1>Ехрепзез</а>&пЬзр;|&пЬзр; < а ИгеГ=1/хпуохсез'>1пуо1сез</а> </пау> Следует ли использовать здесь роли шепи и шепихрет? Ответ на этот вопрос — почти однозначно нет. Меню и элементы меню подразумевают ожидаемое поведение. Пользователь, желающий вызвать меню, будет, наверное, ожидать, что оно раскроется при выбо- ре. А в раскрывшемся меню он, наверное, будет перемещаться по опциям посред- ством клавиш стрелок, а не клавиши <ТаЬ>’. Теперь рассмотрим кнопку + в правом нижнем углу приложения, которая позволяет пользователю создавать новое задание, открывая соответствующую всплывающую форму (рис. 9.6). Код для этой кнопки следующий: <ЬиРРоп с1аззНате='асМВиРРоп' опС1хск={() => зеРГогтОреп(Ргие)}>+</ЬиРРоп> Нужно ли присвоить данной кнопке какую-либо роль? Нет, не нужно, поскольку этот элемент уже и так является кнопкой. Но мы в состоянии предоставить некото- рую дополнительную информацию о том, что может ожидать пользователь, когда он нажмет эту кнопку. Как уже упоминалось чуть ранее, нажатие этой кнопки 1 В интересной статье Адриана Роселли (Ас1пап ИозеШ, ЬПр8://огеН.1у/18АМ1) рассматриваются вопросы, касающиеся меню и элементов меню.
вызывает открытие всплывающего окна. Такое действие можно указать явно в коде НТМЬ посредством атрибута агЁа-Казрорир! <ЬиЁЁоп аг1а-казрорир=' сНа1од' с1аззЫаше='адсШиЁЁоп' опС11ск={() => зеЁГогтОреп(Ёгие)}>+</ЬиЁЁоп> Рис. 9.6. нажатие кнопки + открывает всплывающую форму для создания новой задачи Значение атрибута агха-Ьазрорир должно совпадать с ролью компонента, который отобразится в результате нажатия кнопки. В данном случае мы будем отображать диалоговое окно. Атрибуту агЁа-Ъазрорир также можно присвоить значение Ёгие. Тем не менее средство чтения с экрана будет интерпретировать этот компонент как шепи, поскольку компоненты со связанными с ними всплывающими окнами обычно служат для открытия меню. Поскольку мы присвоили атрибуту агха-Ьазрорир значение с11а1од, нам нужно обес- печить, чтобы роль отображаемого компонента таэкгопп также была сНа1од. В лис- тинге 9.11 приведен текущий код компонента тазкгогт. Листинг 9.11. Текущий код компонента тазкЕопл сопзё ТазкГогтп = ({ Ёазк, сопЁехЁз, опСгеаЁе, опС1озе, ореп }) => { геЁигп <Мойа1 ореп={ореп} опСапсе!={с1озе}> <ЁОГШ> </Ёогт>
<Мода1ГооЁег> <Ьи!!оп опСИск={ . . .} >Сапсе!</Ьи!1:оп> <Ьи!!оп опСИск={. . .}>8аVе</Ьи^1^оп> </Мода!Гоо!ег> </Мода!> } Мы заключим компонент тазкгогт в компонент мода!, как показано в листинге 9.12. | Листинг 9.12. Заключение компонента ТазкЕопп в компонент Мода! хтрог! './Мода!.сзз1 !ипс!1оп Мода1({ ореп, опСапсе!, сЬЫдгеп }) { 1! (!ореп) { геЁигп пи!1 } геЁигп <д!у с!аззЫате='Мода!1 > <д!у с!аззЫате='Мода1-д!а1од' {сНИдгеп} </д!у> </д^> ) ехрогЁ де!аи1Ё Мода! Данный компонент мода! состоит из двух частей: ♦ внешняя оболочка мода!, которая служит для затенения другого содержимого страницы и является полупрозрачным слоем; ♦ внутренний раздел Мода1-д!а1од, который отображает содержимое в контейнере, имеющем вид окна. Класс мода! допускает многократное использование и пригоден для других объек- тов, например для окон предупреждения. Поэтому мы присвоим ему дополнитель- ное свойство !!!1е, которое будет применяться к компоненту мода!-д!а!од. Благода- ря свойству ипе назначение данного диалогового окна будет понятным для любо- го пользователя. Модифицированный таким образом компонент Мода! приведен в листинге 9.13. Листинг 9,13* Код модифицированного компонента Мода! ...................................................................... гтрогЁ './Мода!.сзз1 ГипсЁЁоп Мода!({ ореп, опСапсе!, сИИдгеп, го!е, Ё1Ё1е }) {
!Г (!ореп) { ге!игп пи11 } геТигп <сИу го!е=1ргезеп!а!1оп' с1аззМаше= 'Мода!' <д!у с!аззМаше=1Мода!-д!а1од' го!е={го1е} Ё1Ё1е={Ё1Ё1е} {скИдгеп} </сНу> </д!у> } ехрог! де!аи1! Мода! А в листинге 9.14 приведен код модифицированного компонента ТазкЕопп. Листинг 9.14. Кодмодифицированногокомпонента ТаакЕохт сопз! ТазкГогт = ({ !азк, соп!ех!з, опСгеаЁе, опС1озе, ореп }) => { ге!игп <Мода1 !!!1е='Сгеа!е ог ед!! а Ёазк' го1е=,д!а!од| ореп={ореп} опСапсе!={с!озе}> <Гогт> </!огт> <Мода!Гоо!ег> <Ьи!!оп опСИск={.. . }>Сапсе!</Ьи!!оп> <Ьи!!оп опСИск={... }>8ауе</ЬиС!оп> </Мода!Гоо!ег> </Мода1> } Наконец, рассмотрим кнопку удаления задачи в виде значка мусорной корзины, расположенного рядом с каждой задачей (листинг 9.15). Листинг 9.15. Код кнопки удаления задачи <!тд згс=(0е1е1:е1соп} И1д!11={24} Ие!дк!={24} а1!='Се1е!е !соп' аг!а-Ъазрорир=' сИа!од' го1е='Ьи!!оп1 !1!1е='Ве1е!е'
опС11ск={еуГ => { еИ;. з1:орРгорадаЁ1оп () еИ;.ргеуепЁ0еГаи1Ё() опйе1е1:е () }} Значок мусорной корзины функционирует как кнопка, поэтому мы и присвоили ему эту роль. Он уже имеет атрибут агга-Ъазрорир, поскольку в диалоговом окне пользователю даются указания подтвердить удаление. Но точно так же, как и в случае со списками и элементами списка, лучше реализо- вать компоненты, функционирующие как кнопки, в виде настоящих кнопок НТМЬ. Поэтому мы превратим этот компонент в кнопку, облекающую изображение, как показано в листинге 9.16. | Листинг 9.16. Модифицированный код кнопки удаления задачи <ЬиГГоп опСИск={еу11 => { еVГ. зГорРгорадаМоп () еуЪ. р^еVеп1^^еГаи1Ё () опРе1еГе () }} Се1еГе' аг1а-Ъазрорир=1 с11а1од' <1тд згс={Ре1еРе1соп} Н1С1ЬЬ={24} Ье1дкЁ={24} а1Р='Ое1еРе 1соп' /> </ЬиРРоп> Такой код будет не только более понятным для разработчиков, но также позволит перемещаться к этой кнопке и устанавливать на ней фокус посредством клавиши <ТаЬ>. Обсуждение В некоторых аспектах роли совмещаются с ориентирами. Например, существуют роли для ориентиров, такие как та1п и ЪеасЗег. Но предназначение ролей и ориенти- ров разное. Ориентиры, как можно предполагать по самому названию, являются способом для выделения основных частей веб-страницы. Роли же, наоборот, опи- сывают предполагаемое поведение какой-либо части интерфейса. В обоих случаях как ориентиры, так и роли служат для придания дополнительной информативности веб-странице.
Если интерфейс содержит компоненты, функционирующие как стандартные эле- менты НТМЬ (например, списки), часто будет лучше оформить соответствующий стиль обычной НТМЬ-разметкой, чем создавать элементы, кодируя их самостоя- тельно. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ы1р8://огеН.1у/0СГ§А. 9.3. Проверка доступности посредством Е8Нп( ЗАДАЧА Когда нет необходимости в оборудовании для обеспечения доступности, иденти- фикация проблем с доступностью может представлять трудности2. В процессе раз- работки также могут возникнуть проблемы регрессии, нарушающие доступность ранее протестированного кода. Что нам нужно, так это способ, позволяющий быстро и легко обнаружить пробле- мы с доступностью по мере их возникновения. Нам требуется какой-либо процесс, который непрерывно наблюдает за создаваемым кодом и немедленно фиксирует возникающие проблемы, пока мы еще помним, что этот код делает. РЕШЕНИЕ Более очевидные проблемы с доступностью в коде можно обнаружить при помощи инструмента езИпЕ, сконфигурированного должным образом. Инструмент езНгл выполняет статический анализ кода. Он может найти неисполь- зуемые переменные, пропущенные зависимости в вызовах хука изеЕЕЕесЕ и т. п. Если вы создали свое приложение при помощи средства сгеаЕе-геаЕ-арр, то, скорее всего, средство езИтл уже постоянно исполняется для вашего приложения. Сервер для разработки запускает езИпр на исполнение при каждом компилировании кода, и любые ошибки езИпъ отображаются в окне сервера. Если средство езИгл еще не установлено на вашем компьютере, его можно устано- вить, выполнив следующую команду прт: $ прт ЕпзЕаИ —зауе-беу езИпЕ Точно так же езИпЕ можно установить и при помощи уагп. Возможности езИпЕ можно расширить с помощью подключаемых модулей. Подключаемый модуль для ез11пЕ содержит набор правил, соблюдение которых статическим кодом езЕшЕ про- веряет при сохранении кода. Один из подключаемых модулей специально предна- значен для тестирования на наличие проблем с доступностью. Этот модуль называ- ется ]зх-а11у и устанавливается следующим образом: $ прт ЕпзЕаИ —5аVе-с1еV езИпЕ-рХидЕп-дзх-аИу 2 Мы обнаружили это сами в процессе работы над данной главой. В результате мы, несомненно, пропустили много, много проблем с доступностью в примере приложения.
Средство езИп!; можно исполнять вручную, добавив в файл раска§е.]§оп приложе- ния код, приведенный в листинге 9.173. Листинг $.17, Код для ручногоисполнения ез1хлъ "зсггрГз": { "Нпё": "езИп! зге" }, Но прежде чем использовать модуль дзх-аПу, его нужно сконфигурировать. Это делается путем обновления раздела езИп^сопПд файла раска§е.]зоп, как показано в листинге 9.18. Листинг 9.18.Обиовлениераздела е811песапГ1д "езИп^СопГтд": ( "ех1:епс1з": [ "геас<:-арр" "геасЪ-арр^ез!:", "р1ид1п: з зх-а11у/гесо1тапепс1есГ ], "р1ид!пз": [ "^зx-а11у" ], "ги1ез": {} } Эта конфигурация дает указание езИгл применить новый модуль, а также подклю- чает набор рекомендуемых правил по доступности. При желании можно также настроить работу каждого из правил, добавив дополни- тельные конфигурационные данные в раздел ги1ез. В листинге 9.19 приведен при- мер такой настройки, с отключением одного из правил. Листинг 9.19. Отключение правила евИлъ "езИп11СопГ1д": { "ех^епдз": [ "геас!;-арр" "геас1:-арр/]ез11", "р1ид!п: з5х-а11у/гесоггапепс1есГ Это особенно полезно, если нужно проверить код в хуках рге-сопипИ Сй или на сервере интеграции.
"ркидгпз": [ "]зх-а11у" ], "ги1ез": { "^зх-аПу/по-опскапде": "оГГ" ) } Отключение всех правил нецелесообразно, но для отключения конкретного прави- ла по-опскапде может быть веская причина. Разработчики модуля ]зх-аИу создали это правило из-за проблемы со старыми браузерами, каждый из которых реализовывал правило опсИапде по-своему. Одни браузеры генерировали событие опСкапде всякий раз, когда пользователь вводил символ в поле ввода, а другие создавали это событие только после того, как поль- зователь покинул это поле. Такое разное поведение создавало множество проблем для людей, работающих со средствами обеспечения доступности. Возможное решение — заменить все обработчики события опСкапде обработчиками события опв1иг, тогда все браузеры создавали бы событие изменения поля единооб- разно: по выходу пользователя из поля. Но в настоящее время данное правило полностью устарело, и поэтому его не реко- мендуется использовать в модуле ^зx-а11у. Если в коде КеасХ заменить все обработ- чики события опСкапде обработчиками события опВ1иг, то приложение будет рабо- тать совсем по-другому. Кроме того, в результате изменится стандартный способ отслеживания состояния полей форм в КеасЦ через событие опСкапде. Поэтому в данном случае лучше отключить это правило. Теперь, разрешив правила доступности, можно запустить езИпъ на исполнение: $ прт гип 11пё В одной из ранних версий приложения езПпт обнаружил несколько ошибок (лис- тинг 9.20). Листинг 9.20. Ошибки, обнаруженные езПлъ в ранней версии приложения $ прт гип Ипб > арр@0.1.0 Ипб арр > езИпб зге арр/згс/Тазк/Тазк.]з 6:9 еггог УтзтЫе, поп-1пкегаск1Уе е!етепкз ю.кк сИск капс11егз тизк кауе ак 1еазк опе кеуЬоагс! Избепег зх-а11у/с1^ск-еVепк5-каVе-кеу-еVепкз (Видимые, неинтерактивные элементы с обработчиками щелчков должны иметь, по крайней мере, один слушатель клавиатуры) 6:9 еггог ЗбаЫс НТМЪ е1ешепбз мббк еуепб капс11егз гесратге а го!е (Статические НТМЬ-элементы с обработчиками событий должны иметь роль) Ззх-а11у/по-збаб1с-е1етепб-1пбегасб1опз X 2 ргоЫетз (2 еггогз, 0 чагпгпдз)
Чтобы разобраться, в чем причина этих ошибок, взглянем на исходный код в файле Тазк^з (листинг 9.21). Листинг 9.21. Исходный код компонента Тазк —........-...................... -.............._________.____._________________I <11 с1аззЫате="Тазк"> <сИу с1аззЫате="Тазк-сопЬепЬз" опСЫск=(опЕсШ:}> </сИу> </И> Компонент тазк отображает подробности задачи внутри небольшой панели, назы- вающейся карточкой (рис. 9.7). Раутегйз | СгеаЁе раутепГз ассоипГ Рис. 9.7. Приложение отображает задачи в отдельных панелях, каждая из которых содержит кнопку удаления задачи Щелчок мышью по панели задачи открывает форму для редактирования подробно- стей задачи. Это осуществляется посредством обработчика события опСИск для раздела <с!1у> Тазк-сопбепбз. Чтобы понять, что здесь "не нравится" езИпс, сначала посмотрим на следующее сообщение об ошибке: 6:9 еггог ЗЬаЫс НТМЬ еЬетепЬз мИк ечепЬ капсПегз ^е^и^^е а го1е (Статические НТМЬ-элементы с обработчиками событий должны иметь роль) Ззх-аИу/по-зЬаЫс-еЬетепЬ-ЬпЬегасЫопз Такие элементы, как <сИл/>, являются статическими и не имеют встроенного инте- рактивного поведения. По умолчанию, это просто объекты, которые упорядочива- ют другие объекты. Недовольство езИпь вызвано тем, что наличие обработчика со- бытия опСИск подразумевает, что данный раздел <с^> по сути функционирует как активный компонент. Поэтому для пользователей устройств обеспечения доступ- ности нужно сообщить о назначении этого компонента. Средство езИпь ожидает, что мы сделаем это, присвоив разделу «Цу> роль4. Мы присвоим этому разделу «Нл7> роль кнопки, чтобы указать, что пользователь активизирует компонент, щелкая по нему мышью. Щелчок кнопкой мыши по пане- ли задачи вызывает открытие всплывающего окна для редактирования этой задачи. Поэтому мы также присвоим этому разделу <с11у> атрибут агЬа-Ъазрорир, чтобы со- общить пользователю, что щелчок по панели задачи открывает диалоговое окно. Соответствующий код приведен в листинге 9.22. 4 Подробная информация о ролях и их применении приводится в разделе 9.2.
Листинг 9.22. Код для присвоения роли и атрибута разделу <&?> компонента Тазк <И с1аззМаше=,Тазк,> <с!1у с1аз8Ыате=,Тазк-сопбепбз' го1е='ЬиЁЁоп' аг1а-казрорир=1 с11а1од' опСИск= { опЕсНк} </с1гу> </И> Часто вместо использования роли ЬиМоп лучше преобразовать элемент в собст- венно тег кнопки. Но в данном случае раздел <с!1у> заключает в себя довольно крупный блок текста НТМ1_, поэтому будет более разумным присвоить ему роль, вместо того, чтобы решать задачу, как сделать, чтобы серая кнопка выглядела как карточка. Если исполнить езНпъ снова, то опять будут выданы два сообщения об ошибках. Но теперь одно из этих сообщений новое (листинг 9.23). Листинг 9.23. Сообщения об ошибка после повторного исполнения езИпЪ $ прт гип ИпЁ > арр@0.1.0 Ипб арр > езИпТ зге арр/згс/Тазк/Тазк.]з 6:9 еггог \71з1Ые, поп-1пбегасЫуе еТешепбз сИск ЪапсПегз тизЬ кауе аб 1еазб опе кеуЬоагс) Избепег 3 зх-а1 Ту/сИск-еуепТз-кауе-кеу-еуепкз (Видимые, неинтерактивные элементы с обработчиками щелчков должны иметь, по крайней мере, один слушатель клавиатуры) 6:9 еггог ЕТешепбз и1ЬЬ 'ЬиЫюп' хпбегаебгуе го!е гсизб Ье ЬаЬЬаЫе (Элементы с интерактивной ролью "Ьиббоп" должны быть табулируемыми) Ззх-аНу/тпЬегасЫуе-зиррогбз-^осиз Х2 ргоЫетз (2 еггогз, 0 иагп1пдз) Мы уже сказали, что панель задачи ведет себя как кнопка. Но роли должны подчи- няться правилам. Если мы хотим, чтобы что-то считалось кнопкой, оно должно вести себя как кнопка. Одно из свойств кнопки — возможность перехода к ней и установки на ней фокуса посредством клавиши <ТаЬ>. Эту возможность можно реализовать, добавив в компонент тазк атрибут ьаЫпбех, как показано в листин- ге 9.24. Листинг 9.24. Добавление атрибута иымех в компонент тазк <И с1а5зЫате='Тазк’> <с11у сТаззИате^Тазк-сопЬепЁз'
го1е='Ьиббоп' ЁаЫпс1ех={0} опСИск= {опЕсПб} </сИу> </И> Значение о атрибута иаЫпйех означает, что наша задача будет частью набора табу- лируемых элементов страницы (т. е. элементов, между которыми можно переме- щаться при помощи клавиши <ТаЬ>). Атрибут еаЫпдех может иметь несколько значений. Значение -1 означает, что со- ответствующий элемент может получать фокус только программными средствами, а значение 0 означает, что это обычный табулируемый компонент. Если баЫпбех больше нуля, значит, система фокусирования должна дать ему более высокий приоритет. Обычно лучше избегать значений больше нуля, поскольку они могут создавать проблемы с доступностью5. Если исполнить езИпб снова, появится всего лишь одно сообщение об ошибке (лис- тинг 9.25). Г Листинг 9.25. Сообщение об ошибке после добавления атрибута ьаЫпдех $ прт гип Ип1: > арр@0.1.0 Ипё арр > езИпб зге арр/згс/Тазк/Тазк.^з 6:9 еггог У131Ые, поп-1п1:егасбгуе е1етепбз мтбЬ сИск ЪапсПегз тизб Ьауе аЪ 1еазб опе кеуЬоагб ИзТепег ^5x-а11у/с1^ск-еVеп1:з-каVе-кеу-еVепЁз (Видимые неинтерактивные элементы с обработчиками щелчков должны иметь, по крайней мере, один слушатель клавиатуры) 1 ргоЫетз (1 еггогз, 0 магптпдз) Это сообщение означает, что у нас есть событие опСИск, чтобы сообщать, что про- исходит при щелчке мышью по панели задачи, но нет кода для его обработки. Если у пользователя нет возможности использовать клавиатуру, то он не сможет редак- тировать задачу. Поэтому нам необходимо добавить какой-либо обработчик нажатия клавиши. Мы добавим код для вызова события редактирования при нажатии пользователем кла- виши <ЕЩег> или пробела (листинг 9.26). 5 Проблемы, связанные со значениями баЫпйех больше нуля, рассматриваются в разделе 9.8.
Листинг 9.26. Код для вызова события редактирования <И с1а5зМате="Тазк"> <сИу с1аззМате="Тазк-сопГеп1з" го1е="Ьи111:оп" 11аЫпс1ех= {0} опСИск= {опЕсШ:} опКеуОомп={(еу1) => { И (еVI:.кеу === 'ЕпРег' || еуХ.кеу === ' ') { еуЁ. ргеуепЮеГаиХЁ () опЕсНХ () } }} </сИу> </И> Добавление обработчика клавиатуры устраняет оставшуюся ошибку. Для каждого из правил для модуля дзх-аИу есть страница на веб-сайте СПНиЬ (Ийр8://оге||.1у/ио7Ку), содержащая дополнительную информацию о том, как мо- жет возникнуть нарушение данного правила и что делать, чтобы не допустить этого. Обсуждение Модуль ]зх-а11у, наверное, является одним из наиболее полезных для средства езИпт. Часто правила контроля качества кода проверяют стиль программирования и могут обнаружить значительное число проблем с ним. Но подключаемый модуль ]зх-а11у способен по-настоящему изменить дизайн разрабатываемого приложения. Возможность навигации в приложении посредством клавиатуры важна не только для тех, кто использует средства обеспечения доступности, но также и для тех, кто часто пользуется вашим приложением. Пользователи, постоянно работающие с приложением, часто предпочитают клавиатуру вместо мыши, поскольку это тре- бует меньше движений и обеспечивает большую точность. Мы также рассмотрели, как посредством настроек атрибута раЫпйех элементам страницы можно придать клавиатурную фокусировку. Некоторые браузеры (осо- бенно ЕйеГох) предоставляют неявные признаки наличия клавиатурного фокуса у элементов. Ясно указать пользователю, где в настоящее время находится фокус, можно, добавив в приложение следующий код С88 высокого уровня: : Хосиз-У1з1Ые { оиШпе: 2рх зоИб Ыие; }
Это правило стиля добавляет различимое очертание вокруг компонента, имеющего клавиатурный фокус. Некоторые пользователи с большей вероятностью предпочтут осуществлять навигацию посредством клавиатуры, увидев, что эта опция доступна. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу Ьйр8://оге11.1у/ОСГеА. 9.4. Динамический анализ посредством ахе ОеуТоо1з ЗАДАЧА Инструменты статического анализа, такие как езИпб, могут помочь обнаружить многие проблемы с доступностью. Но возможности статического анализа ограни- чены, и он часто не способен выявить ошибки времени исполнения. При исполнении программа может функционировать таким образом, который ин- струменты статического анализа не в состоянии предвидеть. Нам нужно проверить доступность приложения при его исполнении в веб-браузере. РЕШЕНИЕ Эту задачу можно решить, установив в браузер подключаемый модуль ахе 1ЭеуТоо18. Данный модуль предоставляется как для браузера ЕйеГох (ЬИрз:// огеП.1у/81ТсВ), так и для браузера СЬготе (Ьйрз://оге11.1у/МЫК0). После установки модуля в консоли разработчика браузера появляется дополни- тельная вкладка (рис. 9.8). Рис. 9.8. Вкладка ахе ОеуТоП в консоли разработчика браузера Чтобы разобраться с работой данного инструмента, приведем в негодность немного кода в примере приложения, описываемого в этой главе. Это приложение содержит компонент тазкЕопп, который отображает всплывающее окно. Компоненту была присвоена роль сИа1од, которую мы изменим на какое-либо недействительное значение, как показано в листинге 9.27.
Листинг 9.27. Присвоение недействительной роли компоненту ТазкГога сопзё ТазкЕогт = ({ Ёазк, сопЁехЁз, опСгеаЁе, опСХозе, ореп }) => { геЁигп ( <Мос1а1 Ё1Ё1е=”СгеаЁе ог есИЁ а Ёазк" го1е="Е1зЪ" ореп={ореп} опСапсе!={сХозе) > <ёопп>...</Ёогт> <МодаХГооЁег>...</МодаХЕооЁег> </МобаХ> } Откроем приложение в браузере по адресу Ьйр://1оса1Ьо8<:3000 и нажмем кнопку + для создания задачи. Откроется диалоговое окно создания задачи (рис. 9.9). Рис. 9.9. Нажатие кнопки + открывает всплывающую форму для создания новой задачи Теперь откроем в браузере панель инструментов разработчика, перейдем на вклад- ку ахе ВеуТооЬ и выполним аудит страницы. В результате будут обнаружены две ошибки (рис. 9.10).
Рис. 9.10. Присвоение компоненту недействительной роли приводит к двум ошибкам Первая ошибка вызвана тем, что компонент диалогового окна больше не имеет действительной роли, а вторая — тем, что он больше не имеет роли <Па1од, что означает, что он больше не может служить в качестве ориентира. Некоторые роли, такие как с11а1од, помечают компонент как необходимый элемент страницы. Все части приложения должны находиться внутри ориентира. Если выполнить сброс кода и обновить аудит ахе ВеуТоок, то сообщения об ошиб- ках исчезнут. Можно надеяться, что в будущем средства статического анализа кода будут под- держивать сканирование всего кода на выявление недействительных значений ролей6. Но средство ахеОеуТоок может также выполнять проверку на наличие дру- гих, более тонких, проблем. В рассматриваемом примере приложения отредактируйте файл Арр.сзз, добавив в него следующий код, чтобы изменить цвет шрифта основного заголовка: ы { со1ог: #9е9е9е; } Мападе Тазкз Согйаси I Еуепк | Тазкз ] 1Чо1е5 | Т!тейес | Пшгу. I Ехрепзез 11пУО1се$ Рис. 9.11. Результат редактирования файла Арр.сзз Результаты этого редактирования не выглядят слишком радикальными (рис. 9.11), но заставляют средство ахе ОеуТоок выдавать ошибку (листинг 9.28). 6 К тому времени, когда вы будете читать эту книгу, такое средство, возможно, уже будет существовать.
Листинг 9.28. Ошибка, вызываемая редактированием файла Арр.сзз Е1ешеп1:з тизб ЬаVе зиГЛЛсЛепЬ со1ог сопЬгазЬ ЕЛх ЬЬе ЛоИоиЛпд: Е1етепЬ Наз ЛпзиЛГЛсЛепЬ со1ог сопбгазЬ оГ 2.67 (Логедгоипд соЛог: #9е9е9е, Ьаскдгоипс! соЛог: ЛопЛ зЛхе: 24.0рЬ (32рх), Лопб неЛдЬЬ: Ьо1с1) . Ехресгед сопбгазь гаЫо оЛ 3:1 (Между цветами элементов должен быть достаточный контраст) (Исправьте следующее:) (Контраст цвета элемента величиной 2.67 недостаточен) (Гогедгоипс! соЛог: #9е9е9е, Ьаскдгоипс! соЛог: Гопб зЛхе: 24.0рЬ (32рх), ГопЛ иеЛдЫ: ЬоЛй) . (Ожидается коэффициент контрастности величиной 3:1) Ошибки с неверно заданным контрастом сравнительно легко исправить в консоли разработчика браузера СЬготе. Выберите для исследования заголовок <ы>, в част- ности цветовой стиль этого элемента, а затем щелкните по значку панели выбора цвета. В результате откроется всплывающая панель, содержащая информацию о коэффициенте контрастности элемента, вызывающего проблему (рис. 9.12). 31у*в9 Соглринк! ЬауоЛ Е*еп1 ЦЛвпес» ООМ Вгевкройй» РгореИИв АоомвЫКу ЯИег е1елеп1.51у1е { } Ы < соХог: В«9е9е9е М { И>р1. ЙМ1Е-; шаг юагд1. 1МГ911 ГопГ' 1пНегйес11 & •9е9е9е 1пЗех.с^$:1 пРол±, ‘8едое VI1, 'КоЬо1о‘, ‘Охудеп*, ‘цЬипТи1, *Свп1аге1Л‘ Мейе', залв-зегИ; НЕХ Ьо<1у { тагдхп ТопХ-1 ^ыеЬк! Соп(га8( га»ю 2.63 $ Рис. 9.12. Панель с информацией о цветовых свойствах, вызывающих ошибку Открыв раздел Соп<га$1, можно откорректировать цветовые характеристики, чтобы они отвечали требованиям АА и ААА стандарта доступности для контраста (рис. 9.13).
Рис. 9.13. Корректировка цвета, чтобы коэффициент контраста отвечал требованиям стандарта доступности Браузер СЬготе предлагает заменить цвет № 949494 цветом № 767676. Многие лю- ди даже не заметят разницу, но чтение данного текста будет значительно легче для людей, восприимчивых к контрасту (рис. 9.14). Мападе Тазкв СйШасХа I I Тазкз | | ТтлеВес I Сшгу. I Ехвепгеа I кпагкег Рис. 9.14. Цвет изменен так, чтобы контраст соответствовал стандарту ААА Иногда, если браузер СЬготе не может определить конкретный цвет фона, то он не отображает информацию о контрасте. Этой проблемы можно избежать, вре- менно ПРИСВОИВ Проверяемому Элементу атрибут Ьаскдгоипс1Со1ог. Обсуждение Средство ахеЭеуТоок легко использовать, но при этом оно может обнаружить многие проблемы доступности, не поддающиеся определению инструментами ста- тического анализа. Хотя в процессе работы разработчику приходится выполнять ручную проверку на наличие ошибок доступности, в следующей главе мы увидим, что существуют спо- собы автоматизации тестов на доступность при помощи браузера.
Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ь11р8://оге11.1у/ОСГ§А. 9.5. Автоматизация тестирования в браузере посредством Сургеев Ахе ЗАДАЧА В предыдущем разделе мы выяснили, что некоторые проблемы с доступностью проявляются только при исполнении приложения в настоящем браузере, и поэтому их нельзя обнаружить при помощи инструментов статического анализа. Если при тестировании в браузере полагаться на ручные методы, то существует большая вероятность возникновения ошибок регрессии. Было бы намного лучше автоматизировать такие типы ручных тестов, которые инструменты наподобие ахе ОеуТоок позволяют нам выполнять в браузере. РЕШЕНИЕ Автоматизировать тестирование на доступность в браузере можно при помощи подключаемого модуля сургез-ахе для фреймворка тестирования Сургезз. В этом модуле используется та же библиотека ахе-соге, что и в модуле ахе ОеуТоок. Тем не менее поскольку мы будем применять эту библиотеку для тестирования на уровне браузера, то сможем автоматизировать процесс проверки, чтобы сервер интеграции мог сразу же обнаруживать ошибки регрессии. Для начала необходимо установить для нашего приложения фреймворк Сургезз и библиотеку ахе-соге: $ прт 1пзРа11 —заVе-с^еV сургезз ахе-соге Затем можно установить и сам модуль сургезз-ехе: $ прт 1пзЁа11 —зауе-деу сургезз-ахе Если это первая установка фреймворка Сургезз, то нужно запустить на исполнение ее приложение, чтобы создать соответствующие папки и начальный код, который будет служить исходным для тестов. Приложение Сургезз запускается на исполне- ние следующей командой: $ прх сургезз ореп Далее нам нужно выполнить конфигурацию модуля сургезз-ехе. Для этого отредак- тируем файл сургезз/8иррог1/тс1ех.]8, добавив в него следующую строку: йпрогЁ 'сургезз-ахе' Еще нам потребуется пара хуков, которые позволят записывать ошибки в процессе прогона теста. Для этого добавим в файл сургезз/ркщшз/тдех.зз код из листин- га 9.29.
| Листинг 9.29. Код хуков для добавления в файл еургезз/р1идхпз/хгиШ:. эз шодиХе.ехрогЁз = (оп, сопЁХд) => { оп('Ёазк', { Ход(теззаде) { сопзо1е.Ход(теззаде) геЁигп пи!1 ЁаЬХе(теззаде) { сопзо1е. ЁаЬХе (теззаде) геЁигп пи11 Далее удалим все примеры тестов из папки сургез^/шХедгайоп, создадим в ней файл ассезз1Ь11ку.]з и вставим в него код из листинга 9.307 *. I ЛИСТИНГ 9.30. Код файла сургезз/хпЁедгахоп/асоеззд±>х1хЬу.^з ГипсЁЁоп ЁегтХпаХЬод(У1о1аЁ1опз) { су.Ёазк( 'Ход', '${у1оХаЁХопз.ХепдЁк} ассеззХЬХХЁЁу V^оXаЁ^оп${ У1оХаЁ1опз.ХепдЁХ1 === X ? 11 : 'з' ) $^1оХаЁ1опз.ХепдЁ1"1 === X ? 'маз' : 'меге'} сХеЁесЁесГ ) сопзё уХоХаЁхопОаЁа = уХоХаЁхопз.тар( ({ Ы, ипрасЁ, дезсгХрЁЁоп, посХез )) => ({ Ы, ХтрасЁ, безсгХрЁЁоп, посХез: посХез.ХепдЁЪ, су.Ёазк('ЁаЬХе*, V^оXаЁ^оп^аЁа) сопзоХе.ЁаЬХе ^ХоХаЁхопСаЁа) дезсгХЬе('сап Ье изесГ , О => { ХЁСзкоиХс! Ье ассеззхЬХе мЬеп зЁагЁЁпд', () => { Су.У131Ё('/') су.Хп^есЁАхеО су.сЬескАХХу(пиХХ, пиХХ, ЁегтХпаХЬод) 7 Название файла не имеет значения при условии, что его расширение ]з и он находится в папке сургезз/ тХе^гаХюп.
Этот фрагмент создан на основе кода примера из репозитория сургез-ахе (Ьйрз:// огеП.1у/2Ехух). Тест выполняется функцией йезсгхЬе. А функция регпйпаХЬод сообщает об ошибках. Структура теста следующая: 1. Открываем страницу в корневой папке /. 2. Вставляем в страницу библиотеку ахе-соге. 3. Выполняем аудит страницы. Библиотека ахе-соге, которая выполняет большую часть работы, также использует- ся в других инструментах тестирования, например в модуле расширения браузера ахе ОеуТоок. Библиотека исследует текущую модель БОМ, проверяя ее соответст- вие правилам в своей базе правил, а затем сообщает об обнаруженных ошибках. Модуль сургезз-ахе вставляет библиотеку в ахе-соге в браузер, а затем посредством команды сЬескАИу выполняет аудит страницы. Обнаруженные проблемы отправля- ются функции ЁегтХпаХЬод. Если теперь исполнить наш тест в Сургезз, дважды щелкнув по файлу ассез81Ь11йу.]з, то он будет успешно выполнен, как показано на рис. 9.15. Что ж, давайте тогда создадим проблему, добавив еще один тест (листинг 9.31). Рис. 9.15. Код страницы успешно проходит тест на доступность
Листинг 9.31. Код второго теста (' зкоц1с1 Ье ассеззхЫе ыИеп сгеакхпд а Тазк', () => { Су.У151С('/') су.1пдесЬАхе() су. сопЁахпз (1 +') . сИск () су.сЬескАИу(пиИ, пиИ, кегпйпаТЪод) }) Этот тест запускает приложение, вызывает нажатие кнопки +, открывая форму для создания новой задачи, а затем выполняет проверку доступности. В ее текущей форме приложение успешно пройдет и этот тест. Поэтому модифици- руем компонент тазкгогт, присвоив ему недействительное значение роли, как пока- зано в листинге 9.32. Листинг 9.32. Присвоение недействительного значения роли компоненту ТазкЕогта сопзЬ ТазкГогш = ({ Тазк, согЛехЬз, опСгеаЁе, опС1озе, ореп }) => { гекигп ( <Мос1а1 Ё1Ые="Сгеа1:е ог есКо а Ьазк" го1е="ЬаЁ5капд" ореп={ореп} опСапсе1={с1озе} <Гопп>...</Гогш> <Мос1а1ЕооЬег>.. .</Мос1а1ГооЬег> </Мос1а1> ) ) Если выполнить наш тест теперь, то он завершится неуспешно. Тест нужно испол- нять с открытой консолью 1ауа8спр1: (рис. 9.16), чтобы увидеть ошибку в таблице консоли. Обсуждение Замечательное введение в тему аудита доступности и тестирования при помощи модуля сургезз-ахе было представлено в докладе Марси Суттон (Магсу 8ийоп, Ьир8://огей.1у/п86К2) на конференции КеасЫ8 С1Й5 СопГегепсе. Из доклада мы впервые узнали об этом модуле и с тех пор постоянно используем его. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬНрз^/огеПЛу/ОСГ^А.
Рис. 9.16. Подробности ошибки в консоли ^уа8спр{ 9.6. Добавление в страницу кнопок пропуска содержимого ЗАДАЧА Часто страницы содержат большой объем информации в самом начале. Там могут быть навигационные ссылки, меню быстрых действий, ссылки на сайты социаль- ных сетей, поля поиска и т. п. Для тех, кто знакомится с содержимым страницы ви- зуально и пользуется мышью для навигации по ней, это не представляет какой-либо большой проблемы. Скорее всего, такой пользователь мысленно отфильтрует все это и перейдет к основному содержимому страницы. Но тем, кто "просматривает" страницу при помощи средства чтения с экрана, мо- жет, придется прослушивать подробности всех этих начальных элементов на каж- дой посещаемой странице. Современная технология чтения с экрана часто позволя- ет пользователям автоматически переходить между разделами и заголовками, но тем не менее, чтобы вычислить, где начинается важное содержимое, может потре- боваться некоторое время. Поэтому код многих веб-сайтов содержит скрытые ссылки и кнопку с надписью типа "Перейти к содержимому", позволяющую пользователям с помощью клавиа- туры перейти к началу основной части страницы.
Один из таких веб-сайтов — УоиТиЬе. Если на этом сайте нажать клавишу <ТаЬ> три раза подряд, то в его левом верхнем углу отобразится кнопка 8К1Р ^АVIСАТIО^ (рис. 9.17), после чего нажатие клавиши пробела переместит фокус клавиатуры на основное содержимое. ОЫай” ЗК1Р НДУЮАТКЖ О 6 |В О Рм№мМмм11АХ7 Биллу _1ахх Слк Вова _ ВДы СИ* Мияк / В 9М 9 тошЬа адо 10 5ЮН 1Н ПшМапп Р1ау11(Ш ТЪаОодо ЕмНоой Ц** Мив1с СкдовЫр* С*гй »Сиа*опК Эняш РооИ Шогкм кпжГалЙу РЬыМалл О 7 ВМ у1виув • 4 (1а г • Ожмг МЬ Оод (Хж&Ьп* ДЬмД к * боод Воу ЙМУккоз 3 5М У1<вд * 1 толтЬ вдо ДукЯ, Ои* Ора, МагНп Балк 1 Кудо. ТБ* ОС Ммвк ф 1 9М - 9 ггадгНЪж «до Рис. 9.17. Кнопка 8К1Р ИА\/16АТ0М на веб-сайте УоиТиЬе Как можно создать такую кнопку, которая отображается только при навигации по- средством клавиши <ТаЬ>? РЕШЕНИЕ Данный рецепт содержит компонент многократного использования зкгрви^оп, который можно вставлять по сути на любую страницу, не нарушая ее дизайн или разметку. Этот компонент должен обладать следующими возможностями: ♦ Быть скрытым, пока не будет открыт посредством клавиши <ТаЬ>. Просто про- зрачная кнопка здесь не подойдет, поскольку пользователь может случайно на- жать ее щелчком по этой части экрана. ♦ Отображаться поверх содержимого страницы, чтобы не оставлять для нее места в разметке. ♦ Функционировать как распознаваемая кнопка. Это означает, что компонент должен быть распознаваем средствами чтения с экрана и вести себя как кнопка. Когда на нем установлен фокус, он должен срабатывать при нажатии клавиши <Еп1ег> или клавиши пробела. ♦ Скрываться после срабатывания. В процессе разработки мы добавим еще несколько других требований, но перечис- ленных достаточно, чтобы приступить к работе.
Начнем с создания нового компонента, называющегося зкирвипоп. Этот компонент будет возвращать один раздел «Иу>, допуская включение в него любых передан- ных ему потомков. Соответствующий код приведен в листинге 9.33. Листинг 9.33. Код компонента зкжрвиъъоп сопзё ЗкЁрВиЁЁоп - (ргорз) -> { сопзё { сЁаззИате, скНсксеп, ...оЁЬегз } = ргорз геЁигп ( с1аззКате={'2к1рВиЁЁоп ${с!аззНате || ’ 1}'} {...оЁкегз}> {сННдгеп} </с11у> ) Компонент также будет принимать имя класса и любые другие свойства, которые могут передаваться ему родителем. Компонент должен распознаваться средствами чтения с экрана как настоящая кнопка. Этого можно добиться, заменив раздел «Ну> настоящей кнопкой, но мы не будем так делать, чтобы облегчить задачу применения к нему стилистического форматирования. Но мы присвоим ему роль кнопки и, поскольку роли имеют пра- вила, также присвоим его атрибуту ЁаЫпйех значение о. Последнее нужно сделать в любом случае, поскольку пользователь должен быть в состоянии перейти к этой кнопке посредством клавиши <ТаЬ>. Соответствующий код приведен в листин- ге 9.34. Листинг 9.34. Присвоение компоненту зкхрвиъьоп роли ЬиЪЬэп сопзГ 5к1рВиЁЁоп = (ргорз) => { сопзГ { с!аззМате, сЫ1с1геп, ...оЁкегз } = ргорз геЁигп ( с1аззМате={'ЗкЁрВиЁЁоп ${с1аззЫате || '} го1е="ЬиЁЁоп" ЁаЫпс1ех={0} {...оЁкегз} {сИПбгеп} </с!1У> ) } Нажатие кнопки должно вызывать какое-то действие. Или, вернее, действие долж- но вызываться нажатием клавиши <Еп1ег> или клавиши пробела. Поэтому мы по- зволим данному компоненту принимать свойство опсиск, но присоединим к нему
обработчик события, срабатывающий при нажатии клавиши <Еп1ег> или клавиши пробела. Соответствующий код приведен в листинге 9.35. । Листинг 9.35. Добавление в компонент зк±рВиРРоп свойства опСИск сопзё ЗкЁрВиЁЁоп = (ргорз) => { сопзё { с!аззКате, скНскгеп, опСИск, ...оЁкегз } = ргорз геЁигп ( «Цу с1аззЫате={'ЗкЁрВиЁЁоп ${с1аззЫате || ' го1е="ЬиЁЁоп" ЁаЫпс1ех={0} {...оЁИегз} опКеуВомп-((еуЁ) => { 1Ё (еуЁ.кеу === 'ЕпЁег' || еуЁ.кеу === ' ') { еуЁ.ргеуепЁ0еГаи1Ё() опСИск (еуЁ) {скИбгеп} </сНу> Конечно же, мы могли бы назвать это свойство опКеусомп, но кнопки обычно имеют свойства опСИск, и это название легче запомнить, когда мы будем использовать его. Наконец, мы позволим компоненту принимать атрибут геЁ, что будет полезным в следующем рецепте. Атрибуты геЁ нельзя передавать таким же образом, как большинство других свойств. Отрисовщик КеасТ с помощью атрибута геГ отслеживает в модели ВОМ сгенерированные элементы. Чтобы компонент мог принимать объект г сё, нам нужно заключить весь компонент в оболочку в вызове функции Кеас1 СогеагсЖеГ. Эта функция возвращает заключен- ную в оболочку версию компонента, извлекая атрибут гег из родительского компо- нента и явно передавая его облекаемому ею компоненту. Это может звучать немно- го сложновато, но в реальности означает лишь то, что показано в листинге 9.36. Листинг 9.36. Полный код компонента вкхрвиъъоп со всеми модификациями нпрогЁ { ЁогыагсЖеЁ } Ёгот 'геасЁ1 ЁтрогЁ './ЗкгрВиЁЁоп.сзз1 сопзё ЗкЁрВиЁЁоп = ГогмагбКеЁ((ргорз, геГ) => { сопзё { с!аззКате, скПскгеп, опСИск, ...оЁИегз ) = ргорз
ге1:игп ( <С11У с1аззКате=('5к1рВиЁЁоп ${с1аззЫате || ’'}'} го1е="ЬиЁЁоп" ГаЫпс1ех={0} геЁ={геП {...оЁкегз} опКеуБоип={(еУЁ) => { 1Ё (еУЁ.кеу === ’ЕпЁег1 || еуЁ.кеу === 1 ') { еуЁ. ргеуепЁОеГаиИ () опСИск (еVЁ) } }} > {скПёгеп} </с11у> ) }) В листинге 9.36 приведен полный код компонента 5к1рВиЁЁоп, включая импортиро- вание информации о стилевом оформлении. Это всего лишь кнопка. Все остальное осуществляется оформлением стиля в файле БИрВиЯоп.сзз. Нам нужно, чтобы наша кнопка отображалась поверх всего остального содержимо- го страницы, поэтому мы присвоим ее атрибуту г-тпдех какое-либо по-настоящему большое значение: .ВкхрВиЁЁоп { г-1Пбех: 10000; } Кнопка должна быть скрыта до тех пор, пока пользователь не перейдет к ней по- средством клавиши <ГаЬ>. Это можно было бы реализовать, сделав кнопку про- зрачной, но появились бы две проблемы. Первая состоит в том, что кнопка может разместиться поверх какого-либо активируемого щелчком элемента. В таком слу- чае он заблокирует щелчки, если только атрибутам рсапьег-еуепЁз не присвоить зна- чение попе. Другая проблема заключается в том, что кнопка, хотя и прозрачная, все- таки находится на экране, и может оказаться еще одной помехой для средства чте- ния с экрана. Если средство считывания с экрана пространственно преобразует экран в код Брайля, то пользователь может услышать "Перейти к содержимому" посередине фрагмента текста. Поэтому мы не будем размещать нашу кнопку на экране до тех пор, пока она нам не понадобится. Соответствующий код С88 приведен в листинге 9.37. Листинг 9.37. Код С88 для размещения кнопки вне экрана .ЗкхрВиЁЁоп { 2-1пс1ех: 10000; розхЁЁоп: аЬзо1иЁе;
1еЛ:: -ЮООрх; 1:ор: -ЮООрх; Что же происходит, когда кнопка получает фокус? Мы можем задать стили, кото- рые применяются только в этом случае. Соответствующий код С88 приведен в лис- тинге 9.38. Листинг 938. Код С88 для применения ккнопке при получении фокуса . 8к1рВи!:€оп { 2-1Пс1ех: 10000; розШоп: аЬзоХиЁе; 1е1Г: -ЮООрх; Г:ор: -ЮООрх; } .ЗктрВиЁЁоп:Госиз { 1ор: аи^о; 1е1Т: аи!:о; Помимо этого, можно просто добавить немного визуального стилистического оформления (листинг 9.39). Важно помнить, что не все, кому придется иметь дело с этой кнопкой, будут работать со средством считывания с экрана. Некоторые пользователи могут применять клавиатурную навигацию потому, что они не могут задействовать мышь или просто считают, что для них это удобнее. Листинг 9.39. Дополнительное Стилистическое оформление кнопки . 8к1рВи1Лоп { 2-1пс1ех: 10000; розЮЮп: аЬзо1иЁе; 1е^: -ЮООрх; Ёор: -ЮООрх; Гогйз-зтге: 12рх; Ипе-ЪехдкЁ: 16рх; сИзр1ау: хпНпе-Ыоск; со1ог: Ыаск; ^оп!:-^ат11у: запз-зегИ; Ьаскдгоипс1-со1ог: #ШГ88; рабсИпд: 8рх; тагд1п-1еЛ:: 8рх; Т .8к1рВи<:1оп:^оси5 { 1ор: аЩо; 1е±^: аиТо; }
Теперь мы можем вставить компонент зкгрВисеоп где-либо возле начала страницы. Хотя он не будет видимым до тех пор, пока не получит фокус, его позиция на экра- не имеет значение. Он должен быть на расстоянии двух или трех нажатий клавиши <ТаЬ> от начала страницы. Мы добавим его в раздел <Иеас1ег>, как показано в лис- тинге 9.40. Листинг 9.40. Размещение компонента зкхрвиъъоп на странице <кеадег> <5к1рВиЁЁоп опС11ск={() => босшпепЁ. диегуЗе1ес1:ог (1. аскЗВи^оп1) . Госиз ()} > Зктр Ьо сопЬепк </5к1рВиЫ2оп> <Ь1>Мападе Тазкз</Ы> </Ъеас1ег> Здесь мы находим элемент, который получит фокус, просто используя метод досшпепь. диегуЗеТесЁог. Элемент, который нужно пропустить, можно было бы снаб- дить ссылкой, или иначе перейти к требуемому элементу. Но мы открыли для себя на практике, что наиболее прямолинейным подходом будет вызов простого метода босшпепк. яиегуЗе1ес1юг. Он позволяет с легкостью обращаться к элементам, которые могут отсутствовать в текущем компоненте. Он также не полагается на переход к закладке в странице, что может оказаться неработоспособным в случае изменения метода маршрутизации приложения. Если мы теперь откроем приложение в браузере и нажмем клавишу <ТаЬ>, то в ле- вом верхнем углу приложения должен отобразиться компонент зкхрВииоп в виде кнопки 8к1р То сопТепТ (рис. 9.18). Обсуждение Желательно разместить компонент знрвиыюп на расстоянии трех нажатий клавиши <ТаЬ> от начала страницы. Также полезно сделать количество нажатий клавиши <ТаЬ> для перехода к этой кнопке одинаковым для всех страниц приложения. Тогда пользователь вскоре вычислит, как перескакивать к важной части каждой страницы. Мы также обнаружили, что такие кнопки пропуска второстепенного со- держимого пользуются популярностью у людей, находящих работу с клавиатурой более продуктивной, нежели с мышью. Можно также создать стандартную кнопку пропуска содержимого для каждой страницы, которая перемещает фокус на первый табулируемый элемент главного раздела <ша1п> страницы8. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу М(р8://огеП.1у/0СГ^А. 8 Дополнительная информация о разделах <ша1п> приводится в разделе 9.1.
Тазкз IьийеI тик51 щди | лпщЕи I йшу I Ежииг! бавки Саг Ви*1пмаоТПс« | ияк [ №* со-ногкшд «рк« Сос11пд С1мгу«г ВиМ С1 мгмег Р<н1й Сг«М« ц*«г роПЫ Агккой | Сг«1а АлдпоИ»<41 ЕтаП Соигм» НТсЬ V*»* СОИ1И Ногле &№Я0 РЬопе Са11 ассоипит Ж А*к ЫхмЛ кМегпеЪйпа< иж Оота1ппшпа Виу скнти^лепч ОЖсе РодЫогсотрапу ( Соггфй!* гждЫгаЬоп йеийй ЙТЙПЖ 5а1в» их гадогшюп В Ацк ессоигилп Егйй I МирЬивЬан ат*4 УмЛЪЬе ассоип! Ъдотаг гаг* вссрий Вапкассоип* Орел Ъдейъгм Ь*гД ассомт РезеагсЬ СМоом сотрапу Ж пате СЬеск йм мЫЯейотжйп патм Яаск I ПщМег йес* мссяига Раупмли Сг*ж1ж цЖупчГЛ» яссоил! 8Норр!пд МоМ ягЪс^еи В РигеЪма впб пмх!|?у то4а! вгПсИа Виу сатегй ОдЙж! 51_Я. Длили Виу НдНи ИпдЙдГц АпжКт 02026, Дтй^&тчЫжс! СогтаиПап Сар. Д1 ккд Йемгл^ Рис. 9.18. Отображение кнопки перехода к содержимому при нажатии клавиши <ТаЬ> 9.7. Добавление возможности пропуска областей страницы ЗАДАЧА В разделе 9.6 мы увидели, что кнопки для пропуска содержимого позволяют поль- зователю быстро пройти мимо второстепенных объектов в виде заголовков и эле- ментов навигации в начале страницы и добраться до основного содержимого. Но возможны ситуации, когда пользователь хотел бы иметь возможность пропус- тить набор компонентов даже в основном содержимом. Рассмотрим пример прило- жения создания и редактирования задач, которое мы рассматриваем в этой главе. Пользователь может создать довольно большое количество задач в каждой группе (рис. 9.19). В данном случае если пользователь хочет перейти к группе задач зьорршд, то ему для этого пришлось бы пройти через все другие 14 предшествующих задач. Более того, каждая из этих задач имеет две фокусные точки: сама задача и кнопка удале- ния этой задачи. Следовательно, даже после перехода в начало основного контента, чтобы добраться до требуемого содержимого, пользователю придется пройти через каждую из 28 фокусных точек. Как можно предоставить пользователю возможность пропустить набор компо- нентов?
РЕШЕНИЕ Данную задачу можно решить, создав пропускаемые области (т. е. набор компонен- тов) при помощи созданного в предыдущем разделе компонента зкхрви^оп. Например, перемещаясь из начала в конец при переходе в какой-либо раздел ос- новного содержимого страницы, например раздел задач ОГПсе, нужно отобразить кнопку, позволяющую пользователю полностью пропустить все задачи этого раз- дела и перейти в следующий раздел (рис. 9.20). Подобным образом мы хотим, чтобы эта кнопка также отображалась и при перехо- де в раздел ОГПсе, перемещаясь с конца в начало содержимого, также позволяя пропустить все задачи этого раздела, но теперь перейти в предыдущий раздел (рис. 9.21). Эти кнопки должны отображаться только при входе в группу, но не при выходе из нее. Это означает, что кнопка 8к1р ОГПсе отображается только при перемещении вперед посредством нажатия клавиши <ГаЬ>, а кнопка 8к1р ЬеГоге ОГйсе — только при перемещении назад. Прежде чем вдаваться во все тонкости реализации возможности пропуска области, рассмотрим, как мы будем использовать эту возможность. Наш пример приложения отрисовывает ряд групп задач с помощью компонента ТазкзСоп^ехГз, как показано в листинге 9.41.
Рис. 9.20. Кнопка пропуска группы для перехода в следующую группу Мападе Таака Сирией I Ежйа | Тмка I Ыо1м | ТЬпейвс Рчку I Ехмп»« I ктуоке» Ноте 5кфШ«*О«Нса ЛОЛО------ Оота)ппмп» Ви*1пм«оГНс« 5ПкЙо ВиШКиАа 1ихй-й Нта со-елмкИд СяН ассошйагН АЛ еЬои1 гйвтайол*1 иж ги>м ЕтаЙ Сод1пд ОК1се 5**ир Ьи«И*н яггиЦ Няд)я№ сотрясу СотркЛ» гадНимкт двйй «Лпе УмЛОЬа аеевип* ВапкассоигЛ Орт Ьтлан Ыюк мхаипв 8Ьск ВДйЫг ЛкА ассоигх РаутмЛя Сгм1е мутеой асоимп 02026, Ата1дата1к1 СогаиНвгЬ Согр. АМ №д№ Нвисмк!. 8ай* их гаДОгавоп АЛвссодИлп^ Виу емпегз 0*дНл1 ЧЬЙ. Атахол РеввагсН СЬмма сотрапу пат» ОЪес1с1ог жтПаЫе скмтийп гшгм» 8Ьорр1пд МосШагОсй» РигсНжи «лй тсиИу пнмМ Лкй1 Вцу*ЮЬ<« МпоКдМ Мпиол Наймов» БЛа Виу ЬиеИем «мЬ вЯе СЬаг(» ВиуСЙжК ргсклде Кс*йМ Рис. 9.21. Кнопка пропуска группы для перехода в предыдущую группу
Листинг 9.41. Отрисовка ряда групп задач посредством компонента ТазкзСопЕепЕз ЕтрогЕ ТазкЬЕзЕ Егот 1 . . /ТазкЬЕзЕ' ЕтрогЕ './ТазкСопЕехЕз.сзз' ЕипсЕЕоп ТазкСопЕехЕз({ сопЕехЕз, Еазкз, оп0е1еЕе, опЕдЕЕ }) { геЕигп сопЕехЕз.тар((с) => { сопзЕ ЕазкзЕогСопЕехЕ = Еазкз.ГЕЕЕег((Е) => Е.сопЕехЕ === с.уа1ие) ЕЕ (ЕазкзЕогСопЕехЕ.ЕепдЕЬ === 0) { геЕигп <с11у с1аззМате="ТазкСопЕехЕз-сопЕехЕ">&пЬзр;</с11у> } геЕигп ( <сИх7 кеу={с.уа!ие} с1аззМате="ТазкСопЕехЕз-сопЕехЕ"> <Ъ2>{с.пате}</И2> сТазкЫзЕ Еазкз={ЕазкзЕогСопЕехЕ} оп0е1еЕе={опВеЕеЕе} опЕб1Е={опЕсИЕ} /> </<±и/> ) }) } ехрогЕ (ЕеЕаиЕЕ ТазкСопЕехЕз Каждый "сопЕех!” (т. е. группа задач для покупок 8Ьоррт§, офиса ОГЛсе, исследо- ваний КезеагсЬ и т. д.) имеет заголовок и список задач. Мы хотим, чтобы у пользо- вателя была возможность полностью пропустить любую из этих групп при пере- мещении как вперед, так и назад. Для этого мы сначала поместим каждую группу задач в оболочку нового компонента 5кЕр, как показано в листинге 9.42. Листинг 9.42. Помещение группы задач в оболочку компонента ЗкЕр | ЕтрогЕ ТазкЬЕзЕ Егот '../ТазкЬЕзЕ' ЕтрогЕ ЗкЕр Егот '../ЗкЕр' ЕтрогЕ './ТазкСопЕехЕз.сзз' ЕипсЕЕоп ТазкСопЕехЕз({ сопЕехЕз, Еазкз, опЬеЬеЕе, опЕйЕЕ }) { геЕигп сопЕехЕз.тар((с) => { сопзЕ ЕазкзЕогСопЕехЕ = Еазкз.ЕЕЕЕег((Е) => Е.сопЕехЕ === с.уа1ие) ЕЕ (ЕазкзЕогСопЕехЕ.ЕепдЕЪ === 0) { геЕигп «ЗЕу с1аззМате="ТазкСопЕехЕз-сопЕехЕ">&пЬзр;</с1Еу> } геЕигп ( <с!Еу кеу={с.Vа1ие} с1аззМате="ТазкСопЕехЕз-сопЕехЕ"> <5кЕр пате={с.пате}> <Ь2>{с.пате}</И2> <ТазкЬЕзЕ Еазкз={ЕазкзЕогСопЕехЕ}
опВе1еЁе= {опОеЁеЁе} опЕдЁЁ={опЕсИё} /> </5кЁр> </<±ГУ> } ехрогЁ йеЁаиЁЁ ТазкСопЁехЁз В результате такого размещения группы задач в (пока несуществующем) компо- ненте Бкхр при каждом входе в группу задач будет отображаться компонент ЗкЁрВиЁЁопз. Компоненту Зк1р нужно передавать только имя группы, которое будет отображать- ся в надписи кнопки "8к1р..." и "8к1р ЬеГоге...". Чтобы создать компонент зк1р, начнем с простого компонента, который отрисовы- вает два компонента зкхрвиигоп и любой переданный им дочерний компонент (лис- тинг 9.43). Листинг 9.43. Код для отрисовки компонентов Зкхрвиьъоп 1трогЁ ( изеКеЁ } Ёгот 'геасЁ' ЁтрогЁ ЗкЁрВиЁЁоп Ёгот '../ЗкЁрВиЁЁоп' ЁтрогЁ './ЗкЁр.сзз' сопзё ЗкЁр = ({ сИПскгеп, пате }) => { сопзё зЁагЁВиЁЁоп = изеКеЁ () сопзё епбВиЁЁоп = изеКеЁ() геЁигп ( «1гу с!аззКате="ЗкЁр"> <ЗкЁрВиЁЁоп ге!={ЗЁагЁВиЁЁоп}>2к1р {пате}</ЗкЁрВиЁЁоп> {скИйгеп} <Зк1рВиЁЁоп геЁ={епс1ВиЁЁоп}>ЗкЁр ЬеЁоге {пате}</ЗкЁрВиЁЁоп> </сНу> Здесь мы создали два объекта геЁ, которые позволят нам отслеживать каждую кнопку. Нажатие пользователем кнопки ЗЁагЁВиЁЁоп будет устанавливать фокус на кнопке епбВиЁЁоп, и наоборот, как показано в листинге 9.44. Листинг 9.44. Установка фокуса на кнопках въагъвиъъоп и егкЗВиЁЪоп гтрогЁ { изеКеЁ, изеЗЁаЁе } Ёгот 'геасЁ' ипрогЁ ЗкЁрВиЁЁоп Ёгот 1../ЗкЁрВиЁЁоп' ЁтрогЁ './ЗкЁр.сзз'
сопзГ 8к1р = ({ сЪНйгеп, паше }) => { сопзГ зГагГВиГГоп = изеКеГ() сопзГ епПВиГГоп = изеВеГО сопзГ зкдрАГГег = () => { 1Г (епдВиГГоп.сиггепГ) { епдВиГГоп.сиггепГ.Госиз() } } сопзГ зкгрВеГоге =()=>{ 1Г (зГагГВиГГоп.сиггепГ) { зГагГВиГГоп.сиггепГ.Госиз() } } геГигп ( <сИу с1аззМате="8к1р"> <8к1рВиГГоп геГ={зГагГВиГГоп} опСИск={зк1рАГГег}> 8к1р {паше} </8к1рВиГГоп> {сЫ1с!геп} ОкхрВиГГоп геГ={епс!ВиГГоп} опСИск={зк1рВеГоге}> 8к1р ЬеГоге {пате} </8к1рВиГГоп> </с1гу> ) В результате исполнения этого кода при входе в группу задач будет отображаться кнопка зГагГВиГГоп в начале группы. Последующее нажатие клавиши <Еп1ег> уста- новит фокус на кнопке епавиггоп в конце списка задач группы. Но вместо перехода к кнопке епавиггоп мы хотим установить фокус на следующем после этой кнопки элементе. Иными словами, мы как будто хотим перейти к кнопке в конце списка, а затем сразу же нажать клавишу <ТаЬ>, чтобы перейти к следую- щему элементу. Это можно сделать, создав функцию ГосизЫехГЕ1етепг, которая будет программно выполнять действие нажатия клавиши <ТаЬ>9. Листинг 9.45. Функция для программного нажатия клавиши <ТаЬ> сопзГ ГосизаЫе8е1есГог = ’а[Г1геГ], ..., * [сопГепГесЛГаЫе] ' ГипсГГоп ГосизКехГЕ1етепГ() { уаг ГосизаЫез = йоситепГ. диегу8е!есГогА11 (ГосизаЫе8е1есГог) уаг сиггепГ = доситепГ.диегу8е!есГогА11(':Госиз') уаг пехГТпбех = О 9 Этот подход основан на ответе пользователя Радека (ЬПр$://огеП.1у/5р8п8) на вопрос на веб-сайте $1аск- ОусгПоуу (Ы<р$://огеП.1у/1л5$В).
ее (сиггепе.1епдГЬ === 1) { Vа^ сиггепе!пс!ех = Аггау.ргоеоеуре.епс1ехОе.са11 ( еосизаЫез, сиггепе [0] ) ее (сиггепе 1пс(ех + 1 < еосизаЫез. 1епдеИ) { пехМпдех = сиггепе 1пс1ех + 1 } } еосизаЫез [пехЫпбех] . еосиз () } Этот код сначала обнаруживает все элементы в модели ВОМ, к которым можно переходить нажатием клавиши <ТаЬ>. Затем он находит в этом списке элемент, на котором в данный момент установлен фокус, а затем устанавливает фокус на следующий за ним элемент. Подобную функцию Госи5Р^еV^оизЕ1етеп1^ можно создать и для программного обрат- ного нажатия клавиши <ТаЬ> для установки фокуса на элементе, предшествующем текущей группе. Затем можно добавить наш компонент экер. Соответствующий код приведен в листинге 9.46. Листинг 9.46. Добавление функций для программного нажатия клавиши <ТаЬ> пироге { изеКее, изеЗеаее } егот 'геасе' етроге { еосизНехеЕ1етепе, еосизР^еV^оизЕ1етепе/ } {гот './еосизЫехГЕХетепГ' етроге ЗкхрВиееоп егот '../ЗкгрВиееоп' пироге 1./5к1р.сзз1 сопзе 8к1р = ({ сЫЫгеп, пате }) => { сопзе зеагевиееоп = изеВее() сопзе епбВиееоп = изеКеео сопзе зкгрАееег = ()=>{ ±е (епс!Виееоп.сиггепе) { епсЗВиееоп. сиггепе. еосиз () еосизКехеЕ1етепе() } } сопзе зкхрВееоге = О => { ее (зеагевиееоп.сиггепе) { зеагевиееоп.сиггепе.еосиз() еосизРгеуеоизЕ1етепе ()
геЁигп ( <с!гу с!аззЫате="ЗкЁр"> ОкЁрВиЁЁоп геЁ={ЗЁагЁВиЁЁоп} опС1Ёск={зкЁрАЁЁег}> ЗкЁр {пате} </8кЁрВиЁЁоп> {скЁЁйгеп) <8кЁрВиЁЁоп геЁ={епс!ВиЁЁоп} опС1Ёск={зкЁрВеЁоге}> ЗкЁр ЬеЁоге {пате} </ЗкЁрВиЁЁоп> </с!гу> При переходе в группу задач (например, ОГПсе) отображается кнопка пропуска, которая позволит нам полностью пропустить группу, переместившись на следую- щий элемент. Нам осталось добавить еще одну возможность. Кнопки пропуска группы должны отображаться только при входе в группу, но не при выходе из нее. Этого можно добиться, обновляя переменную 1пз1с1е значением, соответствующим присутствию или отсутствию фокуса на текущем компоненте, как показано в листинге 9.47. Листинг 9.47. Использование переменнойхгшхав ЁтрогЁ { изеКеЁ, изеЗЁаЁе } Ёгот 'геасЁ' ЁШрОГЁ { ЁосизКехЁЕЁетепЁ, ЁосизРгеУЁоизЕЁетепЁ, } Ёгот './ЁосизЫехЁЕЁетепЁ' ЁтрогЁ ЗкЁрВиЁЁоп Ёгот '../ЗкЁрВиЁЁоп' ЁтрогЁ './ЗкЁр.сзз' сопзё ЗкЁр = ({ сЬЁЁдгеп, пате }) => { сопзё ЗЁагЁВиЁЁоп = изеКеЁ() сопзё епсЁВиЁЁоп = изеКеЁ () сопзё [ЁпзЁде, зеЁ1пзЁс1е] = изеЗЁаЁе(ЁаЁзе) сопзё зкЁрАЁЁег = () => { ёё (епдВиЁЁоп.сиггепЁ) { епдВиЁЁоп.сиггепЁ.Ёосиз() ЁосизКехЁЕЁетепЁ() } } сопзё зкЁрВеЁоге =()=>{ ёё (ЗЁагЁВиЁЁоп.сиггепЁ) { зЁагЁВиЁЁоп.сиггепЁ.Ёосиз() ЁосизР^еVЁоизЕ1етепЁ()
геЁигп ( <сНу с1аззНате=”Зк1р" опЕосиз={(еуЁ) => { И ( е^ЛагдеЁ !== ЗЁагЁВиЁЁОп.сиггепЁ && еуЁ.ЁагдеЁ !== епйВиЁЁоп.сиггепЁ ) { зеМпзЫе (Ёгие) } Н опВ1иг={ (е\7Ё) => { ( е\7Ё. ЁагдеЁ !== зЁагЁВиЁЁоп.сиггепЁ && еуЁ.ЁагдеЁ !== епбВиЁЁоп.сиггепЁ ) { зеЁ1пз1с!е (1а1зе) } }} > <Зк1рВиЁЁоп геЁ=(зЁагЁВиЁЁоп} ЁаЫпс1ех={1пз1с1е ? -1 : 0} опС11ск={зк1рАЁЁег} > Зкхр {пате} </8к1рВиЁЁоп> {скИйгеп} <2к1рВиЁЁоп геЁ={епйВиЁЁоп} ЁаЫпс1ех={1пз1с1е ? -1 : 0} опСИск= {зкхрВеЁоге} > Зкйр ЬеЁоге {пате} </Зк1рВиЁЁоп> </сПу> ) } Теперь наш компонент для пропуска области полностью готов. При перемещении пользователя посредством клавиши <ТаЬ> в группу задач отображается кнопка пропуска области зкхрВиЁЁоп, предоставляющая пользователю возможность пропус- тить все элементы данной группы и сразу же перейти в следующую (или предыду- щую).
Обсуждение Не следуют злоупотреблять возможностью пропуска области. Она лучше всего подходит для пропуска нескольких компонентов, через каждый из которых в про- тивном случае пользователю пришлось бы перемещаться нажатием клавиши <ТаЬ> соответствующее число раз. Но можно применить и другие подходы. Предположим, например, что страница содержит последовательность заголовков и подзаголовков. В таком случае можно добавить кнопки пропуска, позволяющие пользователю переходить на следующий (или предыдущий) заголовок. Некоторые пользователи могут работать с ПО, обеспечивающим специальные воз- можности доступа, позволяющим пропускать группы и разделы компонентов, не задействуя никакого дополнительного кода в приложениях. В таких случаях кнопки пропуска не будут отображаться на странице и пользователь будет полностью их игнорировать. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу ЬИр8://огеП.1у/0СГцА. 9.8. Захват области действия в модальных окнах ЗАДАЧА Приложения Кеас1 часто открывают всплывающие окна. Например, щелчок по панели задачи в рассматриваемом в данной главе приложении для создания и редактирования заданий открывает всплывающее диалоговое окно, в котором поль- зователь может ввести подробную информацию для новой задачи (рис. 9.22). Подобные всплывающие окна часто являются модальными, и для того, чтобы воз- вратиться обратно в приложение, такое окно нужно закрыть. Проблема с пользова- тельскими модальными окнами состоит в том, что они могут терять фокус. Рассмотрим всплывающее окно для создания новой задачи нашего примера прило- жения. Ранняя версия кода приложения как раз приводила к проблеме утраты фокуса. Щелчок мышью по панели задачи открывал всплывающее модальное окно- форму для редактирования данной задачи, и фокус немедленно захватывался пер- вым полем этой формы. Но при нажатии клавиш <81нй>+<ГаЬ> (обратная табуля- ция) фокус перемещался на какой-либо элемент в главном окне приложения (рис. 9.23). Если пользователь в состоянии увидеть, что фокус ушел с формы, то он поймет, что это всего лишь немного странная особенность приложения. Но это может сильно сбивать с толку людей, работающих с ПО обеспечения доступа, которые могут ни- чего не знать о том, что модальное окно все еще открыто. Впечатление может быть еще более странным для людей, которые в состоянии видеть, но не могут пользо- ваться мышью. Они могут установить фокус на компоненте, скрытым за диалого- вым окном.
Рис. 9.22. Всплывающая форма для редактирования задачи Рис. 9.23. Переход фокуса с модальной формы на панель задачи СЬаг1з в главном окне приложения при нажатии клавиш <8ЫЙ>+<ТаЬ>
Поэтому нам нужен какой-либо способ удержания фокуса в пределах набора ком- понентов, чтобы пользователь не мог случайно переместить его на компоненты, которые по идее должны быть недоступными. РЕШЕНИЕ Для решения этой задачи мы установим библиотеку Роси 8 Ьоск для КеасТ, которая удерживает фокус в небольшом подмножестве компонентов. Библиотека устанав- ливается следующей командой: $ прт 1пзХа11 геасХ-Хосиз-1оск Библиотека Еосиз Ьоск заключает набор компонентов в оболочку компонента КеасЁГосизЬоск, который будет следить за фокусом, ожидая, когда он уйдет с него. Когда это случится, то компонент сразу же возвратит фокус обратно внутрь себя. Модальное окно в нашем примере приложения создается компонентом мода! (лис- тинг 9.48). Шистинг 9Ж Компонент мо4а1 для создания модального окна гтрогХ './Мода!.сзз’ ХипсХХоп Мода!({ ореп, опСапсе!, сЬИдгеп, го1е, }) { И (!ореп) { геХигп пи11 } геХигп ( <дгу го!е="ргезепЁаХ1оп" с!аззМате="Мода!" ...> <дгу с1аззЫате="Мода1-д!а1од" го1е={го1е) Ш1е= {ЫМе} ...> {сЫ1дгеп} </сИу> </<±И7> ) } Все содержимое модального окна передается как компоненты-потомки. Мы можем удерживать фокус в пределе набора этих компонентов-потомков, заключив их в оболочку компонента кеасгтосизьоск, как показано в листинге 9.49. Листинг 9.49. Медальное окно в оболочке гцаде&ошис* \ . порог! Веас!ГосизЪоск Хгош 'геас!-!осиз-1оск' пирог! '. /Мода!. сзз' Гипс!1оп Мода!({ ореп, опСапсе!, скНдгеп, го1е, !1!1е }) { (!ореп) { ге!игп пи11 }
геЬигп ( <сИу го1е="ргезегХаЫоп" с1аззМате="Мос1а1" ...> «11У с1а55Ыате="Мос1а1-<±1а1од" го1е={го1е} ЫЁ1е= {} ...> <Веас(; ГосизЬоск> {сЫ 1дгеп} < /КеасХ ГосизЬоск> </с11у> </(Цу> ) } Теперь при многократном нажатии клавиши <ТаЬ> в открытом модальном окне тазкгогт фокус будет последовательно перемещаться по кнопкам и полям этого диалогового окна. При нажатии клавиши <ТаЬ>, когда фокус находится на послед- нем элементе окна, он перейдет на первый элемент окна, а при нажатии клавиш <8Ый>+<ТаЬ>, когда фокус находится на первом элементе, фокус переместится на последний элемент. Библиотека Еосиэ 1_оск работает, создавая скрытую кнопку, атрибуту которой ^аЫпбех присваивается значение 1. Это нарушает правило саЫпбех библиотеки ахе-соге, гласящее, что значение этого атрибута не должно быть больше нуля. —Если в результате возникают проблемы, то данное правило можно отключить. На- пример, в сургезз-ахе можно исполнить команду су.сопПдигеАхе((ги1ез: [{ 16: ' таЫпбех', епаЫеб: Га1зе } ]}), прежде чем выполнять аудит страницы. Обсуждение В нашем примере приложения продемонстрировано пользовательское диалоговое окно, чтобы стало ясно, в чем его недостатки. Используя готовые диалоговые окна и другие компоненты из таких библиотек, как Ма1епа1 (Л, часто можно получить бесплатно многие другие возможности доступности. Кроме того, библиотеки часто создают всплывающие элементы вне пределов "корневого" раздела «Цу> приложе- ния Кеас1. После этого они присваивают атрибуту аНа-Шббеп всего "корневого" раздела <сНу> значение Ьгие, что практически скрывает все остальное приложение от средств чтения с экрана и другого ПО обеспечения доступности. Отличный пример модального компонента, удовлетворяющего требованиям стан- дартов доступности, представляет Кеас1 Мос1а1 разработки команды КеасПЗ (ЬНрз ://огеИ.1у/2п15х). Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ИПр8://оге11.1у/0СГ^А. 9.9. Создание считывателя экрана посредством ЗреесИ АР1 ЗАДАЧА Проверить доступность приложения можно с помощью множества разных инстру- ментов, но по-настоящему трудно получить представление о том, как воспринимает
ваше приложение пользователь с особыми потребностями. Поэтому наилучший подход к созданию доступного приложения — привлекать к процессу создания и тестирования кода людей, которым необходимы устройства для обеспечения дос- тупности специальных возможностей. Что касается остальных, то получить представление об использовании приложения посредством ПО для обеспечения доступности будет тем не менее полезным. Но с этим есть проблемы. Считыватели с выводом шрифта Брайля рассчитаны на уме- ние пользователя читать этот шрифт. Программное обеспечение с голосовым выхо- дом будет хорошей опцией, но большинство таких считывателей экрана довольно дорогие. Компьютеры Мас имеют встроенный считыватель экрана УоюеОуег, со- держащий множество возможностей, позволяющих перемещаться по экрану. Но компьютеры Мас есть не у всех. Браузер СЬготе снабжен хорошо работающим расширением СЬгошеУох, но оно поддерживается только этим браузером и, похоже, больше активно не разрабатыва- ется. Кроме всех этих моментов, считыватель экрана будет считывать все, что отобража- ется на экране. Например, вы можете запустить считыватель экрана лишь для того, чтобы проверить на доступность какую-либо часть своего приложения, но он будет продолжать считывать экран и после того, как вы переключитесь обратно в среду разработки или откроете какой-либо справочный материал в другой вкладке брау- зера. Учитывая все перечисленное, полезно попытаться самому испробовать звуковую версию своего приложения. По крайней мере, это даст вам определенное представ- ление о том, насколько плохо большинство из нас справляются с задачей создания программного обеспечения, с которым могут работать люди с ограниченными воз- можностями. Так как же нам можно испытать свое приложения со считывателем экрана? РЕШЕНИЕ Мы решим эту задачу, создав свой простой считыватель экрана. Очень, очень про- стой считыватель экрана. Это не будет считыватель профессионального качества, но он даст некоторое представление о том, как ваше приложение работает только с клавиатурой и голосовой обратной связью. Он также будет функционировать только с локальным приложением Кеас1, но не с другими страницами браузера или за- пущенными на компьютере приложениями. Этот считыватель называется Та1кТоМе10. Мы добавим небольшой фрагмент кода в приложение создания и редактирования задач, которое рассматриваем в качестве примера в этой главе. Мы не хотим вклю- чать код считывателя экрана в производственную версию приложения, поэтому начнем с добавления файла 1а1кТоМе.]з в главную папку приложения. Содержимое этого файла приводится в листинге 9.50. 10 Выражаем свою благодарность Терри Тиббсу (Теггу Т1ЬЬ§) за его помощь в создании этого инструмента.
। Листинг 9.50. Содержимое файла 1а1кТоМефз ГипсЁ1оп Ёа1кТоМе() { И ( ргосезз. еггу. ЬЮ0Е_ЕЫ7 !== 'ргобисМоп' && зезз1оп51огаде.деМ11ет( Ча1кТоМе') === Чгие' ) { } } Проверяя значения переменной ыобе_еыу, можно ограничить исполнение кода нашей средой разработки. Мы также проверяем переменную сессионного хранилища са1кТоМе. Мы будем исполнять считыватель экрана только тогда, когда значение этой переменной равно егие. Нам нужен код для считывания подробной информации о текущем элементе с фо- кусом. События фокуса не передаются с нижнего уровня на верхний пузырьковым способом, поэтому мы не можем просто присоединить обработчик события опгосиз к какому-либо элементу высшего уровня и начать отслеживать фокус. Но мы в состоянии отслеживать события еосиз ш. Мы можем прикрепить слушатель событий еосиз 1п к объекту йоситепе, и он будет вызываться при каждом переходе пользователя на новый компонент. Соответствующий код приведен в листин- ге 9.51. I Листинг 9.51. Добавление слушателя событий Госиз±п еипсМоп еа!кТоМе() ( И ( ргосезз. егту. ЮЖ_ЕЫ7 !== 'ргосЗисЁгоп' && зезз1оп8еогаде.де1:11:ет( ЧаХкТоМе') === 'егие' ) { йосишепе.адйЕуепеЫзТепег (' Госиз1п', (еуС) => { И (5езз1оп8еогаде.дее1ее1п('еа1кТоМе') === Чгие') { } }) Обратите внимание на то, что мы выполняем дополнительную проверку на наличие компонента еа1ктоме, просто на случай, если пользователь отключил его в процессе работы с приложением. Нам нужно каким-либо образом описывать текущий элемент, имеющий фокус. Решить эту задачу нам поможет функция деесезсг1ре1оп() (листинг 9.52), которая будет предоставлять общее описание текущего элемента на основе его названия, роли и т. п.
Листинг 9.52. Функция деШэзсгхрЪхопО ХипсХхоп деХРезсг1рХ1оп(еХетепХ) { сопзХ подеИате = еХетепХ. пойеИате. ХоИррегСазе () сопзХ го1е = е!етепХ.го1е ? еХетепХ.го!е : подеИате === 'ВиТТОИ' ? 'ЬиХХоп' : подеИате === '1ИРОТ' II подеИатпе === 'ТЕХТАКЕА' ? 'ХехХ ХХеХй ' + еХетепХ.уаХие : побеИате === 'ЗЕЬЕСТ' ? 'зеХесХ ЛеХб 1 + еХетепХ.Vа1ие : еХетепХ.деХАХХгХЬиХе('гоХе') || 'дгоир' сопзХ Х1ХХе = еХетепХ.Х1Х1е || еХетепХ.ХехХСопХепХ сопзХ ехХгаХпзХгисХхопз = побеИате === 'ХИРОТ' || побеИате === 'ТЕХТАКЕА' ? 'Уои аге сиггепХХу Хп а ХехХ ХхеХб. То епХег ХехХ, Хуре.' . I I геХигп го!е + '. ' + ХХХХе + '. ' + ехХгаХпзХгисХхопз } Теперь мы можем получить описание текущего элемента, имеющего фокус, как показано в листинге 9.53. Листинг 9153а Получение описания текущего элемента» имеющего фокус ХипсХхоп ХаХкТоМе() { И ( ргосезз.епу.ИОСЕ_ЕИУ !== ’ргобисХхоп’ && зеззхопЗХогаде.деХХХего!'ХаХкТоМе') === 'Хгие' ) ( скэсигсепХ.аббЕуепХЬхзХепег (' Госизхп', (еVX) => { хХ (зеззхоп5Хогаде.деХ1Хеш('ХаХкТоМе') === 'Хгие') { сопзХ безсгхрХхоп = деХОезсгхрХхоп(еуХ.ХагдеХ) ) }) } } Далее нам нужно преобразовать текст описания в речь. Для этого можно использо- вать АР1-интерфейс \УеЬ 8реесЬ, который в настоящее время поддерживают боль- шинство браузеров. Синтезатор речи принимает в качестве параметра объект, называющийся высказыванием (ийегапсе): кхпсХок. зреесИЗупХЬезхз. зреак ( пек ЗреесИЗупХкезхзИХХегапсе(безсгхрХхоп)
Но прежде чем начинать читать фрагмент текста, нам нужно проверить, не читаем ли мы уже что-либо другое. Если читаем, то мы отменяем предыдущий процесс чтения и начинаем читать новый текст. Это позволит пользователю быстро перехо- дить с одного компонента на другой, когда он решит, что услышал достаточно ин- формации. Соответствующий код приведен в листинге 9.54. Листинг 9.54. Отмена старого процесса чтения и начало нового 1Ё (и1пс1ои. зреескЗупЁЪезтз. зреакхпд) { И1пс1ом. зреескЗупЁкезгз. сапсе! () } мхпдои.зреесЬЗупЁИезхз.зреак( пей ЗреесИЗупЁНезхзиЁЁегапсе (дезсгхрМоп) Теперь у нас есть все необходимые составляющие для создания конечной полной версии функции Ёа1кТоМе (листинг 9.55). I Листинг 9.55. Конечная версия функции ЪаТкТоМе ЁипсЁ1оп Ёа1кТоМе() { И ( ргосезз.епу.Ы00Е_ЕЫУ !== 1 ргодисЁЁоп' && зеззтопЗЁогаде. деЁХЁет (' ЁаХкТоМе') === ' Ёгие' ) { досишепЁ.аскЗЕуепЁТДзЁепег (' ЁосизЁп', (еуЁ) => { (зезз1опЗЁогаде.деЁ1Ёет('Ёа1кТоМе') === 'Ёгие’) { сопзё дезсгЁрЁЁоп = деЁ0езсг1рЁ1оп(еУЁ.ЁагдеЁ) 1Ё (мЁпдом.зреесИЗупЁкезЁз.зреакЁпд) { нхпдои.зреесИЗупЁЪез1з.сапсе1() } н1пс1ои. зреесЬЗупЁЪез1з. зреак ( нем ЗреесЪЗупЁИезЁзиЁЁегапсе(йезсггрЁхоп) ) } }) } } Чтобы использовать функцию Ёа1кТоМе в нашем приложении, мы вызываем ее в файле т<1ех.)$ в самом начале кода приложения, как показано в листинге 9.56. Листинг 9.56. Вызов функции Ёа1кТсМэ ВПрИЛОЖвНИИ;. - ЁшрогЁ ЕеасЁ Ёгогп 'геасЁ' ЁгсрогЁ КеасЁООМ Ёгош 'геасЁ-бош' нпрогЁ './Ёпбех.сзз’
хтрогр Арр Ггогп './Арр' Хтрогк герогкИеЬУХкаХз Ггот '. /герогкИеЬУХкаХз' Хтрогк каХкТоМе Егот './каХкТоМе' каХкТоМе() КеаскООМ.гепбег( <Веаск. 8кгХскМос(е> <Арр /> </Неаск. 8кгХскМос1е>, доситепк. декЕХетепкВуХб (' гоок') ) // 1Г уои мапк ко зкагк теазигХпд регЕогтапсе 1п уоиг арр, разз а ЕипскХоп- // ко Ход гезиХкз (Гог ехатрХе: герогкМеЬУХкаХз(сопзоХе.Ход)) // ог зепс! ко ап апаХукХсз епфоХпк. Ееагп тоге: Х1ккрз://Ык.Ху/СКА-уХкаХз (Если вы хотите выполнять замеры поддерживаемых метрик производительности в своем приложении, передайте соответствующую функцию в функцию герогГИеЬУХЕаХз(), чтобы сохранить результаты в журнале (например, герогкИеЬ71ка1з(сопзоХе.Ход)), или же отправьте ее на узел аналитики(СоодХе). Дополнительную информацию см. по адресу: Иккрз://Ык.Ху/СКА-У1каХз) герогкИеЬ\71каХз () Теперь запустите наше приложение в браузере, откройте консоль разработчика, создайте новую переменную сессионного хранилища таХкТоМе и присвойте ей зна- чение кгие. В результате браузер должен голосом описывать элементы, между ко- торыми вы перемещаетесь нажатиями клавиши <ТаЬ>. Обсуждение Наш считыватель экрана каХкТоМе не более чем просто игрушка, но он поможет вам создавать краткие заголовки и другие метаданные в коде, подчеркивая, насколько важно размещать информацию в начале описаний. Чем быстрее пользователь при- мет решение, что он не заинтересован в данном элементе, тем раньше он сможет пойти дальше. Рассмотренный пример также позволит вам выяснить, какие части вашего приложения представляют трудности для навигации, и даст вам возмож- ность испытать работу вашего приложения без использования экрана. Исходный код данного рецепта можно загрузить на веб-сайте СнХНиЬ по адресу Ьир8://огеП.1у/0СГ§А.
Производительность Однажды преподаватель по информатике у одного из наших авторов начал урок с такой рекомендации: "Никогда, ни при каких обстоятельствах, ни за что в мире не оптимизируйте свой код. Но уж если вы оптимизируете свой код, то делайте это вот таким образом". Как однажды сказал Дональд Кнут, преждевременная оптимизация — это корень всего зла. Лучше всего сначала добиться правильного функционирования кода, а затем работать над тем, чтобы сделать его поддающимся обслуживанию. И только тогда — и только при наличии проблемы — следует думать о том, как ускорить работу программы. Медленный код, который работает, всегда намного лучше быстрого кода, который не работает. Сказав все это, заметим, что в некоторых обстоятельствах производительность мо- жет представлять значительную проблему. Если вашему приложению для загрузки требуется больше, чем несколько секунд, вы можете потерять некоторых пользова- телей, которые не станут ожидать так долго и уйдут со страницы, чтобы никогда больше на нее не возвратиться. На маломощных устройствах медленное приложе- ние может превратиться в такое, с которым просто невозможно работать. В этой главе применяется, как мы его любим называть, эссенциалистский подход к произ- водительности. Не следует часто выполнять настройку кода, но когда такая проце- дура выполняется, то только для кода, для которого она требуется. Мы рассмотрим различные инструменты и методы, которые позволят вам обнаруживать и оцени- вать узкие места в производительности, чтобы при необходимости исправить про- блемы производительности, эти корректировки окажутся в правильном месте, и у вас будет какой-либо способ для измерения разницы в производительности, по- лученной вследствие этих исправлений. Все исправления проблем производительности имеют свою цену. Если вы хотите ускорить исполнение кода клиента, это может стоить вам дополнительной памяти или серверного времени. И почти всегда придется увеличивать объем кода и его сложность. Рецепты в этой главе следуют в том порядке, в котором мы рекомендуем подходить к решению проблем производительности. Мы начинаем с высокоуровневых изме- рений в браузере, и рассматриваем способы, посредством которых можно объек- тивно идентифицировать узкие места производительности. Для обнаруженных узких мест мы покажем, как использовать встроенный в КеасТ компонент РгоШег для локализации отдельных компонентов, являющихся причиной проблемы. Затем
мы рассмотрим низкоуровневые и более точные способы измерения производи- тельности вплоть до величин меньше одной миллисекунды. Только тогда, когда вы сможете точно измерять производительность, можно начи- нать думать о повышении скорости работы своего кода. Затем мы покажем вам всего лишь несколько способов, которые могут улучшить производительность ваших приложений. Некоторые из них простые, например, разделение кода на пакеты меньшего размера или объединение асинхронных сетевых вызовов. Другие же более сложные, например предварительная отрисовка страниц на сервере. В общем, эта глава намного больше об измерении производительности, нежели о настройке производительности. Поскольку вы никогда, ни при каких обстоятель- ствах, ни за что в мире не должны оптимизировать свой код, но когда оптимизи- руете, то нужно начинать с ее измерения. 10.1. Браузерные средства настройки производительности ЗАДАЧА Целесообразно повременить с настройкой производительности до тех пор, пока не будет видно, что в этой области есть проблема. Проблема имеется только в том случае, если пользователь видит, что приложение не работает должным образом. Но если откладывать решение проблемы до тех пор, пока пользователь заметит ее, то решение может быть слишком запоздалым. Поэтому желательно иметь опреде- ленный объективный критерий, показывающий, когда приложение нуждается в на- стройке, нечто, что реалистически измеряет производительность, а не просто опре- деляет код, функционирование которого можно ускорить. Ускорить исполнение кода можно всегда, и разработчики зачастую тратят многие часы на настройку кода, не получив в результате никакого ощутимого эффекта с точки зрения вос- приятия пользователя. Было бы полезным иметь в своем распоряжении какой-либо инструмент, способ- ный выделять фрагменты кода, потенциально требующие оптимизации. РЕШЕНИЕ Наилучший способ проверить производительность — прибегнуть к помощи брау- зера. В конечном итоге единственное, что важно для нас, — это восприятие прило- жения пользователем. Поэтому мы рассмотрим разные браузерные инструменты, способные предоставить объектные измерения и обнаружить потенциальные узкие места в коде. Первым из таких инструментов мы рассмотрим инструмент браузера СЬготе, на- зывающийся Ы^ЬЙюияе.
г Компания Соод1е также предоставляет встроенный модуль оценки производитель- ности для браузера НгеТох. Этот инструмент называется 6оод1е идЫЬоиэе. Хотя данный инструмент хорошо работает, это просто клиентская часть службы 6оод1е X-л Раде 8реес1, и поэтому его можно использовать только на общедоступных веб- страницах. Но расширение ЫдМЬоизе браузера СЬготе пригодно для любой стра- ницы, которую браузер может прочитать. Расширение Ы^ЬЙюизе — это отличное средство для проверки базовой пригодно- сти вашего приложения. Кроме проверки производительности веб-страницы этот инструмент также проверяет ее доступность и соблюдение передовых практик в области веб-разработки. В частности, он проверяет оптимизацию страниц для роботов поисковых систем, а также соответствие веб-приложения требованиям стандартов, чтобы считаться прогрессивным веб-приложением (рис. 10.1). Проверку посредством Ы^Ьсизе можно выполнять двумя способами: или из командной строки, или в браузере. Для работы в командной строке, сначала необходимо установить инструмент ЫдЪЁЬоизе: $ прт 1пзЁа11 -д ИдЫЪоизе Затем можно выполнять проверку: $ ИдЪЬЬоизе Ш:р://1оса1Ьо5Ё:3000 Версия командной строки средства Ы^ЬЙюизе — это просто автоматизированный сценарий для браузера 6оо§1е СЬготе. Ее преимущество состоит в том, что она создает НТМЬ-отчет о проверке, что позволяет использовать ее на сервере непре- рывной интеграции. Со средством Ы^ЫЬоизе можно также работать интерактивно в браузере Соо§1е СЬготе. Делать это лучше всего в окне инкогнито, чтобы снизить вероятность вмешательства в работу Ы^ЬЙюизе других расширений и хранилищ. Запустите браузер СЬготе, откройте в нем свое приложение, откройте окно Инструменты разработчика, а в нем выберите вкладку ЬщЫЬоизе (рис. 10.2). На вкладке ЬщЫЬоизе нажмите кнопку Сенегале аисКС Средство Ы§Ь1Ьоизе нач- нет выполнять ряд проверок страницы, обновляя ее несколько раз в процессе рабо- ты. Аудит производительности концентрируется на шести метриках (рис. 10.3).
• • йшЬНДрр Ж • ♦ <7 СОФ х-лм * ’ О I Уеаг: 2023 РтвуЬш | Нде 8Ш» ЕаНг«1ог*« МаггН &мгааа Минии м*пау *яз*сХ№п &волу Цгпш АгхгИ Тг-"-».-'^ап йепа^г ялб 1ь ахтипоп ргпойт* яйеа уах »* рвЛхпчгс* ям) гл#г *ф*г№ЧХ имт того СяШДОм ОаМС* а омл** О Рпф«ым Мь Арр • Омлор С Вт доскм 9 АСММСИНУ П6ЕО Ссппишу РЦдо (ЬО) ИЛ*МЖА<И Рис. 10.2. Вкладка ЫдЫЬоизе окна Инструменты разработчика браузера СЬготе Эти метрики называются основными показателями веб-производительности (\\еЬ уйак). Они позволяют отслеживать производительность приложений при их ис- полнении в рабочем режиме. ♦ Г1г«1 СопТепШП Раш1, ЕСР (Первое отображение содержимого). Время, требуе- мое браузеру, чтобы начать отображать содержимое страницы после открытия ее пользователем. Метрика ЕСР сильно влияет на восприятие производительно- сти пользователем. До начала отображения содержимого виден только пустой экран, и если это длится слишком долго, пользователь может закрыть эту стра- ницу и перейти на какую-либо другую. Средство Ы^ЬЙюизе измеряет время, истекшее до ЕСР, а затем сравнивает полу- ченное значение с соответствующими глобальными статистическими оценками производительности, сохраненными Соо§1е. Если данный показатель для вашего приложения находится в верхних 25% глобальных значений ЕСР, то он будет
зеленого цвета. В настоящее время ранжирование зеленым цветом означает, что первое содержимое отображается в течение двух секунд. Показатели в верхних 75% глобальных значений выделяются оранжевым цветом, такие страницы начинают отображаться в течение четырех секунд. Все другие значения этого показателя отображаются красным цветом. ♦ 8рее<11т1ех, 81 (Показатель быстродействия). Время, требуемое для визуальной стабилизации страницы. Тест выполняется путем записи видео загрузки страни- цы и проверки на наличие отличий между кадрами. Метрика 81 сравнивается с соответствующими глобальными статистическими показателями. Значение 81 до 4,3 секунды будет в верхних 25% глобальных по- казателей и отображается зеленым цветом, а до 5,8 секунды будет в верхних 75% и выделяется оранжевым цветом. Все другие значения этого показателя отображаются красным цветом. ♦ Ьаг^еяС Соп<еп1Ги1 Рат<, ЬСР (Отображение крупнейшего элемента). Событие полной загрузки поля вывода браузера. При этом другое содержимое может за- гружаться в скрытом режиме, но ЬСР происходит, когда страница будет казаться видимой пользователю. Значения ЬСР до 2,5 секунды отображаются зеленым цветом, а до четырех секунд — оранжевым. Все остальные значения выделяются красным цветом. Отрисовка на стороне сервера может значительно улучшить рейтинг ЬСР. ♦ Типе Со 1п1егасС1Уе, ТТ1 (Время до интерактивности). Время, требуемое, чтобы со страницей можно было взаимодействовать посредством мыши и клавиатуры. В приложениях КеасС это происходит после первой полной отрисовки, после присоединения КеасС обработчиков событий. Для "зеленого" рейтинга этот пока- затель должен быть не большей 3,8 секунды, а для "оранжевого" — не больше 7,3 секунды. Все остальные значения получают "красный" рейтинг. Показатель ТТ1 можно улучшить, отложив загрузку стороннего кода Дауа8спр1 или выпол- нив разделение кода1. ♦ ТоСа! В1оск11щ Типе, ТВТ (Общее время блокировки). Сумма времени всех бло- кирующих задач, происходящих между событиями ЕСР и ТТЬ Блокирующей за- дачей считается все, что занимает свыше 50 мс. Это приблизительно время, тре- буемое для отображения кадра кинофильма, и все, что длится больше 50 мс, становится заметным. Если приложение имеет слишком много блокирующих задач, браузер начнет тормозить. По этой причине показатель ТВТ измеряется короткими периодами. Для "зеленого" рейтинга значение показателя ТВТ долж- но быть не больше 300 мс, для "оранжевого" — не больше 600 мс, а все свыше будет красным. При высоком значении показателя ТВТ пользователь будет ис- пытывать ощущение, как будто бы браузер перегружен. Показатель ТВТ обычно можно улучшить, уменьшив объем исполняемого кода Зауа8спр1 или сократив количество сканирований модели ПОМ. Но наиболее эффективным методом будет, наверное, разделение кода. 1 См. раздел 10.5.
♦ СшшПайуе Ьауои< 8ЫГ(, СЬ8 (Совокупное смещение макета) Измерение сме- щения содержимого или визуального оформления страницы. Если во время загрузки страницы приложение вставляет дополнительное содержимое в уже загруженное содержимое, вызывая тем самым смещение части загруженного со- держимого, то это будет отражаться на метрике СЕ8. Показатель СЬ8— это пропорциональная часть страницы, которая смещается в процессе загрузки. В отчет ЬщЫЬоизе не входит показатель Е1г$11при1 Пе1ау, ЕГО (Время ожидания до первого взаимодействия с содержимым). Этот показатель измеряет время между отправкой пользователем события странице (например, щелчок мышью) и получе- нием этого события обработчиком .1ауа8спрЕ Желаемое значение данного показа- теля не должно превышать 300 мс. Показать ЕГО тесно связан с показателем ТВТ, поскольку блокирующие события обычно создаются обработчиками событий. Кроме значений основных метрик страницы отчет 1л@ЫЬои$е также содержит рекомендации, как исправить обнаруженные им проблемы. Использование Ь1§Ь1Ьоизе будет хорошей начальной точкой проверки разрабаты- ваемого приложения на наличие проблем производительности. Он не выполняет исчерпывающую проверку, но укажет на проблемы, которые вы сами могли бы и не заметить. /"•к На результаты работы ЫдЫбоизе могут воздействовать многие факторы (пропуск- ная способность сети, объем памяти, центральный процессор и т. п.), поэтому сле- дует ожидать варьирующиеся результаты от одного прогона к другому. Онлайно- —X- вые службы, такие как \Л/еЬРадеТе5( (НЦр5://\лплдл/.\л/еЬраде4е51.огд) или 6Тте1пх (ИКрз://д1те1пх.сот), могут выполнять проверку приложения с разных местона- хождений, что позволит получить более реалистичное представление о скорости работы вашего приложения, чем предоставляемое идМНоизе, выполняющим про- верку по адресу М(р://1оса1Ко81:3000. Инструмент Ы^Мюизе хорошо справляется с обнаружением проблем производи- тельности, но выявить причины этих проблем способен в меньшей степени. А эти причины могут быть разными: чрезмерный объем кода страницы или его слишком медленное исполнение, медленно отвечающий сервер, или даже проблема с аппа- ратными ресурсами, такая как недостаточный объем памяти или большой объем кеша. Чтобы выяснить причину узкого места, на следующем шаге можно прибегнуть к средствам производительности самого браузера. В браузерах ЕпеГох и СНготе консоль производительности можно открыть, открыв исследуемую страницу в окне инкогнито, а затем выбрав вкладку РегГогтапсе в панели инструментов разработчика (рис. 10.4). Вкладку РегГогтапсе можно сравнить с системой управления двигателем для брау- зера. На ней можно отслеживать использование памяти, блокирующее центральный процессор задачи, число элементов в модели НОМ и т. п. Для сбора статистических данных по производительности страницы нужно нажать кнопку Кесогг! на панели инструментов, а затем взаимодействовать со страницей в течение нескольких секунд, после чего остановить запись. Система тестирования производительности
выполнит оценку всех указанных вами составляющих. В примере на рис. 10.5 пока- зано возникновение блокирующей операции (см. описание показателя ТВТ ранее в этом разделе) при нажатии кнопки пользователем, и браузер был заблокирован в течение 60,92 мс, пока обработчик события не возвратил управление. Рис. 10.4. Вкладка РегТогтапсе панели Инструменты разработчика браузера Рис. 10.5. Увеличение масштаба, чтобы исследовать долго исполняющуюся задачу Вкладка РегГогтапсе предоставляет все статистические данные, которые вам когда-либо могут потребоваться при настройке производительности. Более того, она, вероятно, содержит намного больше подробной информации, чем вам потребу- ется. По этой причине целесообразнее установить набор инструментов Кеас1 Оеуе1орег Тоо1§ для СЬготе (ЬПр$://оге11.1у/у\СЕр) или НгеГох (Ы1р8://огеП.1у/туу1уп). Может случиться, что после установки эти инструменты по умолчанию не работа- ют в режиме инкогнито. Будет полезным разрешить этот режим, как показано на рис. 10.6 для СЬготе и на рис. 10.7 для РкеГох.
АНо»1п1псодпЙо УУаттд: Соод1е СЬготе саппо! ргеуеп! ех№лбюп5 (гот гесопИпд уоиг Ьгож&тд Ызюгу. То скзаЫе (Ьш ейелшоп !п 1псодпйо то<1е, ипзе1ес1йнв орЬол. Рис. 10.6. Разрешение режима инкогнито для набора Оеуе1орегТоо1з в СЬготе Аип !п Рпуа1в МпНомк О Айои ф Ооп'1 А11о» УУЪвп аИоиж!' №в ех1впшол мН Ьау* ассежв (о уоиг опПп* всГмЬев »Ы1в рпуа1а Ьгошя1пд. 1_еат тоге Рис. 10.7. Разрешение режима приватности для набора Оеуе!орегТоо1з в СЬготе Подобно инструментам оценки производительности браузера в наборе Кеас! Веуе1орег Тоо1§ для записи сеанса производительности нужно нажать кнопку Кесогй в левом верхнем углу панели инструментов (рис. 10.8). ш/ доо X Рис. 10.8. Вкладка Кеас1 РгоЛ1ег в наборе Оеуе1орег Тоо1з для СЬготе После записи сеанса отображаются статистические данные по производительности, а также связанные с Кеас! компоненты, которые отрисовали веб-страницу. Если отображение какого-либо компонента заняло длительное время, то выяснить, какой это компонент, можно, наведя в графе результатов оценки производительности курсор мыши на его данные, в результате чего соответствующий компонент будет выделен (рис. 10.9). Во многих случаях набор инструментов Кеас! Пеуе1орег Тоо1§ оказывается наилуч- шим интерактивным средством для определения причины проблемы производи- тельности. Но, как уже упоминалось ранее, рассматривать возможность настройки
производительности следует только при обнаружении узкого места в производи- тельности пользователем или каким-либо высокоуровневым инструментом наподо- бие [л^Мюизе. Рис. 10.9. Наведение курсора мыши на данные компонента в графике результатов оценки производительности выделяет этот компонент Обсуждение В случае применения эссенциалистского подхода к настройке производительности всегда следует начинать с браузера, или используя само приложение или какой- либо из встроенных инструментов и расширений, рассмотренных ранее в этом раз- деле. 10.2. Слежение за отрисовкой посредством Рго/Пег ЗАДАЧА Браузерные инструменты предоставляют подробную информацию о производи- тельности, и их нужно всегда задействовать на первом этапе при определении при- чин проблем производительности. После определения проблемы полезно получить более подробные статистические данные для небольшой части приложения. Единственный способ повысить произ- водительность — сбор фактических данных производительности до и после моди-
фикации кода. Это может быть трудно реализовать при помощи браузерных рас- ширений, поскольку они "заваливают" данными обо всех аспектах работы прило- жения. Как же нам получить статистические данные только для той части приложения, на- стройку которой мы выполняем? РЕШЕНИЕ Для решения сформулированной задачи можно использовать компонент Кеас! РгоШег. В этот компонент можно заключить любую часть приложения, для кото- рой выполняется настройка. После этого он будет записывать статистические дан- ные при каждой его отрисовке. В табл. 10.1 приведен список и краткое описание собираемых таким образом важных данных. Таблица 10.1. Данные, записываемые компонентом Рго/Иег Показатель Назначение РЬазе (Этап) Что вызвало отрисовку монтирование или обновление Ас1иа1 бигайоп (Фактическая длительность) Сколько времени заняла бы полная отрисовка, если бы не применялось внутреннее кеширование Вазе сЮгайоп (Базовая длительность) Сколько времени заняла отрисовка с применением кеширования 81аЛ Игле (Время начала) Число миллисекунд, истекших после загрузки страницы Соттй йте (Время фиксации) Когда результаты отрисовки прибывают в модель СОМ браузера 1п1егасйопз (Взаимодействия) Обработчики событий, отслеживаемые в настоящее время Чтобы разобраться с работой компонента РгоШег, начнем с рассмотрения примера приложения, окно которого приведено на рис. 10.10. В листинге 10.1 приведен код компонента Арр этого приложения. Листинг 10.1. Код компонента Арр примера приложения Са1епс1аг ЁтрогЁ { изеЗЁаЁе } Ёгот ' геасЁ' ЁшрогЁ { ипзЁаЫе_Ёгасе аз Ёгасе } Ёгот ’ зскескЯег/ЁгасЁпд' ЁтрогЁ './Арр.сзз1 ЁипсЁЁоп Арр ({ опНепдег }) { сопзё [уеаг, зеЁУеаг] = изеЗЁаЁе(2023) геЁигп ( <с11V с1аззЫате="Арр"> <Ь1>Уеаг: {уеаг}</Ъ1> <ЬиЁЁоп опСИск={ () => зеЁУеаг((у) => у - 1) }>РгеуЁоиз</ЬиЁЁоп> <ЬиЁЁоп опС11ск={() => зеЁУеаг((у) => у + 1)}>МехЁ</ЬиЁЁоп> <Ьг />
<УеагСа1епс1аг уеаг=(уеаг} опНепсЗег={опКепс1ег) /> </сНу> ) } ехрог{ сЗеРаиЮ Арр Рис. 10.10. Окно примера приложения Са!епбаг Наше приложение отображает две кнопки: одна для прямого перемещения по го- дам, а вторая — для обратного. Сначала заключим эти две кнопки и компонент календаря в оболочку компонента РгоШег, как показано в листинге 10.2. Листинг 10.2. Заключение кнопок в оболочку компонента РгоЯХег ипрогТ { изеЗ{аЗ:е, РгоГПег } {гот 'геасЗ:' 1трог{ { ип5{аЫе_{гасе аз {гасе ) Ггот 'зсЬесЗиРег/ОгасУпд' строги './Арр.сзз' {ипсРхоп Арр ({ опКепсЗег }) { сопзО [уеаг, зеОУеаг] = изеЗОаОе(2023) геРигп ( <с!гу с1аззМате="Арр"> <Ы>Уеаг: {уеаг}</И1> <РгоРИег 1с1="арр" опКепс1ег={ () => {})> <Ьи{Ъоп опСИск={() => зеЁУеаг((у) => у - 1) }> Ргеухоиз </Ьи00оп>
<ЬиЁЁоп опСИск={ () => зеЁУеаг((у) => у + 1) }>ЫехЁ</ЬиЁЁоп> <Ьг /> <УеагСа1епс1аг уеаг={уеаг) опНепс1ег={опНепс1ег} /> </РгоШег> </(±1Л7> ) } ехрогр беЁаиЁЁ Арр Компонент РгоШег принимает в качестве параметров переменную Ёд и функцию обратного вызова опкепдег. При каждой отрисовке компонента РгоШег он отправля- ет статистические данные функции опкепйег. Поэтому добавим еще подробностей о функции опНепйег (листинг 10.3). Листинг 10.3. Дополнительные подробности о функции оп»«гк1вг ЁтрогЁ { изеЗЁаЁе, РгоШег } Ёгот 'геасЁ' ЁтрогЁ { ипзЁаЫе_Ёгасе аз Ёгасе } Ёгот ' зскедиЁег/ЁгасЁпд' ЁтрогЁ './Арр.сзз' 1еЁ гепбегз = [] 1еЁ Ёгаскег = ( 1д, рказе, асЁиаЁБигаЁЁоп, ЬазеВигаЁЁоп, зЁагЁТйпе, соготЁЁТЁте, ЁпЁегасЁЁопз ) => ( гепдегз.ризЪ({ 1(1, рказе, асЁиаЮигаЁЁоп, ЬазеШгаЁЁоп, зЁагЁТйпе, соттЁЁТЁте, ЁпЁегасЁЁопз: ЗЗОЫ.зЁгЁпдЁЁу(Аггау.Ёгот (ЁпЁегасЁЁопз)), }) ) ЁипсЁЁоп Арр({ опКепдег }) { сопзё [уеаг, зеЁУеаг] = изеЗЁаЁе(2023) геЁигп ( <сИу> <РгоШег 1с1="арр" опКепс!ег={ Ёгаскег}> </РгоШег>
<ЬиГ1оп опСИск={() => сопзо1е. 1аЫе (гепбегз) }>ЗЁаЁз</Ьи1Лоп> </сНу> Функция ггаскег записывает все результаты, получаемые от компонента РгоШег, в массив гепбегз. Мы также добавили в интерфейс кнопку $1аЬ, нажатие которой будет отображать в консоли таблицу параметров отрисовки. Перезагрузим страницу и нажмем несколько раз кнопки Ргеуюиз и Мех1, а затем кнопку В результате в консоли отобразятся статистические данные профиля (рис. 10.11). Рис. 10.11. Статистика отрисовок, выводимая в консоль ^уа8спр1 Эти данные выводятся в табличном формате, что немного облегчает работу с ними, а также позволяет упорядочивать их по любому столбцу. Можно скопировать всю информацию и вставить ее в электронную таблицу для дополнительного анализа. Обратите внимание на то, что столбец хп^егасЁхопз всегда содержит пустой массив. Это объясняется тем, что в настоящее время мы не отслеживаем никаких обработ- чиков событий или других фрагментов кода. Чтобы увидеть, какие обработчики событий исполняются в процессе отрисовки в настоящее время, можно импортиро- вать функцию отслеживания и облачить в нее любой фрагмент кода, который нуж- но отслеживать. Например, в листинге 10.4 показано, как можно начать отслежи- вать событие нажатия пользователем кнопки Ргеуйшя. Листинг 10.4. Код для отслеживания события нажатия кнопки Ргеуюиа ипрогЁ { ипз^аЫе_11гасе аз 1гасе } Г г от 'зсИес1и1егАгас1пд' <ЬиЫ:оп опСИск={ () => { Ггасе(,р^еV^оиз Ьи^оп сИск', регЕогтапсе.пом(), () => {
зе<:Уеаг((у) ~> у - 1) }) }} /> Функция ггасе принимает в качестве параметров метку, отметку времени и функ- цию обратного вызова, содержащую отслеживаемый ею код. Параметр отметки времени мог бы быть датой, но часто будет лучше задать значение в миллисекун- дах, возвращаемое функцией регГогтапсе. пом (). Перезагружаем страницу, нажимаем несколько раз кнопку Иех1, а затем кнопку Ргеу10И8. В таблице результатов должны начать отображаться взаимодействия (пйегасйопз) в виде строк (рис. 10.12). (И ® 1 юр V 0 Нйег СМаиН 1ече!з ▼ 11ззие; О 1.Ыт1:3 (1п- 1Й рПазе асХиаЮиг- ОавеОигаИоп 51агХТ1те сотт11Т1те 1ХегасХ1оп5 0 1 “тоипХ" 447.54999- 234.53534542685 1333.65466546- 1811.765745675473 " Н\н_СОипХ\": 1, \"1б\": 0_ 1 1 "ирбахе" 153.98661- 87.767252662362 2433.65464562- 2478.7657657453 " 1 Л"_соипХ V: 1, М\": 0- 2 1 "ирбахе" 115.54968- 71.543256572268 2565.65464642- 2579.7657564743 ” Н\"_соилх\": 1, \"1б\”: О- 3 1 "ирбаХе" 134.98661- 128.65463563466 2954.66546642- 3014.765765453 " Н\“_соипХ\”: 1, \" 1Й\ " 10- 4 1 “ирбаХе" 79.654634- 54.254252345685 3100.56476632- 3145.7654746743 “ I <\"_соип Х\": 1, \" 16\": 0- 5 1 "ирбаХе" 131.86876- 23.534636564656 3400.63654645- 3478.765745743 Н\"_соипХ\":1,\И1б\”:0- 6 1 “ирбахе" 66.999866- 43.68554325235 3601.87687682- 3654.76576547543 " [ {\“ соипХ\":1,1б\":0_ 7 1 "ирбаХе” 105.61646- 100.535425685 3712.78782349- 3717.765765473 ” Н\,,_мипх\" :1,\И16\":0_ 8 1 "ирбаХе” 61.543258- 42.653523453245 3922.87687872- 4014.745677453 ” ({\"_соипХ\": 1, V 1б\“: О- • 1 "ирбаХе" 66.549998- 60.532523543268 4398.87876867- 4432.6546453653 “[<\“ соипХХ":1,\"1б\“:0- 10 1 "ирбаХе" 67.667854- 51.232543526585 5108.87878768- 5210.3654654334 "Н\" соипХ\":1.\”1б\”:0_ Рис. 10.12. Отображение отслеживаемых взаимодействии в таблице результатов Выходные данные преобразуются в строки по той причине, что функция огасе сохраняет взаимодействия в виде множеств 5ауа8спр(, которые часто не отобража- ются правильно в консоли. Хотя в таблице данные взаимодействий выглядят обре- занными, в действительности это не так, и при копировании можно получить пол- ные данные. В листинге 10.5 приведен пример данных, возвращенных одним отслеженным взаимодействием. Листинг 10.5. Данные одного отслеженного взаимодействия "_соипТ:": 1, "Щ" :1, "пате" : "ргеухоиз ЬиОРоп сИск", "кнпезкатр":4447.909999988042
Обсуждение Компонент РгоШег был добавлен в Кеас( в версии 16.4.3. Функция ыасе все еще экспериментальная, но крайне мощная. Хотя в данном примере мы использовали ее лишь для простого обработчика события, она также может предоставлять времен- ные данные для более крупных фрагментов кода, например для сетевых запросов. Контейнерные компоненты Кеас1 в процессе отрисовки часто имеют большое число сетевых запросов "на лету", и функция ггасе предоставляет возможность увидеть, что происходило во время особенно медленной отрисовки. Она также может дать некоторое представление о количестве отрисовок, полученных из целой цепи раз- ных сетевых процессов. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу 1Шр8://оге11Лу/06Г§А. 10.3. Создание модульных тестов с РгоН1ег ЗАДАЧА Компонент Кеас! РгоШег — это мощный инструмент. Он обеспечивает разработ- чику доступ к той же самой информации о ходе выполнения программы, которую предоставляет набор инструментов Кеас1 Оеуе1орег Тоо1з. Преимущество РгоШег состоит в том, что разработчик может сконцентрироваться на определенном коде, который он пытается оптимизировать. Но данный компонент все равно полагается на ручные взаимодействия разработчи- ка с веб-страницей. Производительность нужно измерять до и после модификации кода. Но как можно быть уверенным в том, что показания до и после модификации отображают те же самые характеристики? Если мы осуществляем тестирование вручную, как можно гарантировать, что каждый раз мы будем выполнять одинако- вый набор действий? РЕШЕНИЕ В этом рецепте мы рассмотрим, как создавать модульные тесты, которые вызывают код с компонентом РгоШег. Автоматизированные тесты позволят нам выполнять повторяемые проверки производительности, чтобы выяснить, насколько эффектив- ными в плане производительности являются вносимые нами правки в код. В модульном тесте мы можем отрисовать компонент КеасТ вне браузера благодаря предоставляемой библиотекой Те§1т§ ЫЬгагу "безголовой" реализации модели ООМ. Чтобы разобраться с использованием компонента РгоШег, мы опять обратимся к примеру приложения календаря (рис. 10.13). Мы можем добавить компонент РгоШег в основной код компонента Арр нашего приложения, а затем позволить любому другому коду передавать функцию опНепбег,
посредством которой можно отслеживать производительность отрисовки. Соответ- ствующий код приведен в листинге 10.6. Листинг 10.6. Добавление компонента РгоШег в код компонента Арр ЁгпрогЁ { изеЗЁаЁе, РгоЁНег } Ёгош 'геасЁ' ЁтрогЁ УеагСа1епс1аг Ёгот './УеагСаЁепдаг' йпрогЁ { ипзЁаЫе_Ёгасе аз Ёгасе } Егот 'зскес1и1ег/Ёгас1пд' ЁтрогЁ ’./Арр.сзз’ ЁипсЁЁоп Арр ({ опКепдег }) { сопзё [уеаг, зеЁУеаг] = изеЗЁаЁе(2023) геЁигп ( <сНу с1аззЫате="Арр"> <Ъ1>Уеаг: (уеаг)</М> <РгоШег 1(1=’,арр" опКепйег={опКепс1ег || (() => {})}> <ЬиЁЁоп опСИск={ () => { Ёгасе('ргеуЁоиз ЬиЁЁоп сИск', регГоппапсе.помО , () => { зеЁУеаг((у) => у - 1) )) )} Р^еV^оиз </ЬиЁЁОп> <ЬиЁЁоп опСИск={() => зеЁУеагЦу) => у + 1) }>ИехЁ</ЬиЁЁоп> <Ьг /> <УеагСа1епс1аг уеаг={уеаг) опРепс1ег={опКепс1ег} />
</РгоШег> </сНу> ) } ехрогр с1е1аи1Т Арр Мы также можем передавать функцию опРепбег вниз по иерархии компонентам- потомкам, чтобы отслеживать их производительность отрисовки. В коде в листин- ге 10.6 мы передаем функцию опКепбег компоненту УеагСа1епбег, который затем мо- жет или задействовать ее в своем компоненте РгоШег, или передать ее далее вниз по дереву компонентов. Можно избежать необходимости передавать функцию опКепбег компонентам- потомкам, создав компонент-поставщик, который будет инжектировать эту функ- цию в текущий контекст. В данном случае мы этого не делаем, чтобы не усложнять код. Но в других разделах в книге есть несколько примеров использования по- ставщиков. Например, компонент-поставщик ЗесиШуРгоуЫег в разделе 7.1. Компоненту РгоШег необходимо передать свойство 16 и свойство опКепбег. При нормальном исполнении приложения компоненту Арр свойство опКепбег не переда- ется, поэтому нужно предоставить функцию по умолчанию: <РгоШег 1б='арр' опКепбег={опКепбег || (О => {})}> Компонент РгоШег сравнительно легковесный и обычно не замедляет производи- тельность приложения. Если забыть удалить данный компонент из кода, то это не I будет иметь большого значения. Компонент РгоШег используется только в режиме разработки. Он удаляется из кода при создании рабочей версии прило- жения. Теперь можно приступать к созданию модульного теста (листинг 10.7). | Листинг 10.7. Код модульного теста 1трогГ { гепбег, зсгееп } Г г от ' @ТезТ1пд-ИЬгагу/геасГ:' нпрогТ изег ^гот ’ @1:е5Т1пд-11Ьгагу/изег-еуеп1:1 бтрогТ Арр У г от '. /Арр1 1е1 гепбегз = [] 1еТ Тгаскег = ( рЬазе, асТиа1РигаТ1оп, Ьазе0игаУ1оп, зТагТИте, сопипхТТгте, хпТегасМопз ) => { гепбегз.ризк({ 16,
рЬазе, асЁиаЮигаЁЁоп, ЬазеЬигаЁЁоп, зЁагЁНте, согпго1ЁТ1те, 1пЁегасЁ1опз: ЛЗОМ. зЁгхпдИу (Аггау. 1гот(1пЁегасЁ1опз)), }) } 1еЁ зЁагЁТЁте = О безсгхЬе(’Арр', () => { ЬеГогеЕасИ(() => { гепёегз = [] зЁагЁТ1те = регЬогтапсе.пон() }) аЁЁегЕас±1(() => { сопзо1е. 1од('Типе Ёакеп: рег1огтапсе.пои() - зЁагЁТЬте) сопзо1е. ЁаЫе (гепЬегз) }) 11; (' з1юи1с1 тоуе ЬеЁыееп уеагз', азупс () => { гепс!ег(<Арр опКепс!ег={Ёгаскег} />) изег.сИск(зсгееп.деЁВуКо1е('ЬиЁЁоп', { пате: /ргеуЁоиз/Ё })) изег.сИск(зсгееп.деЁВуКо1е ('ЬиЁЁоп', { пате: /р^еV^оиз/^ })) изег .сИск(зсгееп.деЁВуВо1е ('ЬиЁЁоп', { пате: /р^еV^оиз/^ })) изег .сИск( зсгееп. деЁВуВоЁе ('ЬиЁЁоп', { пате: /пехЁ/1 })) изег .сИск(зсгееп.деЁВуКо1е ('ЬиЁЁоп', { пате: /пехЁ/1 })) изег .сИск(зсгееп.деЁВуВо1е ('ЬиЁЁоп', { пате: /пехЁ/1 })) }, 30000) }) Тесты длительностью свыше пяти секунд, скорее всего, будут превышать предел тайм-аута ЗезЁ Чтобы избежать этого, проще всего добавить параметр тайм-аута в вызов Функции 1ё и присвоить ему значение 30 000 мс (как это делаем мы в при- X- веденном коде). Это значение нужно будет корректировать в соответствии со сложностью теста. При исполнении этого теста в консоли отображается громадный объем данных (рис. 10.14). Данный тест отличается своей повторяемостью, т. е. при каждом прогоне он будет выполнять одинаковые действия. Мы обнаружили, что модульные тесты, как пра- вило, более единообразны, чем код, исполняемый в браузере. При многократном прогоне нашего теста время исполнения каждого прогона было 21 000±20 мс. Это разброс величиной менее чем 1%. И каждый прогон создал точно 2653 оценки про- филя приложения. Вряд ли бы мы получили повторяемые результаты при ручном тестировании в браузере. В рассмотренном примере мы просто отображаем захваченные данные. При на- стоящем тестировании производительности лучше обработать результаты каким-
либо образом, чтобы, например, узнать среднее время обработки определенного компонента. Затем, когда вы начнете выполнять настройку этого компонента, мож- но быть более уверенным в том, что любые улучшения производительности полу- чены вследствие фактических модификаций кода, а не в результате вариаций в ра- боте браузера. Рис. 10.14. Модульный тест выдает громадный объем информации об отрисовках Обсуждение Хотя мы создаем этот код для проверки производительности в модульном тесте •Гез!, он не является тестом в том же самом смысле, что и обычный тест функцио- нальности, поскольку мы не выполняем никаких утверждений. Хотя утверждения могут быть полезными2, нет смысла создавать тест производительности, который каждый раз показывает, что время исполнения некоторой операции меньше задан- ного значения. Результаты тестов производительности сильно зависят от среды. Если тест в среде разработки утверждает, что некая операция исполнится быстрее, чем за три секунды, не удивляйтесь, если он будет неуспешным на сервере инте- грации, где эта же операция займет девять секунд. Если же вы все-таки хотите отслеживать производительность автоматически, мо- жет быть целесообразным добавить регрессионное тестирование. Регрессионный тест записывает набор статистических данных по производительности в каком- 2 Например, проверяя, что компонент находится в определенном состоянии, прежде чем выполнять какое- либо действие.
либо центральном хранилище вместе с идентификатором среды, создавшей эти данные. Это позволяет проверить, что дальнейшие прогоны теста ненамного мед- леннее предыдущих прогонов в той же среде. По большому счету лучше предоставлять результаты тестирования производитель- ности, а не выдавать подтверждение заданной вами желаемой оценки производи- тельности. Исходный код данного рецепта можно загрузить на веб-сайте (ЗйНиЬ по адресу ЬМр8://оге11.1у/0СГ^А. 10.4. Точное измерение времени ЗАДАЧА Когда в процессе тестирования мы дойдем до такой точки, где потребуется оптими- зировать довольно низкоуровневый код Дауа8спр1, возникнет вопрос: как в таком случае измерить производительность? Для этого можно, например, посредством функции Оаге () создавать временную метку в начале и конце кода 1ауа8спр1. Листинг 10.8. Создание временной метки сопзГ ЬеГогеВасе = пей БаГе() Гог (1еГ 1=0; 1 < 1000; 1++) {} сопзГ аГГегБаГе = пей БаГе () сопзо1е.1од( '1,000 1оорз Гоок', аГГегВаГе.деГТшеО - ЬеГогеБаГе.деГИте() ) Каждую из этих дат можно преобразовать в миллисекунды, а затем отнять одно значение от другого, узнав таким образом длительность исполнения теста. Это был настолько стандартный способ, что новым методам Пте и ПтеЕпд был вы- делен консольный объект, чтобы сократить объем требуемого кода: сопзо1е.Гйпе(' 1, 000 1оорз') Гог (1еГ 1=0; 1 < 1000; 1++) {} сопзо1е.Г1теЕпс1 (' 1, 000 1оорз') Функция г.ипе принимает метку в качестве параметра, и если вызвать функцию гйпеЕпс! с этой же меткой, она отобразит результаты в консоли. Но исполнение этого кода дает следующий результат: 1,000 1оорз: Отз Как видим, полученный результат некорректен. Приложения Кеас! редко содержат функции длительного исполнения, поэтому небольшие фрагменты кода Лауа8спр( обычно необходимо оптимизировать только при их многократном вызове браузе-
ром. Например, может быть целесообразным оптимизировать код игры, который отрисовывает на экране анимацию. Оценить производительность небольших фраг- ментов кода трудно вследствие их чрезвычайно короткого времени исполнения, которое может не превышать одной миллисекунды. В подобных ситуациях изме- рять производительность с помощью объектов Баге нельзя, поскольку они имеют разрешение величиной всего лишь в одну миллисекунду, хотя системные часы компьютера намного точнее. Нам нужно какое-либо средство, которое позволило бы измерять промежутки вре- мени длительностью меньше одной миллисекунды. РЕШЕНИЕ Для решения этой задачи мы воспользуемся функцией регГогшапсе.поиО. Вызов этой функции возвращает метку времени с высокой точностью, измеряемой в долях миллисекунды. Например, исполнение этой функции в консоли браузера СЬготе дает результаты наподобие следующих: > рег^огтапсе.пои() < 10131.62500000908 Как видим, здесь время измеряется по-другому, чем в объектах Баге .1ауа$спр1. В Лауа8спр1 время отсчитывается начиная с 1 января 1970 г., а функция регГогтапсе.поиО измеряет время, начиная с момента загрузки текущей веб-стра- ницы3. При попытке исполнить функцию регГоппапсе.поиО в браузере РиеГох происходит интересная вещь: > регГогтапсе.поиО < 4194 По умолчанию НгеГох возвращает только целую часть результата в миллисекундах, по сути, нивелируя большинство преимуществ данного способа. Это объясняется настройками безопасности данного браузера. Теоретически если ДауаБспр! может измерять время исполнения крошечных фрагментов кода с высокой точностью, то это может создать сигнатуру браузера. Чтобы включить высокое разрешение времени в НгеГох, введите в строку адреса адрес аЬои^:соп^1д, в открывшемся списке настроек найдите параметр рг1уасу.гес1исеТ1тегРгес1з1оп и присвойте ему значение Еа1зе. После этого точность возвращаемых функцией рег€оппапсе.пои() результатов увеличится: рег1оппапсе.пои() 151405.8 3 При исполнении в Коде функция регГоппапсе.пои () измеряет время, начиная с начала текущего про- цесса.
Обязательно установите для этого параметра его исходное значение 1а1зе, если вы не желаете дать возможность сторонним лицам отслеживать вас. Возвращаясь обратно к нашему примеру кода тестирования, мы можем измерить время исполнения циклов, как показано в листинге 10.9. Листинг 10.9. Код для измерения времени исполнения циклов сопзГ ЬеГогеО = регГогтапсе. пои () Гог (1еГ 1=0; 1 < 1000; 1++) {} сопзГ аГГегО = регГогтапсе.пом() сопзо1е.1од('1, 000 1оорз Гоок', аГГегО - ЬеГогеО) сопзЬ ЬеГоге1 = регГогтапсе.пом() Гог (1еГ 1=0; 1 < 100000; 1++) {} сопзГ аГЬег1 = регГогшапсе.пом() сопзо1е.1од('100, 000 1оорз Гоок', аГГег! - ЬеГоге!) Исполнение этого кода выдает следующее: 1,000 1оорз Гоок 0.03576700000007804 100,000 1оорз Ьоок 1.6972319999999854 Эти результаты намного точнее и предоставляют больше информации о производи- тельности соответствующего кода 1а\а8спр1. В данном случае можно видеть, что добавление в цикл дополнительных итераций не масштабирует время исполнения цикла линейным образом. Это наводит на мысль, что движок 5а\а8спр( начинает на лету оптимизировать код, как только он понимает, что все итерации цикла одина- ковы. Обсуждение По сравнению с датами Лауа8спр1 функция регГогтапсе.пом о обладает несколькими преимуществами. Кроме дополнительной точности на эту функцию не воздейству- ют изменения часов, что является положительным аспектом, если вы решите доба- вить функциональность мониторинга производительности в код с длительным исполнением. Также она ведет отсчет времени от нуля, когда страница начинает загружаться, что полезно для оптимизации тайминга загрузки страницы. Но на основе регГогтапсе. пом о не следует создавать высокоуровневые функции тайминга. Например, мы однажды создали простую функцию Ышекеерего для генерирования кода 1ауа8спр1, чтобы немного упростить работу с функцией регГогтапсе. пом () (ЛИСТИНГ 10.10). Листинг 1010. Кодфункции ЫтекеврегО ГипсЫоп* Гипекеерег () { 1еГ пом = 0 мЪНе (Ггие) у!е1д -пом + (пом = регГогтапсе.пом()) )
Функция должна была устранить необходимость вычисления разницы между вре- менем начала и окончания. Без этой функции такая задача решалась, как показано в листинге 10.11. Листинг 10.11, Обычный код для вычисления разницы I между начальным и конечным временем сопзГ ЬеГогеО = регГоппапсе.пои() Гог (1еГ 1=0; 1 < 1000; 1++) {} сопзГ аГГегО = регГоппапсе.помО сопзо1е.1од('1,000 1оорз Гоок', аГГегО - ЬеГогеО) сопзГ ЬеГоге1 = регГоппапсе.помО Гог (1еГ 1=0; 1 < 100000; 1++) {} сопзГ аГГег1 = регГоппапсе.помО сопзо1е.1од('100,000 1оорз Гоок', аГГег! - ЬеГоге!) А функция ПтекеерегО позволила бы выполнять эту задачу, как показано в листин- ге 10.12. ; Листинг 10.12. Вычисление разницы между начальным и конечным временем при помощи функции Млеке^егО сопзГ Г = ПтекеерегО Г.пехГ () Гог (1еГ 1=0; 1 < 1000; 1++) {} сопзо1е.1од('1, 000 1оорз Гоок', Г.пехГО•Уа1ие) Гог (1еп 1=0; 1 < 100000; 1++) {} сопзо1е.1од('100,000 1оорз Гоок', Г.пехГО .уа1ие) Как видим, больше нет надобности в уродливых переменных ЬеГоге и аГГег. После каждого вызова функции г.песго.уа1ие время бы обнулялось, устраняя необходи- мость в вычислениях. Какая же с этим проблема? Заключение функции регГоппапсе.помО внутри другой функции добавляет значительный объем времени к измерению, аннулируя ее точ- ность: 1,000 1оорз Гоок 0.05978800000002593 100,000 1оорз Гоок 19.585223999999926 В данном случае, хотя исполнение 100 000 циклов занимает всего лишь 1,60 мс, наша функция возвращает результат, равный 19 мс. Никогда не вставляйте функцию регГоппапсе.помО в другую функцию, если вы хотите, чтобы она возвращала точные результаты. Исходный код данного рецепта можно загрузить на веб-сайте СгйНиЬ по адресу ЬНр8://огеП.1у/0СИ2А.
10.5. Уменьшение размера приложений посредством разделения кода ЗАДАЧА Одно из самых отрицательных воздействий на производительность одностранич- ных приложений оказывает объем кода 1ауа8спр1, который нужно загрузить и ис- полнить. Мало того, что отрисовка кода 5ауа8спр1 занимает время, так еще и уве- личенная нагрузка на пропускную способность сети может значительно замедлить исполнение вашего приложения на устройствах, подключенных к мобильной сети. Рассмотрим для примера приложение с синхронизированными маршрутами, кото- рое мы использовали в главе 2 (рис. 10.15). Рис. 10.15. Главное окно приложения с синхронизированными маршрутами Хотя само приложение крошечного размера, оно содержит несколько довольно крупных пакетов кода Дауа8спр1, как показано в листинге 10.13. Листинг 10.13. Пакеты кода Зауа5пр1 для приложения с синхронизированными маршрутами $ 1з -1 ЬиНсЗ/зРаТтс/зз ЁОЁа1 1336 -гм-г—г— 1 адпйп 161800 12:07 2.4с1Ь4с1779.сЬипк. дз -гм-г—г— 1 йаугдд аскптп 1290 12:07 2.4с1Ь4сГ779.сЬипк.]з.ЫСЕМЗЕ.ёхё -гм-г—г— 1 скплЩд аскптп 461100 12:07 2.4<ЗЬ4с1779.сЬипк.]5.шар -гм-г—г— 1 даухдд адтхп 4206 12:07 3.307а63д5.сИипк.]з -гм-г—г— 1 с^1с1д айтп 9268 12:07 3.307а63с15.сИипк.дз.шар -гм-г—г— 1 даухйд аскп1п 3082 12:07 та1п.е8аЗе1сЬ.сИипк.]з -гм-г—г— 1 баутдд асЫп 6001 12:07 та!п.е8аЗе1сЬ.сЪипк.дз.тар -гм-г—г— 1 с1ау1с1д асЫп 2348 12:07 гипЁ1те-та1п.67(ЗГ5Г2е.]3 -гм-г—г— 1 <ЗаУ1с1д а<±п!п 12467 12:07 гипЁнпе-таз-П.67с1Г5Г2е.^з.тар $ Самый большой пакет (2.4дЬ4д779.с1шпк.]8) содержит основной код фреймворка Кеас1, а специфичный для приложения код содержится в небольшом файле таш.е8аЗе1сЬ.сЬипк.]8. Это означает, что размер данного приложения настолько мал, насколько возможно для приложения Кеас(. Размер большинства приложений Реас! значительно больше, часто до 1 Мб, что будет представлять значительную проблему для пользователей с медленным сетевым подключением.
Так что же можно сделать, чтобы уменьшить размер пакетов кода Зауа8спр1 в при- ложениях Кеас1? РЕШЕНИЕ Решением данной проблемы будет подход с разделением кода, который состоит в разбиении основного кода приложения на несколько пакетов меньшего размера. Тогда браузер сможет загрузить эти пакеты без особой спешки. Конкретный пакет будет загружаться только в случае надобности одного из содержащихся в нем ком- понентов. Приложение, которое мы рассматриваем в качестве примера в этом разделе, конеч- но же, не требует разделения кода. Как и все корректировки для улучшения произ- водительности, разделение кода следует предпринимать только в том случае, если это окажет существенное положительное воздействие на производительность при- ложения. Но мы выбрали это приложение для демонстрации разделения кода, поскольку так будет легче наблюдать действие этого процесса. Для разделения кода приложений КеасХ предусмотрена функция Хагу: ипрогЁ { Хагу } Хгот 1геасХ' В качестве параметра эта функция принимает фабричную функцию, которая при вызове импортирует компонент. Функция Хагу возвращает компонент-заглушку, который не делает ничего до тех пор, пока браузер не выполнит его отрисовку. Тогда компонент-заглушка исполняет фабричную функцию и динамически загру- жает пакет, содержащий настоящий компонент. Чтобы разобраться, как это работает, рассмотрим компонент АЬоих из нашего демонстрационного приложения (листинг 10.14). Листинг 10.14. Кед компонента АЬоиъ ипрогЁ { ЫауЫпк, КесЦгесТ, КоиТе, ЗиХХск } Ггот 'геасХ-гоиХег-дош' Хтрогх РеорХе Хгот './Реор1е' Хтрогх ОГХХсез Хгот ' ./ОХНсез' ХтрогХ './АЬоиХ. сзз1 сопзХ АЬоиХ = ()=>( <с11у сХаз5Нате="АЬоиХ"> <сИу сХаззНате="АЬоиХ-ХаЬз"> <ЫауЫпк Хо="/аЬоиХ/реорХе" сХаззКате="АЬоиХ-ХаЬ" асГ1УеС1аззМате="асМуе" РеорХе </ЫауЫпк> <КауЪ1пк Хо='7аЬоиХ/оГХХсез" сХаззЫате="АЬоиХ-ХаЬ"
асЁ1уеС1а5зЫате="ас111уе" > 0??1сез </НауЫпк> </с!1У> <Зи1бсЪ> СЕоиЁе ра1Ъ="/аЬои!:/реор1е"> <Реор1е /> </ВоиЁе> <КоиИе раИИ="/аЬои11/о^1сез"> <0ГПсез /> </КоШ:е> сКесИгесЁ 1:о="/аЬои1:/реор1е" /> </5ызЛс11> </сИл/> ) ехрогр <1е^аи1б АЬоиЁ Браузер выполняет отрисовку компонентов Реор1е и сшеез только при посещении пользователем соответствующего маршрута. Если в настоящее время браузер нахо- дится на пути /аЬои1/реор1е, то компонент ошсез не будет отрисован. Значит, по- тенциально мы могли бы отложить загрузку данного компонента на более позднее время. Это можно реализовать при помощи функции 1агу. Заменим импортирование компонента отсез вызовом функции 1агу: //гтрогЁ О^Псез Ггот " ./ОЕПсез" сопзб ОГЛсез = 1агу(() => хтрогк ('. /Оккхсез')) Объект, который теперь хранится в переменной отсез, будет выглядеть для остального кода приложения просто как любой другой компонент. Это так назы- ваемая "ленивая" заглушка (1ату р1асеЬо1дег). Она содержит ссылку на фабричную функцию, которую вызовет, когда браузер выполнит ее отрисовку. Но если мы сейчас выполним обновление страницы, то браузер отобразит сообще- ние об ошибке (рис. 10.16). Еггог: А НеасГ согпропепг зизрелбеб »Ы1е гелбеПпд, ЬШ по СаИЬаск 1Л чтаз зресЮеб. х Абб а кЗизрепзе Та11Ьаск=...> сошропеШ ЫдНег 1п Ню 1гее 1о ргомбе а 1оа0!пд 1пб1са!аг ог р!асеКо1бег 1о б!зр1ау. г II йхк челе сМ1ар>«4« ПобмХе - '«апопуарикх гс/ЦъНх. ]к:7 * | Арр <ГСИ ‘-/Арр‘; 5 | 11^0г« гвогЩИШ! Ггои ‘./гертнидомаи*; * I * 7 | Ас«1ООН, гепбег! в | «Яж»сЛ 9 | /> !• | «/Нмс 1.51Г1С Шебе», VI» Рис. 10.16. Сообщение об ошибке при "ленивой" загрузке
Наш компонент-заглушка не будет ожидать загрузки настоящего компонента перед возвращением. Вместо этого он подставит какой-либо другой код НТМЬ, пока ожидается завершение загрузки настоящего компонента. Мы можем организовать этот интерфейс "загрузки”, используя контейнер Зизрепзе, как показано в листинге 10.15. ; Листинг ЮЛ 5. Использование контейнера Зизрепзе I ........../_____'.............:_____'1_.................... '_____ ХгсрогЁ ( Хагу, Зизрепзе } Хгот 'геасЁ' ХгпрогЁ { ЫауЫпк, КесИгесЁ, ЕоиЁе, ЗиХёсЪ } Хгога 1 геасЁ-гоиЁег-бот' ипрогЁ РеорХе Хгош './РеорХе' // ХтрогЁ ОХХХсез Ггош './ОХХХсез' ХтрогЁ './АЬоиЁ.сзз' сопзё ОХХХсез = Хагу (() => ХтрогЁ ('./ОХХХсез')) сопзё АЬоиЁ =()=>( <сИ\^ сХазз!'1ате="АЬоиЁ"> <с1Ху^ сХаззНате="АЬоиЁ-ЁаЬз"> <^аV^^пк Ёо="/аЬоиЁ/реорХе" сХаззМате="АЬоиЁ-ЁаЬ" асЁ^VеСXазз^ате="асЁ^Vе" > РеорХе </ЫауЬХпк> <^аV^^пк Ёо="/аЬоиЁ/оГГХсез" сХаззЫате="АЬоиЁ-ЁаЬ" асЁГУеСХаззЫате="асЁХуе" > ОХХХсез </МауЬХпк> </с1Хх/> <3изрепзе ЁаХХЬаск={<с1Ху>Ьоа<±Хпд.. .</ЬХу>}> <Зм1ёсИ> <КоиЁе раЁЪ="/аЬоиЁ/реорХе"> <РеорХе /> </КоиЁе> <ЕоиЁе раЁИ="/аЬоиЁ/оЁЁ1сез"> <ОХЁ1сез /> </КоиЁе> СЕесИгесЁ Ёо="/аЬоиЁ/реорХе" /> </8иХёсЪ> </Зизрепзе> </<1Х\^> ехрогЁ «ХеЁаиХЁ АЬоиЁ
Компонент-заглушка 1агу ищет в своем контексте резервный компонент ГаПЬаск, предоставляемый контейнером Зизрепзе, и отображает его на странице, пока он ожидает загрузку дополнительного пакета кода 1ага8спр1. В данном случае мы выводим простое сообщение "Ьоадш^...", но можно использо- вать любой подстановочный интерфейс, чтобы создать у пользователя иллюзию, что новый компонент уже загрузился, прежде чем он в действительности загрузит- ся. Например, этот же метод применяется на домашней странице УоиТиЬе. Пока загружается содержимое сайта, в браузере отображается набор блоков и пря- моугольников вместо иконок видеоклипов, которые будут вскоре загружены (рис. 10.17). Для загрузки значков клипов часто требуются две-три секунды, но этот метод создает у пользователя впечатление, что они загружаются мгновенно. Рис. 10.17. Сайт УоиТиЬе отображает макет домашней страницы, пока загружаются рекомендуемые клипы Если сейчас обновить страницу нашего демонстрационного приложения, оно снова начнет отображаться нормально, как показано на рис. 10.18. Рис. 10.18. Добавление компонента Зизрепзе устраняет ошибку загрузки Тем временем за кулисами сервер для разработки У/еЬраск выделит код компонен- та огпсез в отдельный пакет кода 1ауа8спрЕ Он также разделит код на отдельные пакеты при создании сборки. Для этого сервер ХУеЬраск использует метод встряхивания дерева (1гее зЬакт§), чтобы определить,
те компоненты, которые можно без проблем разместить в определенные пакеты Лауа8спр1. Встряхивание дерева— это процесс, который рекурсивно анализирует, какие файлы с кодом импортируются другими файлами, начиная с какого-либо исходного файла, например 1пбех.]8. Это позволяет серверу \Л/еЬраск избежать добавления в пакет кода, который никогда не импортируется никаким другим кодом. Вызовы функции КеасЁ.1агу не отслеживаются данным процессом, поэтому код с "ленивой" загрузкой не включается в начальный пакет 6ауа8спр1. Вместо этого сервер \Л/еЬраск исполняет отдельный процесс встряхивания дерева для каждого файла с "ленивой" загрузкой, в результате чего в рабочем приложении создается множе- ство пакетов кода небольшого размера. Если теперь создать новую сборку, а затем проверить папку 18 сборки, то мы уви- дим, что она содержит несколько новых файлов, как показано в листинге 10.16. । Листинг 10.16. Список файлов в новой сборке $ уагп ЬиНб . ..ВиНбз соде $ 1з -1 ЬиИд/зГаПс/дз 1о1а1 1352 -гм-г—г— 1 с1ау1с1д адпйп 628 12:09 0.а30Ь3768.сИипк.зз -гм-г—г— 1 даухдд адтхп 599 12:09 0.а30Ь3768.скипк.дз.тар -гм-г—г— 1 даухдд адтхп 161801 12:09 3.17664178.скипк.^з -гм-г—г— 1 даухдд адтхп 1290 12:09 3.17664178.скипк. 1з.ЫСЕ№Е.1х1 -гм-г—г— 1 даухдд адтхп 461100 12:09 3.17664178.скипк.1з.тар -гм-г—г— 1 даухдд адтхп 4206 12:09 4.а74Ье2М.сИипк. ]3 -гм-г—г— 1 даухдд адтхп 9268 12:09 4.а74Ье2Ы.сЪипк.зз.тар -гм-г—г— 1 даухдд адтхп 3095 12:09 тахп.е4де2е45.скипк.дз -гм-г—г— 1 с^Щд адтхп 6089 12:09 тахп.е4де2е45.сЬипк.^з.тар -гм-г—г— 1 даV^дд адтхп 2361 12:09 гип1хте-тахп.96106006.дз -гм-г—г— 1 с^Мд адтхп 12496 12:09 гип!хте-тахп.9д106006.дз.тар Поскольку размер нашего демонстрационного приложения невелик, вряд ли опи- санная оптимизация как-либо повлияет на производительность, но давайте прове- рим в любом случае. Проверить производительность загрузки легче всего при помощи инструмента ЬщИШоизе браузера СЬготе. На рис. 10.19 показаны данные измерения производи- тельности оригинальной версии этого приложения. Оптимизация приложения добавлением в него возможности "ленивой” загрузки слегка повышает производительность, в основном за счет уменьшения времени на завершение ГСР(рис. 10.20). Полученное улучшение производительности нельзя назвать существенным, но тем не менее оно показывает, что "ленивая" загрузка может быть полезной даже для крошечных приложений.
Рис. 10.19. Данные производительности приложения без разделения кода Рис. 10.20. Данные производительности приложения с разделением кода Обсуждение Все виды оптимизации имеют свою цену, но реализация разделения кода требует минимальных усилий, и мы используем эту оптимизацию наиболее часто. Данный метод нередко улучшает такие важные веб-показатели, как РСР и ТТ1. Чрезмерно увлекаться этим не стоит, поскольку фреймворку требуется выполнять дополни- тельную работу по загрузке и оценке каждого сценария. Но для большинства дос- таточно крупных приложений разделение кода немедленно даст определенный положительный эффект.
Разделение кода часто лучше всего выполнять на уровне маршрутов. Маршруты управляют видимостью компонентов и поэтому будут хорошим местом для отде- ления кода, который нужно загрузить сейчас, от кода, который необходимо загру- зить позже. К тому же если пользователь создаст закладку в каком-либо месте ва- шего приложения, то при переходе по этой закладке он загрузит только код, тре- буемый для данного места. Исходный код данного рецепта можно загрузить на веб-сайте СгйНиЬ по адресу Ы1р8://оге11.1у/0СГ^А. 10.6. Объединение сетевых обещаний ЗАДАЧА Многие приложения КеасЕ осуществляют асинхронные сетевые вызовы, ожидание ответа на которые сильно тормозит работу приложения. В течение этих вызовов приложение, скорее всего, не выполняет какой-либо существенной работы, поэтому оно не занято, а просто ожидает. С течением времени уровень сложности клиентских приложений повысился, а уро- вень сложности АР1-серверов— снизился. В случае бессерверных приложений АР1-интерфейсы серверов стали настолько стандартными, что не требуют никакого специализированного кода, что ведет к повышению числа вызовов АР1-интерфейса, осуществляемых клиентским кодом4. Рассмотрим соответствующий пример. Имеется приложение, которое считывает данные нескольких людей из АР1-интерфейса сервера бэкенда. Сервер содержит конечную точку, которая при получении от браузера запроса сет по адресу /реор1е/1234 возвратит данные человека с идентификатором 1234. Для формирова- ния этих запросов разработчик создал хук, код которого показан в листинге 10.17. । Листинг 10.17. Код для создания запросов к конечной точке ЕтрогЕ { изеЕЕЕесЕ, изеЗЕаЕе } Егот 'геасЕ' ЕтрогЕ { деЕ } Егот './Еакегоз' сопзЕ изеРеор1е31ом = (...1дз) => { сопзЕ [реор1е, зеЕРеор1е] = изеЗЕаЕе([]) изеЕЕЕесЕ(() => { 1еЕ сЦдСапсе! = Еа1зе ;(азупс 0 => { сопзЕ гези1Е = [] Еог (1еЕ 1 = 0; 1 < 1<Зз.1епдЕк; 1++) { сопзЕ 1д = Мз [1] 4 Исключение составляет служба СтарЬрЬ. В этой среде клиент может отправить серверу бэкенда сложный запрос, и стандартизированный интерпретатор запросов соберет вместе на сервере результаты низкоуровне- вых запросов. Среда исполнения ОгарЬрЬ позволяет получить быстрые сетевые ответы без необходимости выполнять настройку клиента.
гезиЁЁ.ризЪ (аыаЁЁ деЁ('/реорЁе/' + Ёд)) } И (! дЁдСапсе!) { зеЁРеорЁе(гезиЁЁ) } }) О геЁигп () => { дЁдСапсе! = Ёгие } // езИпЁ-сПзаЫе-пехЁ-Ипе геасЁ-Иоокз/ехЬаизЁ^е-сЗерз }, [...Ёдз]) геЁигп реорХе } ехрогЁ деЁаи1Ё изеРеорЁеЗЁои Этот хук вызывается следующим образом: сопзё реорЁеЗЁом = изеРеорЁеЗЁом(1, 2, 3, 4) Данный код отправляет серверу вызов для каждого значения идентификатора. Перед тем как сохранить ответы в массиве, код ожидает ответы на все запросы. Та- ким образом, если конечной точке АР1-интерфейса требуется 5 секунд для выдачи ответа, то, чтобы возвратить все данные, хуку изеРеор1е31о« потребуется 20 секунд. Можно ли каким-либо образом ускорить этот процесс? РЕШЕНИЕ Для решения данной задачи мы объединим асинхронные обещания, чтобы одно- временно в процессе подачи было несколько запросов к АР1-интерфейсу. Большинство библиотек для работы с асинхронными запросами возвращают обе- щания. Если ожидать обработки обещания, то оно возвратит полезную нагрузку ответа. Но в нашем примере с кодом хука изеРеор1е51ом ожидание обработки этих обещаний осуществляется в последовательности, как показано в листинге 10.18. Листинг 10.18. Ожидание обработки обещаний сопзё гези1Ё = [] Ёог (1еЁ 1 = 0; ё < 1йз.1епдЁЁ1; Ё++) { сопзё Ёд = Ёдз[Ё] гезиЁЁ.ризЪ(аиаЁЁ деЁ('/реорХе/' + Ёд)) } Запрос для второго человека не отправляется, пока не будет получен ответ на за- прос для первого человека. Это и является причиной, почему 5-секундная задержка превращается в 20-секундное время отклика при считывании данных для четырех людей.
Данную задачу можно решить другим способом. В частности, запросы можно от- правлять, не ожидая ответа, чтобы все они находились в процессе обработки одно- временно. Затем нам нужно дождаться всех ответов, и, получив последний из них, мы можем возвратить данные из хука. Реализовать параллельную отправку запросов можно при помощи функции 1а\а$спр1 Ргопйзе.аИ. Эта функция принимает в качестве параметров список обещаний и объединяет их в одно обещание. Таким образом, мы можем объединить несколько вызовов дехо, как показано в листинге 10.19. Листинг 10.19. Объединение нескольких вызовов деХ() сопзх [гез1, гез2, гезЗ] = аиаХХ Ргопйзе.аХК деХ (' /реор1е/1'), де1г (' /реор1е/2'), деХ (' /реор!е/3') ) Функция Ргошгзе.аИ объединяет не только обещания, но также и результаты. Если дождаться массива обещаний с функцией Ргопйзе.аП, мы получим массив, содержа- щий все обещания. Теперь мы можем модифицировать хук изеРеорХеЗХом, используя в нем функцию Ргопйзе.аИ. Соответствующий код приведен в листинге 10.20. Листинг 10.20. Версия хука изеРеорХеЕазХ с использованием функции РголДзе.аИ ХтрогХ { изеЕХХесХ, изеЗХаХе } Хгот 'геасх' ХтрогХ { деХ } Хгот './ХакеХоз' сопзХ изеРеорХеЕазХ = (...16з) => { сопзХ [реорХе, зеХРеорХе] = изеЗХаХе([]) изеЕХХесХЦ) => { ХеХ 616СапсеХ = ГаХзе ;(азупс () => { сопзХ гезиХХ = аыаХХ РготХзе.аХХ( 16з.тар((16) => деХ('/реорХе/' + 16)) ) 1Х (!616СапсеХ) { зеХРеорХе(гезиХХ) } }) О геХигп () => { 616СапсеХ = Хгие
// езИпХ-сИзаЫе-пехХ-Ипе геасХ-Ноокз/ехкаизХгуе-йерз }, [..Лдз]) геХигп реорХе } ехрогХ деГаи1Р изеРеор!еГазХ Ключевыми в этом коде являются следующие три строки: сопзХ гези1Х = аыахХ Ргопйзе.аИ ( 1бз .тар ((1д) => деХ('/реор1е/' + Ы)) Сопоставляя (метод хбз.тар) идентификаторы элементам массива обещаний, воз- вращенных сетевыми запросами, мы можем дождаться результата функции Ргопйзе. а11 и получить массив со всеми ответами. Если измерить время исполнения обоих хуков, то увидим, что хук изеРеорХегазх считывает данные четырех пользователей чуть больше, чем за пять секунд. Факти- чески мы исполнили пять запросов за время, требуемое для исполнения одного. В табл. 10.2 для сравнения приведено время исполнения каждой версии хука изеРеор!е. Таблица 10.2. Время исполнения каждой версии хука изеРеор!е Версия хука Время исполнения, мс изеРеор!е81ом 20011,224999994738 изеРеор!еГазХ 5000,99999998929 Обсуждение Данный подход значительно улучшит производительность сетевых запросов, даже при наличии множественных независимых асинхронных запросов. Если отправлять большое количество параллельных запросов, то браузер, сетевая плата или сервер могут начать ставить их в очередь. Но этот метод тем не менее позволяет получить ответ быстрее, нежели последовательность отдельных запросов. Параллельные запросы усиливают нагрузку на сервер, но это вряд ли будет сколь- ко-либо большой проблемой. Во-первых, как уже упоминалось, серверы часто ста- вят запросы в очередь, когда они заняты. Во-вторых, сервер будет одинаково выполнять общий объем работы. Мы просто концентрируем эту работу в более короткий период времени. Исходный код данного рецепта можно загрузить на веб-сайте СгйНиЬ по адресу йИр8://огеП.1у/0СГ^А.
10.7. Отрисовка на стороне сервера ЗАДАЧА Одностраничные приложения наполняют веб-страницы различными возможностя- ми, делая их похожими на настольные приложения. Работа с приложением напо- добие Сюо§1е Посз почти неотличима от использования текстового редактора на персональном компьютере. Но все имеет свою цену. Одна из основных проблем с производительностью одно- страничных приложений состоит в том, что прежде чем браузер может создать ин- терфейс, ему нужно загрузить большой объем кода 1ауа8спр1. Тело кода НТМЬ приложений, созданных при помощи такого инструмента, как сгеа^е-геасг-арр, содержит лишь пустой раздел <с11у> с идентификатором гоом «ЦУ 1С1="ГСЮЁ"></С11ЛГ> И лишь этот пустой раздел <с1т будет доступен браузеру для отображения, пока движок 5а\а8спр1 не загрузит сопутствующий код 1ауа8спрЬ исполнит его и обно- вит модель ВОМ. Даже если уменьшить размер загружаемого кода, разделив его на отдельные паке- ты, и учесть, что браузер кеширует код 1ауа8спрЬ чтение кода и организация ин- терфейса могут тем не менее занять пару секунд. Создание всего интерфейса полностью на основе кода 1ауа8спр1 означает, что од- ностраничные приложения страдают двумя основными проблемами. Во-первых, и наиболее важно то, что восприятие пользователем может ухудшиться, особенно в случае больших приложений КеасТ Во-вторых, поисковая оптимизация (8ЕО) приложения будет плохой. При сканировании сайтов роботы поисковых систем часто не ждут, пока 1а\а8спр1 выполнит отрисовку интерфейса, а загружают базо- вый НТМЬ-код страницы и индексируют его содержимое. Для многих бизнес- приложений это, может, не слишком важно. Но для, например, сайта интернет- магазина скорее всего будет желательным, чтобы было проиндексировано как можно больше страниц, чтобы увеличить число посещений. Поэтому будет полезным, если вместо отображения пустого раздела <сИу>, пока загружается код НТМЬ, можно было бы включить в страницу приложения начальный код НТМЬ перед тем, как браузер загрузит и исполнит код 1ауа8спрЬприложения. РЕШЕНИЕ Для решения данной задачи мы рассмотрим, как использовать отрисовку на сторо- не сервера, чтобы заменить пустой раздел <сНу> страницы Кеас1 заранее отрисован- ным кодом НТМЬ. Это возможно благодаря механизму взаимодействия КеасГ с мо- делью ООМ веб-страницы. При отрисовке компонента в Кеас! вместо непосредственного обновления модели ПОМ исполняется фрагмент кода, приведенный в листинге 10.21.
Листинг 10.21. Код для обновления виртуальной модели (ЮМ КеасРООМ.гепбег( <КеасР. 8Рг1сРМос1е> <Арр /> </КеасР. 8Рг1сРМос1е>, досшпепР. деРЕ1етепРВу1с! (' гооР ') Метод гепдег обновляет виртуальную модель ПОМ, которая периодически синхро- низируется с настоящими элементами НТМЬ-страницы. Среда Кеас1 выполняет это обновление рациональным способом, обновляя в реальной модели ПОМ только те элементы, которые не совпадают с элементами в виртуальной модели ЬОМ. Отрисовка на стороне сервера выполняется не в виртуальную модель ВОМ среды КеасТ, а в строку. Когда браузер запросит у сервера НТМЬ-страницу, мы выполним отрисовку версии содержимого Кеас1 в строку, затем вставим эту строку в код НТМЬ и лишь тогда возвратим код браузеру. Это означает, что браузер немедленно отобразит содержимое НТМЬ-страницы, до того как он начнет загружать код Лауа$спр1-приложения. Код для реализации этого на стороне сервера может выгля- деть так, как показано в листинге 10.22. Листинг 10.22. Код для предварительной отрисовки на стороне сервера 1еР гпдехНТМЪ = СсопРепРз о! 1пдех.ИРт1> сопзР арр = СгепОег Арр ёо зРг1пд> хпдехНТМЬ = хпдехНТМЬ.гер1асе( '<сИу 1с1="гооё"></с11у> 1, '<с1гу 1с1="арр">$ {арр}</сР1Л7>' ) гез. сопРепРТуре (1 РехР/ЪРт!1) гез.зРаРиз(200) геРигп гез.зепбЦпдехНТМЬ) Чтобы разобраться с этим подходом более подробно, создадим приложение сгеаРе-геаср-арр. Отрисовку на стороне сервера поддерживают многие инструменты и фреймворки КеасЦ но средство сгеаРе-геаср-арр не входит в их число. Поэтому рассмотрим, как преобразовать приложение сгеаРе-геаср-арр в версию с поддержкой отрисовки на сервере (88К), чтобы понять все шаги, необходимые для реализации отрисовки на сервере в Кеас1: $ прх сгеаРе-геасР-арр ззгарр Нам нужно создать сервер, чтобы разместить на нем код отрисовки. Начнем с соз- дания папки для хранения серверного кода: $ тксНг зегуег
Для создания сервера мы используем средство Ехргезз. Наш сервер код будет вы- полнять отрисовку компонентов приложения. Нам потребуется несколько дополнительных библиотек, чтобы помочь с загрузкой компонентов КеасГ В основной папке приложения (не в подпапке для сервера) выполните следующую команду установки: $ прт ЁпзЁаИ —заVе-сIеV 1дпоге-зЁу1ез иг1-1оас!ег @ЬаЬе1/гед1зЁег Средство сгеаРе-геасР-арр создает код, использующий большое количество совре- менных возможностей 1ауа8спр1, которые недоступны в установочном варианте среды. Поэтому первое, что нам нужно сделать в нашем серверном коде, — разре- шить эти возможности 5ауа8спр1, чтобы сервер мог исполнять наши компоненты КеасТ. Для этого в созданной нами ранее папке для сервера создайте файл тс!ех.)$ и вставьте в него код, приведенный в листинге 10.23. ! Листинг 10.23. Код файла 1П<1ех.18 геди1ге('хдпоге-зЁуЁез’) гедшге (' иг!-1оас1ег') гедшге (1 Ше-1оас1ег1) гедиЁге (' гедепегаЁог-гипЁхте/гипЁЁте') гедиЁге('@ЬаЬе1/гед1зЁег')({ Ёдпоге: [ / (пос1е_тос1и1е5) / ], ргезеЁз: [ '@ЬаЬе1/ргезеЁ-епу', [ '@ЬаЬе1/ргезеЁ-геасЁ', ( гипЁЁте: 'аиЁотаЁЁс1, }, ], ], рЁидЁпз: [ ], }) гедиЁге('./ззг') Этот файл содержит настройки возможностей языка 1ауа8спр1, которые мы будем использовать в серверном коде. В частности, мы загружаем подключаемый модуль ргезеЁ-геасЁ компилятора ВаЬе1, который устанавливается автоматически в каждом приложении сгеаЁе-геасЁ-арр. В конце этого сценария загружается файл ззг.]з, в ко- торый мы поместим наш основной серверный код. Далее в папке сервера создайте файл ззг.]з и вставьте в него код, приведенный в листинге 10.24. Листинг 10.24. Серверный код для файла ззг^а
ЁтрогЁ раЁк Ёгот 'раЁк' сопзё зегуег = ехргезз() зегуег.деЁ( /.(]3|сзз|тар|1со|зуд|рпд)$/, ехргезз.зёэёёс(раЁк.гезо!уе(___сИгпате, '. ./ЬиИб')) ) зегуег.изе(1 *', азупс (гед, гез) => { 1еЁ 1пс1ехНТМЬ = Ёз.геайГИеЗупс ( раЁк.гезоЗл/е (_сНгпате, ' . ./ЬиИ6/1пс1ех.кЁт1'), { епсосктд: 'иЁ Ё 81, } ) гез.сопЁепЁТуре('ЁехЁ/кЁт!') гез.зЁаЁиз(200) гекигп гез.зепб(ЁпбехНТМЪ) }) зегуег.ИзЁеп(8000, () => { сопзо1е.1од('Ьаипскес! аЁ кЁЁр://1оса!козЁ:8000!') }) Наш пользовательский сервер будет работать подобно серверу для разработки, со- держащему средство сгеаке-геасЁ-арр. Он создает веб-сервер следующей строкой кода: сопзё зегуег = ехргезз() При получении сервером запроса на код Зауа8спр1, таблицу стилей или изображе- ние он будет искать соответствующий файл в папке ЬиПд (листинг 10.25). В этой папке средство сгеаЁе-геасЁ-арр сохраняет созданную им развертываемую версию приложения. Листинг 10.25. Поиск сервером запрошенного материала в папке ЬиПд зегуег.деЁ( /.(зз|с5з|тар|1со|зуд|рпд)$/, ехргезз.зёэёёс(раЁк.гезо!уе (_(Пшате, 1 ../ЬиНсГ)) ) На запросы материала любого другого типа сервер возвращает содержимое файла Ьш1д/1пдех.Ыш1: зегуег.изе(1 * 1, азупс (гед, гез) => { })
Наконец, сервер запускается на порту 8000: зегуег. ПзГеп (8000, () > { сопзо!е.1од( 'ЬаипсЪес! аГ ЬГГр://ГосаГЪозГ:8000! ') }) Но прежде чем запускать сервер, нужно выполнить сборку нашего приложения, задав следующую команду: $ уагп гип Ьи11с1 В процессе сборки приложения в папке ЬиПс! создаются все статические файлы, ко- торые будут нужны нашему серверу. Вот теперь сервер можно запустить на испол- нение: $ поде зегуег ЬаипсЬед аТ: ЬГГр://ГосаПлозГ:80001 Запустив сервер, открываем браузер по адресу Ьир://1оса1Ьо81:8000. В браузере должно отобразиться наше приложение КеасГ (рис. 10.21). ЕсШ 5гс/Арр. ] 5 апд зауе 1о ге1оад, Ьеагп Реас! Рис. 10.21. Приложение Кеас1, раздаваемое созданным нами сервером Пока что все идет по плану. Но в действительности мы не выполняем никакой отрисовки на стороне сервера. Для этого нам нужно загрузить немного кода КеасГ для загрузки и отрисовки компонента Арр (листинг 10.26). Листинг 10.26. Код для загрузки и отрисовки компонента Арр ]
1шрогЁ раЁк Ёгот 'раЁИ' тшрогЁ { гепбегТоЗЁгтпд } Г г от ' геасЁ-йот/зегуег' йпрогЁ Арр Ггот '../згс/Арр' сопзё зегуег = ехргезз () зегуег. де!: ( /.^з|сзз|тар|1со|зуд|рпд)$/, ехргезз.зЁаЁ1с(раЁЪ.гезо1уе(___сНгпате, '. ./ЬиНсГ)) ) зегуег.изе(’*', азупс (гед, гез) => { 1еЁ ЁпбехНТМЬ = Гз.геабЕ11е5упс( раЁк.гезоЪ/е (_____сНгпате, ’../ЬиНб/Ёпйех.ЪЁт!'), ( епсосИпд: 'иЁЁ8', 1 ) сопзё арр = гепдегТоЗЁггпд(<Арр />) 1пс1ехНТМЬ = ЁпбехНТМЬ. гер1асе ( ’ <61V 16= " гооё " >< /61 ч>', '<61у 16="арр">${арр)</61У>' ) гез.сопЁепЁТуре('ЁехЁ/ИЁт!') гез.зЁаЁиз(200) геЁигп гез.зеп6(1п6ехНТМЬ) }) зегуег.НзЁеп(8000, 0 => { сопзоХе.1од('ЬаипсЬеб аЁ ЪЁЁр://1оса1ИозЁ:8000!') }) В коде в листинге 10.26 используется функция гепбегТоБЁглпд из библиотеки геасЁ- бош/зегуег КеасЬ Эта функция делает то, что можно ожидать по ее названию. Вме- сто отрисовки компонента Арр в виртуальную модель ЬОМ она выполняет его от- рисовку в строку. Таким образом, мы можем заменить пустой раздел «1^> в файле тёех.Ыт! кодом НТМЬ, сгенерированным из компонента Арр. Если теперь переза- пустить сервер и перезагрузить веб-браузер, мы увидим, что, хотя приложение и продолжает работать, функционирует оно не совсем правильно (рис. 10.22). Вместо изображения вращающегося логотипа Кеас1 мы видим его искаженную вер- сию. Чтобы понять причину этого, взглянем на возвращенный сервером сгенериро- ванный на нем код НТМЬ, который приведен в листинге 10.27. Листинг 10.27. Возвращенный сервером код НТМЬ - <61У 16="арр"> <сИу с1азз="Арр" 6аЁа-геасЁгооЁ=""> <Ьеа6ег с1азз="Арр-11еа6ег">
<1тд згс="[оЬ^есЁ ОЬ^есТ]" с1азз="Арр-1одо" а1Т="1одо’7> <р>ЕсИг <сос!е>згс/Арр.]з</сос!е> апс! зауе Ро ге1оас1.</р> <а с1азз="Арр-Ипк" ИгеГ="ИРРрз://геасР]з.огд" РагдеТ="_Ыапк" ге1="поорепег погеРеггег"> Ьеагп ВеасР </а> </Ьеас1ег> </сИ^> </с11у> \о^° е Есй1 5 г с/Арр5 апс! зауе Ю ге1оад Рис. 10.22. Приложение Кеас1 отображает искаженное изображение 8\/6 Обратим внимание на то, что с элементом нпд произошло что-то странное. Вместо того, чтобы выполнять отрисовку изображения 8УС, он пытается загрузить 1ЖЬ [оь^еср оь^еср]. Что здесь происходит? В коде Кеас1 логотип загружается следующим образом: хтрогр 1одо Ргот './1одо.ЗVд' <1тд згс={1одо} с1аззКате="Арр-1одо” а!Р="1одо" /> Этот код полагается на какую-то конфигурацию ХУеЬраск из средства сгеаре-геаср- арр. При обращении к приложению через сервер для разработки сборщик модулей У/еЬраск заменяет любые импортированные файлы 8УО сгенерированными КеасЪ- компонентами, содержащими необработанное содержимое 8УО, используя для это- го библиотеку зудг. Библиотека зудг позволяет загружать изображения 8УО, как любые другие компоненты Веаск Таким образом, мы можем импортировать эти изображения как простой файл *.]8.
Но созданный нами сервер не содержит такой конфигурации ^еЬраск. Вместо того чтобы заморачиваться с настройкой \\геЬраск на сервере, мы можем скопировать файл логотипа !о§о.зу§ в папку риЬНс, а затем заменить код в компоненте Арр кодом из листинга 10.28. Листинг 10.28. Новый код компонента Арр // йпрогЁ 1одо Ёготп './1одо.зуд' йпрогЁ ’./Арр.сзз' ГипсЁЁоп Арр () { геЁигп ( <сИу с1аззМате="Арр"> <Иеас1ег с1аззКате="Арр-Ъеадег"> <1тд згс="/1одо.зуд" с1аззЫате="Арр-1одо" а1Ё=’'1одо” /> <Р> ЕсЦё <сос!е>згс/Арр.зз</сос1е> апд зауе ёо геТоад. </р> <а с1аззЫате="Арр-11пк" ЬгеЁ="Р1ЁЁрз: / / геасЁ ] з. огд” ЁагдеЁ="_Ыапк" ге1="поорепег погеЁеггег" Ьеагп КеасЁ </а> </Ъеас1ег> </с1гу> ) } ехрогЁ деГаи1Ё Арр Далее снова выполним сборку приложения и перезапустим сервер: $ уагп ЬиНд $ поде зегуег Теперь наше одностраничное приложение должно отображаться правильно, как показано на рис. 10.23. Но нам нужно выполнить еще один шаг. Отрисовка одностраничной версии нашего приложения осуществляется кодом из файла 8гс/шс!ех.]8 (листинг 10.29). Листинг 10.29. Код файла 5Гс/|пе1ех/1$ для отрисовки одностраничного приложения КеасЁООМ.гепдег( <КеасЁ.ЗЁГ1СЁМоде> <Арр /> </КеасЁ. 5Ёгд.сЁМос1е>, досишепЁ. деЁЕ1етепЁВу1с1 (' гооё ’)
Еби 5 гс/Арр. ] 5 апс! зауе (о ге1оаб. 1_еагп КеасГ Рис. 10.23. Теперь приложение отображает изображение 8\/С правильным образом Этот код также будет исполняться, даже когда мы обращаемся к приложению через сервер для отрисовки на стороне сервера. В таком случае браузер загружает пред- варительно отрисованную версию веб-страницы, а затем загрузит код 1ауа8спр1 для одностраничного приложения. При исполнении кода одностраничного приложения будет исполнен код из файла шс!ех.]$, приведенный в листинге 10.29. Браузеру все еще нужно загружать и исполнять код 5ауа8спр1, чтобы сделать интерфейс инте- рактивным. Метод кеасбоом. гепаег может заменять все наше предварительно отри- сованное НТМЬ-содержимое, даже когда в этом нет надобности. Поэтому после замены вызова этого метода вызовом метода ВеасбООМ.Ъудгабе НТМЬ-содержимое в модели ООМ будет меняться только тогда, когда оно отличается от НТМЬ- содержимого в виртуальной модели ПОМ. Содержимое нашей отрисованной на стороне сервера страницы должно быть таким же, как и содержимое виртуальной модели ПОМ. В результате метод Ьудга^е не будет обновлять элементы страницы, а просто присоединит к ней набор слушателей событий, чтобы сделать страницу интерактивной. Таким образом, у нас теперь есть приложение с отрисовкой на стороне сервера. Но загружается ли такое приложение сколько-либо быстрее? Самый простой способ проверить время загрузки страницы — выполнить для нее оценку производительности Ы^ИгЬоизе в браузере. Как упоминалось ранее, средст- во [л^ЫКоизе проверяет производительность, доступность и целую кучу других характеристик. Оно сможет предоставить нам показатели, при помощи которых мы в состоянии сравнить производительность наших двух версий приложения. Когда мы выполнили эту проверку на нашем инструментальном ноутбуке, обраща- ясь к обычному серверу Кеас1 для разработки, встроенному в средство сгеа^е-геась-
арр, мы получили оценку производительности величиной 91 из 100 возможных и время РСР, равное 1,2 секунды (рис. 10.24). Это неплохая оценка производительности. Но, опять же, мы исполняем небольшое приложение КеасТ. Рис. 10.24. Базовая производительность приложения без отрисовки на стороне сервера А какие будут результаты проверки версии приложения с отрисовкой на стороне сервера? Ведь серверу все же придется потратить некоторое время для отрисовки кода КеасР Будет ли приложение исполняться сколько-либо быстрее? Результаты оценки производительности версии приложения с отрисовкой на стороне сервера показаны на рис. 10.25. Рис. 10.25. Базовая производительность приложения с отрисовкой на стороне сервера Общая оценка повысилась с 99 до 100. Значение времени для РСР уменьшилось до 0,6 секунды, т. е. вдвое по сравнению с исходной версией приложения. Кроме того, если при исполнении исходной версии приложения в браузере многократно нажи-
мать кнопку обновления страницы, то часто перед отображением обновленного содержимого будет кратковременно отображаться пустое окно белого цвета. Это происходит по той причине, что загруженное НТМЬ-содержимое состоит всего лишь из пустого раздела <сИу>, который браузер и отображает как белую страницу перед тем, как 1ауа8спр( сможет отобразить приложение. Сравним это поведение с поведением версии приложения с отрисовкой на стороне сервера. В этом случае повторные нажатия кнопки обновления вызывают только возврат в исходное положение изображения логотипа, и не наблюдается никаких белых экранов. Хотя на сервере все же происходит процесс отрисовки, который для строчной вер- сии НТМЬ-содержимого требует меньше времени, чем для отрисовки этого же на- бора элементов ВОМ. Обсуждение В этом разделе мы рассмотрели основные положения, как можно организовать от- рисовку приложения на стороне сервера. Подробности реализации для вашего при- ложения будут, скорее всего во многом другими, в зависимости от используемых в нем дополнительных библиотек. Например, в большинстве приложений Кеас! применяется маршрутизация. При ра- боте с пакетом геасг-гоигег в серверный код нужно будет добавить дополнительный код, чтобы иметь возможность выполнять отрисовку разных компонентов, в зави- симости от запрошенного браузером пути. Например, в листинге 10.30 показано возможное использование компонента згаПсКоиГег из пакета геасг-гоигег. Листинг 10.30. Возможное использование компонента ЗЬаПсВоиьаг гтрогГ { ЗГаГ1сКоиГег } Ггот 'геасГ-гоиГег-йот' сопзГ арр = гепс!егТо8Гг1пд ( <8ГаГ1сКоиГег 1осаГ1оп=(гед.ог1д1па1иг1) сопГехГ={{}}> <Арр /> </8Га€1сКоиГег> ) Компонент згаГхсКоигег выполняет отрисовку своих дочерних элементов для одного конкретного маршрута. В данном случае мы задали маршрут ог1д1па1икь из запроса браузера. Если браузер запросит маршрут /регзоп/1234, то компонент згаМсКоигег выполнит отрисовку компонента Арр для этого маршрута. Обратите внимание на то, что компонент згаЫсКоиГег также подойдет для передачи любого дополнительного содержимого остальному приложению. Для этой цели также можно было бы использовать контекст. В случае применения в разрабатываемом приложении разделения кода с помощью функции Кеасг.1агу следует иметь в виду, что этот метод не будет работать на сто-
роне сервера. Но, к счастью, существует способ обойти это ограничение. В частно- сти, вместо функции Кеасг.1аху можно использовать библиотеку ЬоадаЫе Сотро- пеп18, которая обеспечивает ту же функциональность, но может исполняться также и на стороне сервера. Таким образом, эта библиотека предоставляет все достоинст- ва отрисовки на стороне сервера вместе с преимуществами разделения кода. Но, как и с любым типом оптимизации, за отрисовку на стороне сервера нужно расплачиваться усложнением кода и дополнительной нагрузкой на сервер. Версию одностраничного приложения со статическим кодом можно развернуть на любом веб-сервере. Но приложения с отрисовкой на стороне сервера такой возможностью не обладают. Для них нужен сервер 5ауа$спр(, что может повысить стоимость ва- ших хостинговых услуг. Кроме того, если вы с самого начала знаете, что будете выполнять отрисовку на стороне сервера, вам, наверное, следует рассмотреть возможность использования такого инструмента, как Кагг1е или Кех1.]$, и применить этот подход с первого дня разработки своего приложения. Наконец, для повышения производительности веб-страницы существуют и альтер- нативные подходы, без необходимости прибегать к отрисовке на стороне сервера. Например, можно задействовать фреймворк Са18Ьу. Это средство может выполнять предварительную отрисовку страницу в процессе сборки приложения, предостав- ляя многие из преимуществ отрисовки на стороне сервера без необходимости соз- дания серверного кода. Исходный код данного рецепта можно загрузить на веб-сайте СпНиЬ по адресу Ьйр8://огеП.1у/0СГ^А. 10.8. Использование основных показателей веб-производительности ЗАДАЧА Намного важнее иметь рабочий и легкочитаемый код, нежели высокооптимизи- рованный код. Как упоминалось ранее, оптимизация всегда имеет свою цену. Но если в приложении возникают заметные проблемы с производительностью, чрезвычайно важно обратить на них внимание и исправить их как можно быстрее. Большая часть интернет-торговли полагается на покупки случайными посетителя- ми веб-магазина. Но если посетитель вашего веб-сайта должен ожидать его загруз- ки слишком долго, то он может уйти и больше никогда не вернуться. Разработчики часто отслеживают производительность серверов с помощью средств отслеживания (называющихся маяками — Ьеасоп8), встроенных в код. В случае на- личия проблемы с производительностью маяк выдает сигнал тревоги, и разработ- чик может исправить эту проблему, прежде чем она окажет отрицательное воздей- ствие на большое количество посетителей сайта. Но как можно встроить отслеживающий маяк в клиентский код?
РЕШЕНИЕ Для решения этой задачи мы рассмотрим, как отслеживать основные показатели производительности веб-приложений. Мы уже упоминали показатели производи- тельности в разделе 10.1. Это небольшой набор показателей производительности, измеряющих наиболее важные параметры приложения. Например, показатель СЬ8 измеряет величину смещения части страницы в процессе загрузки. Для отслеживания показателей веб-производительности подойдут разные инстру- менты, например, расширение браузера СЬготе Гл^ЬОюизе. Термин "основные по- казатели веб-производительности" (англ. м>еЬ уИа1$) должен напоминать вам об основных жизненных показателях (англ. чНа! таких как частота пульса и кровяное давление, поскольку они предоставляют нам информацию об исходной проблеме, которую необходимо решить. Если вы создали свое приложение при помощи средства сгеаЬе-геаЁ-арр, то, скорее всего, оно уже содержит код, который может автоматически отслеживать его ос- новные показатели веб-производительности. Например, файл згс/тс1ех.)8 содержит в конце вызов функции герогЪИеЬУзХаТз для создания отчета об основных показате- лях веб-производительности (листинг 10.31). ' Листинг 10.31. Вызов функции герогШвЬУхкаХз в конце файла 8гс/1пс1ех.|з хтрогх гтрогр 1трогР троги трогр КеасР Ргот 1геасР1 ВеасРООМ Ргот ' геасР-бот' '. /1пс1ех. сзз1 Арр Ргот './Арр' герогРИеЬУ1Ра1з Г г от './герогРИеЬ71Ра1з' НеасЁЭОМ.гепдег( <КеасЁ. ЗёгтсЬМойО <Арр /> </КеасЁ. 31:г1сгМобе>, досишепЁ.де^ЕТетеп^ВуТб('гооё') ) герог1:КеЬ71Ра1з () Функции герог^иеЬх/ИаТз можно передать функцию обратного вызова, которая позволит отслеживать различные показатели в процессе исполнения приложения. Например, передадим ей сопзоТе.Ход: герогЁИеЬУ1Ёа1з(сопзо1е.1од) В результате в консоли Зауа8спр1 браузера будут выводиться показатели веб- производительности в виде последовательности объектов Д8ОИ (рис. 10.26). Но отслеживание веб-производительности в действительности так не осуществля- ется. Лучше отправлять данные обратно на сервер для хранения. Например, можно отправлять конечную точку АР1-интерфейса методом розт, как показано в листин- ге 10.32.
|М1| Н11*1лд Таг ир<1ж1е «1дпж1 Тгав МН... >(лмы “ПТа", м1м.' 8895.5Ы99991125, <№!№ 8995.58999801125, еиГг1вК Лггау(1), 18; 'у1-1819375289115-175в74299423а”} Млат,- "ГСР", чо1ив/ 12343.999899999397, 4а1<:«> 12343. .>90990999397, вл1г1м.' ЗгглуЦ), 18; ' 91-1613375289114-8443971Ш89Г} *{пт; "«О*, »1ис/ 12.884999994848473, 4»1Га> 12.8о4999у94848473, «л2г2ал Лггау(Ц, 18: ’,у1-181»175289114-6129994й43559я} Цпяао; “1СР", га!иа: 12343.99, 4(Ия> 12343.99, «тГг1«: Аггяу/1}, 18: "И-1629575219114-890425305741Т'} Рис. 10.26. Основные показатели веб-производительности, выводимые в консоль ^уа8спр1 Листинг 10.32. Отправка конечной точки АР1-интерфейса методом розт герогХйеЬ71Ьа1з ( (ул.Ьа1) => { ТеЬсЬ(' /Ьгаск71Ьа1з', ( Ьоду: ^0К.зЬг1пд1Ту (У1Ьа1), теЬЬод: ’РОЗТ', кеераХгуе: Ыие, }) }) Многие браузеры оснащены встроенной функцией записи основных показателей веб-производительности. Если пользователь уходит со страницы, браузер отменяет обычные сетевые запросы, например запросы, связанные с вызовом функции ТегсЬ. Если учесть, что большинство важных параметров веб-производительности связа- ны с загрузкой страницы, было бы неразумно потерять эти показатели. По этой причине целесообразно рассмотреть использование функции пауТдаьог.зепдвеасоп, если есть такая возможность (листинг 10.33). Листинг 10.33. Использование функции п^ТдаЪог.зепдВеасоп герогЬИеЬУ1Ьа1з((У1Ьа1) => { 1Г (пауЬдаЁог.зепдВеасоп) { паутдаЬог.зепсВеасоп (' АгаскУ1Ла1з', З’ЗОЫ.з^гл.пда.Гу (уЮа!)) } е!зе { Се<:сЪ(' ДгаскУзЛаХз', { Ьоду: ^0Ы.зЬг1пд11:у (узЛа!), теЬЬод: 'РОЗТ', кеераНуе: Ьгие, }) } }) Если пользователь откроет страницу, но затем, не дождавшись ее загрузки, уйдет с нее, функция паугдаиог.зепдвеасоп сможет завершить отправку своего запроса розт.
Обсуждение Для записи основных показателей веб-производительности на рынке предлагаются коммерческие службы отслеживания, например зепйу.ю (М1р8://8еп1гуло). Если у вас установлена система мониторинга производительности, то ее можно будет настроить на использование основных показателей веб-производительности для предоставления дополнительных возможностей мониторинга производительности вашей системы. Наконец, рассмотрите возможность мониторинга основных показателей веб-про- изводительности с помощью средства Соо§1е Апа1убс8, как описывается на веб- сайте средства сгеа^е-геасЁ-арр (Ы1р8://огеП.1у/^1тХ1).
Прогрессивные веб-приложения Прогрессивными веб-приложениями (ПВП) называются веб-приложения, которые ведут себя подобно традиционным локально устанавливаем приложениям. Они могут работать в офлайновом режиме, интегрироваться с локальной системой уве- домлений, а также способны выполнять длительные фоновые процессы, которые продолжают исполняться даже после ухода посетителя с веб-сайта. Прогрессивны- ми эти приложения называются потому, что они плавно снижают уровень своей функциональности, если какая-либо возможность не поддерживается текущим браузером. В данной главе мы рассматриваем почти исключительно один аспект ПВА: сервис- воркеры. Иногда термин "прогрессивное веб-приложение" употребляется для опи- сания любого браузерного приложения с большим объемом возможностей .1ауа$спр1. Но на самом деле если приложение не использует сервис-воркеров, то это не ПВП. По сути, сервис-воркер — это локальный сервер для данного приложения. Сервер бэкенда представляет собой механизм распределения программного обеспечения и поставщика "живых" служб предоставления данных, но в действительности всем управляет сервис-воркер, поскольку он обеспечивает доступ к сети. Он принимает решения, каким образом удовлетворять сетевые запросы — обращением к бэкенд- серверу или к своему локальному кешу. При отсутствии сетевого подключения он может заменить сетевые ресурсы локальными заглушками. При работе в офлайно- вом режиме он даже может ставить в очередь запросы на обновление данных, а при возобновлении сетевого подключения синхронизировать их с сервером бэкенда. Эта тема хорошо подходит для последней главы, поскольку мы испытали наиболь- шее удовольствие от работы над ней. Сервис-воркеры являются одной из наиболее захватывающих возможностей современных браузеров. Надеемся, что вам понра- вится изучение подробностей их работы. 11.1. Создаем сервис-воркеры посредством МогкЬох ЗАДАЧА Прогрессивные веб-приложения могут продолжать функционировать даже в оф- лайновом режиме. Они могут кешировать любой требуемый код или содержимое,
которые не подвергаются воздействию обновления страницы пользователем. Они могут исполнять фоновые операции независимо от кода, исполняемого в браузере. Все это возможно благодаря использованию в ПВП сервис-воркеров. Сервис- воркер — это что-то наподобие веб-воркера. Веб-воркер представляет собой фраг- мент кода .1ауа8спр1, который выполняется в отдельном потоке от кода ]а\а8сг1р(, исполняющегося в веб-странице. А сервис-воркеры — это специализированные веб-воркеры, которые могут перехватывать сетевой обмен между веб-страницей и сервером, что дает им громадный контроль над зарегистрировавшей их страницей. Сервис-воркер можно рассматривать как некий тип локального прокси-сервиса, доступного после отключения от сети. Наиболее часто сервис-воркеры применяются для кеширования содержимого на локальном компьютере. Браузеры кешируют большую часть отображаемого ими содержимого, но сервис-воркер делает это намного более агрессивно. Например, принудительное обновление в браузере часто заставляет его загружать содержимое из сети, но не влияет на сервис-воркеры, независимо от количества таких обнов- лений. На рис. 11.1 показан принцип работы сервис-воркера. Код страницы Сервис-воркер >________________; Сервер Передай мне файл 1одо зуд ; 1 Передай мне файл 1одо.5уд ' ; Вот тебе файл 1одо.8Уд ; Вот тебе файл 1одо.5уд ; ; Передай мне файл 1одо.5Уд Вот тебе файл 1одо.$уд Передай мне файл 1одо.$уд ! Вот тебе файл 1одо.$уд _ Код страницы *______________> Сервис-воркер Сервер Рис. 11.1. Сервис-воркер перехватывает все сетевые запросы В случае, показанном на рис. 11.1, сервис-воркер кеширует файлы при их первой загрузке. При последующих запросах файла 1о§о.8У§ сервис-воркер будет возвра- щать этот файл из своего кеша, а не из сети. Подход, применяемый сервис-воркером для кеширования данных и принятия ре- шений о возврате их из кеша или сети, называется стратегией. В этой главе мы рассмотрим различные стандартные стратегии сервис-воркеров. Сервис-воркеры хранятся на сервере в виде отдельных файлов 1а\а8спр1, которые браузер загружает из определенного 1ЖЬ-адреса и устанавливает. Нет никаких за- претов на создание своего сервис-воркера и установки его в общедоступной папке
приложения, но подход с написанием сервис-воркера с чистого листа чреват не- сколькими проблемами. Прежде всего, задача создания сервис-воркера кошмарно трудная. Сервис-воркеры не только содержат сложный код, но также имеют замысловатые жизненные цик- лы. Чрезвычайно легко ошибиться и получить сервис-воркер, который не будет за- гружаться или будет кешировать не те файлы. Что еще хуже, можно создать такой сервис-воркер, который полностью изолирует ваше приложение от сети. Кроме того, сервис-воркеры можно использовать для предварительного кеширова- ния кода приложения. Для приложений Кеас1 это просто фантастическая возмож- ность. Вместо загрузки нескольких сотен мегабайтов кода ДахаБспр! сервис-воркер может возвратить его за долю секунды из локального кеша. Это означает, что при- ложение может запуститься почти моментально даже на маломощном устройстве с сетевым подключением плохого качества. Но кеширование кода тоже не так просто осуществить. Предположим, что наше приложение Ксас1 содержит следующие сгенерированные файлы с кодом 1ауа!5спр1 (листинг 11.1). Листинг 11.1. Файл приложения с кодом ЛауаЗспр! $ 1з ЬиИд/зкаИс/^з/ 2.сП0ба!Ь5. скипк.]з 2 .с!106а1Ь5. скипк. ]3 .шар 3.9е7 9Ь28 9.скипк.]з.тар тахп.О 95е14 с4.скипк.]з.тар гип11те-та1п. Ы75с5с19. з з.тар 2. с1106аГЬ5. скипк. ] з. ЫСЕ№Е. 1x1 3.9е79Ь289.скипк.дз тайп.095е14с4.скипк.д з гипР1те-та1п.Ы75с5с19. ]3 $ Чтобы выполнить предварительное кеширование этих файлов, сервис-воркер дол- жен знать их названия, поскольку он будет загружать файлы в фоновом режиме, даже до того, как браузер их запросит. Поэтому созданный вручную сервис-воркер должен будет содержать названия всех файлов, которые он будет предварительно кешировать. Но что получится, если вы слегка измените исходный код, а затем снова выполните сборку приложения (листинг 11.2)? Листинг 11.2. Файлы Лауа$сг1р1 слегка модифицированного приложения $ уагп гип ЬиНб $ 1з ЬиИб/зкаГхс/зз/ 2. с! 10 ба 1Ь5. скипк. ] з 3.9е79Ь289.скипк.дз.тар гипк1те-та1п. Ы75с5с19. ] з .тар 3.9е79Ь289.скипк.}3 гипк1те-та1п.Ы75с5с19. ]з $ 2. сГ106а1Ь5. скипк. зз.тар та!п.15Ьббсс7.скипк.дз.тар 2. сП 0 ба 1Ь5. скипк. ] з. ЫСЕ№Е. кхк та!п.Г5Ьббсс7.скипк.з з
Как видим, файлы Дауа$спр1 новой версии сборки будут иметь другие названия. Это означает, что вам нужно будет обновить сценарий сервис-воркера последними версиями сгенерированных названий файлов. Так как же можно создать стабильные сервис-воркеры, которые всегда синхрони- зированы с последней версией кода приложения? РЕШЕНИЕ Поставленную задачу можно решить при помощи набора инструментов от компа- нии 6оо§1е, называющегося УУогкЬох (1Шр8://оге11.1у/9дРХЬ). Этот набор инстру- ментов позволяет генерировать сервис-воркеры, содержащие актуальную инфор- мацию о самых последних версиях файлов приложения. Набор ХУогкЬох содержит коллекцию стандартных стратегий для обработки под- робностей общих случаев использования сервис-воркеров. Например, выполнить предварительное кеширование приложения можно посредством всего лишь одной строки кода в УУогкЬох. Чтобы разобраться с работой набора ХУогкЬох, начнем с исследования примера приложения, окно которого изображено на рис. 11.2. Раде 2 УИйщг* 1г> раде 21 Ей» $ г- / Арр д 5 вгк!5ЭТВШ теЬай Рис. 11.2. Демонстрационное приложение содержит две страницы Это простое двухстраничное приложение на основе стандартного приложения, сге- нерированного средством сгеа1:е-геас±-арр. Мы создадим сервис-воркер, который будет выполнять предварительное кеширование всего кода приложения и всех его файлов. Начнем с установки нескольких библиотек из ХУогкЬох: $ уагп адд ыогкЬох-соге $ уагп асМ иогкЬох-ргесасМпд $ уагп аск! ыогкЬох-гоиЁ1пд Назначение каждой из этих библиотек будет рассмотрено в процессе создания на- шего сервис-воркера. В приложении создадим для сервис-воркера новый файл 8етсе-\уогкег.]8 (лис- тинг 11.3). Этот файл можно разместить в той же самой папке, что и остальные файлы кода приложения.
Листинг 11.3. Содержимое файла веплсе-ууогкег.р для сервис-воркера хтрогк { сИепЁзСХайп } Ёгот ' могкЬох-соге1 гтрогЁ { ргесаскеАпйКоиЁе } Ёгот 'могкЬох-ргесасЪЁпд1 сИепЁзСЁаЁтО ргесасЬеАпбКоиЁе(зе!Ё.___ИВ_М7Ш1ГЕЗТ) При создании сервис-воркера вручную этот файл нужно поместить в папку, где хранится другое статическое содержимое. Например, в приложении сгеаЁе-геасЁ- арр это будет папка риЬНс. Наш сервис-воркер будет выполнять предварительное кеширование всего кода приложения. Это означает, что он будет автоматически кешировать все файлы С88, Дауа8спр1, НТМЬ и изображений, входящие в состав приложения. Наш сервис-воркер вызывает функцию сПепСзеХайп библиотеки иогкЬох-соге, кото- рая сделает его контроллером всех клиентов в пределах его области видимости. Клиентами являются веб-страницы, а областью видимости — любая веб-страница, НКЕ-адрес которой находится в том же пути, что и сервис-воркер. Набор ХУогкЬох создаст наш сервис-воркер с путем Ьйр8://Ьо8(/8егУ1се-\Уогкег.]8, означая, что он бу- дет контроллером всех страниц, адрес которых начинается с кЦр8://Ио81/. Функция ргесасЪеАпдКоиЁе будет заниматься всеми непривлекательными подробно- стями процесса предварительного кеширования. Она создает и управляет локаль- ным кешем, а также перехватывает сетевые запросы на файлы приложения, загру- жая их из локального кеша, а не из сети. Сервис-воркеры работают только при их загрузке через протокол НТТРЗ. Но ! большинство браузеров позволяют исключение этому правилу для сайтов, загру- маемых из адреса 1оса11ю81. В целях безопасности браузеры не будут исполнять —X, сервис-воркеры в приватных вкладках. После создания сервис-воркера его нужно зарегистрировать из основного кода приложения. Регистрация — это сложный процесс, но, к счастью, практически поч- ти всегда одинаковый. Создав код регистрации для одного приложения, его можно использовать без изменения для регистрации других приложений. Кроме того, если для создания приложения применяется шаблон сга-Ёетр1аЁе-рма, то он сгенерирует код регистрации для вас1. Тем не менее полезно знать подробности процесса регистрации. Это позволит вам получить представление о жизненном цикле сервис-воркеров, что, в свою очередь, намного облегчит понимание кажущегося странным поведения, возникающего после развертывания приложения. В основной папке приложения создайте файл ге^181ег\Уогкег.]8, содержащий код, приведенный в листинге 11.4. 1 См. раздел 11.2.
Листинг 11.4. Файл гедЫегУ\/огкег.|5 для регистрации сервис-воркера сопзГ гедхзГег = (раГЪТоЮогкег, опТпзГаП, опПрбаГе, опЕггог) => { // Ие иШ игхГе ЬЫз сос!е зЬогГ1у } сопзГ гедГзГегйогкег = () => { гедхзГег( '/зегУ1се-ыогкег.]з', (гед) => сопзо1е.1пГо('ЗегуГсе могкег ГпзГаНесГ, гед), (гед) => сопзоГе.1пГо('ЗегуГсе иогкег ирдаГес!', гед), (егг) => сопзоГе.еггог('ЗегуГсе могкег ГаПеб', егг) ) ) ехрогГ деГаиГГ гедхзГегИогкег Пока ЧТО оставим функцию гедхзГег пустой. В приложении функция гедхзгегиогкег будет вызываться в файле тс1ех.]8, как пока- зано в листинге 11.5. Листинг 11.5» Вызов функции гедхвГех^бгкег нпрогГ КеасГ Ггот 'геасГ' хтрогГ ВеасГООМ Ггот 'геасГ-дот' ГтрогГ '. /1пс1ех. сзз' 1трогГ Арр Ггот './Арр' ГтрогГ гедГзГегКогкег Ггот './гедГзГегМогкег' ВеасГСОМ.гепйег( <ВеасГ. ЗГг1сГМос1е> <Арр /> </ВеасГ. ЗГг1сГМос1е>, доситепГ. деГЕ1етепГВу!с1 (' гооГ') ) гедГзГегИогкег() Функция гедГзГегкогкег вызывает функцию гедГзгег, передавая ей путь нашего сге- нерированного сервис-воркера: зетсе-ууогкег^з. Теперь можно приступать к созданию функции гедхзгег (листинг 11.6). Листинг 11.6. Код функций гедхзГег сопзГ гедгзГег = (раГЪТоИогкег, опТпзГаП, опЦрдаГе, опЕггог) => { 1Г ( ргосез5.епу.МСЮЕ_ЕГП/ === 'ргодисПоп' && 'зегутсеМогкег' 1п паугдаГог ) { сопзГ риЬИсПг! = пен ПР.Ь ( ргосезз. епу. РПВЫС_ПВЬ,
иЁпбом. 1осаЁ1оп. ЬгеГ ) И (риЬИсОгЁ.огЁдЁп !== мЁпдом.1осаЁ1оп.ог1д1п) { геЁигп } // Сюда вставляется код для загрузки и регистрации } } В ней мы сначала проверяем, что приложение находится в рабочем режиме и что браузер может исполнять сервис-воркеры. Слово "прогрессивное" в названии про- грессивное веб-приложение означает, что прежде чем использовать возможность, мы всегда должны проверять ее наличие. Почти все браузеры (за исключением 1п1егпе1 Ехр1огег) поддерживают сервис-воркеры, но при отсутствии такой под- держки браузером мы можем полностью пропустить загрузку сервис-воркера. В таком случае приложение не сможет функционировать в офлайновом режиме, но в остальном должно работать должным образом. Код также содержит дополнительную проверку на исполнение по указанному адре- су рцвыс_цкь приложения, чтобы избежать междоменных проблем, которые возни- кают при загрузке кода из сетей распределения содержимого2. Здесь мы создаем упрощенную версию кода из библиотеки сга-Ёетр1аЁе-рма. Допол- нительная информация предоставляется на веб-сайте ОнНиЬ по адресу кИрз:// огеНЛу/ЛКЛЕО. Теперь можно загрузить и зарегистрировать сервис-воркер, как показано в листин- ге 11.7. Листинг 11.7. Загрузка и регистрация сервис-воркера сопзё гедЁЗЁег = (раЁйТоИогкег, опТпзЁаИ, опЦрдаЁе, опЕггог) => { И ( ргосезз.ет7.МОВЕ_ЕЫ7 === 'ргодисЁЁоп' && 'зегУ1сеИогкег' 1п паухдаЁог ) { сопзё риЬИсОг! = пеы ШЬ( ргосезз. епу. РОВЫС_иКЬ, ы1пс1ои. ЁосаЁЁоп.ИгеЁ ) 1Г (риЫ1сСг1.ог1д1п !== м1п6ои.1осаЁ1оп.ог1д1п) { геЁигп } иЁпскж.аскЗЕуепЁЬЁзЁепег (' 1оасР , азупс () => { Ёгу { 2 Здесь мы создаем упрощенную версию кода из библиотеки сга-Ёешр1аЁе-риа. Дополнительная инфор- мация предоставляется на веб-сайте СйНиЬ по адресу 11Цр8://оге11.1у/дК5Е0.
сопзЬ гед!з1:гаД1оп = амазЛ пау!да1:ог.зегу1сеИогкег.гед1з12ег ( ргосезз. епу.РСВЫС_иКЪ + ра^ЪТоИогкег ) // Сюда вставляется код для проверки хода исполнения } са!;сИ (егг) { 1Г (опЕггог) { опЕггог(егг) } ) }) ) ) Когда мы знаем, что веб-страница загрузилась, можно зарегистрировать сервис- воркер, передавая функции пау1да<;ог.зегУ1сеИогкег.гед15Ёег его полный 1_1КЬ-адрес: Ьйр8.7/ко8(/§егу1се-\уогкег.] 8. Функция возвращает объект гедгзтгаМоп, который можно использовать для отсле- живания и управления сервис-воркером. Например, при помощи объекта регистра- ции можно узнать об обновлении или установке сервис-воркера, как показано в листинге 11.8. Листинг 11.8. Использование объекта гедхзъгаиоп для получения информации об установке или обновлении сервис-воркера сопзё гедхзЁег = (раЁМоИогкег, оп1пзЁа11, оп11рс1аЁе, опЕггог) => { И ( ргосезз. ет/.ЬЮСЕ_Е№7 === 'ргодисМоп' && 'зегу1сеИогкег' 1п паугдаЁог ) ( сопзё риЬИсПг! = пен ШЬ ( ргосезз. ет/. РиВЪ1С_ШЪ, М1пс1ом. 1осаЁ1оп.кгеЁ ) 1Г (риЫ1сиг1.ог1д1п !== м1пс1ом. 1осаЁ1оп.ог1д1п) { геЁигп ) м1пбом.ас(с1ЕуепЁЬ1зЁепег (' 1оасГ , азупс () => { Ёгу { сопзё гед1зЁгаЁ1оп = ама1Ё пауЁдаЁог.зегухсеИогкег.гедЁзЁег( ргосезз. ет/.РивЫС_ШЪ + раЁИТоИогкег ) гед13ЁгаЁ1оп.опирс1аЁеЁоипс1 = () => { сопзё могкег = гед13ЁгаЁ1оп.1пзЁа111пд 1Г (могкег) { могкег.опзЁаЁесИапде = () => { 1Ё (могкег.ЗЁаЁе === 11пзЁа11ес1') {
1Г (пау1даТог.зегу1сеИогкег.сопТго11ег) { (опПрбаТе) { опПрбаТе (гед!з1:гаР1оп) } } е1зе { 1Г (опТпзТаИ) { оп1пзБа11 (гедхз^гаМоп) } } } } } } } саТсИ (егг) { (опЕггог) { опЕггог(егг) } } }) Обработчик опирдаЁеГоипс! исполняется, когда браузер начинает устанавливать сер- вис-воркер. После того как браузер установит сервис-воркер, можно проверить, продолжает ли исполняться предыдущий сервис-воркер, вызвав для этого функцию пау1даСог.8егу1сеИогкег.соп!:го11ег. Если нет, значит, это установка нового сервис- воркера, а не обновление старого. Способ обновления сервис-воркеров является, пожалуй, одним из их наиболее трудных для понимания аспектов. Если страница уже находится под управлением сервис-воркера, то браузер поставит нового сервис-воркера в состояние ожида- ния. Это означает, что новый сервис-воркер не будет делать абсолютно ничего до тех пор, пока не прекратит работу старый сервис-воркер. Сервис-воркер пре- кращает работу тогда, когда пользователь закрывает все управляемые этим вор- кером страницы. В результате после обновления сервис-воркера новый код не бу- дет исполняться, пока вы не закроете и снова не откроете страницу. Этот процесс может сбивать с толку любого разработчика, выполняющего ручное тестирование какой-либо возможности сервис-воркера. Прежде чем приступать к созданию приложения, нам нужно выполнить настройку инструментальных средств для преобразования нашего файла исходного кода 8егу1се-\уогкег.]8 в плотно упакованный код сценария сервис-воркера. Для сборщика пакетов ШеЬраск нужно установить подключаемый модуль ыогкЬох- кеЬраск-рХидхп: $ уагп хпзТаИ -Б иогкЬох-иеЬраск-рТидтп
Для приложений, созданных при помощи средства сгеаъе-геас^-арр, устанавли- вать и конфигурировать модуль \Л/огкЬох \Л/еЬраск Р1ид1п не нужно, поскольку это средство автоматически устанавливает и конфигурирует данный модуль. Теперь добавим в файл конфигурации \уеЬраск.сопГщ.)8 код из листинга 11.9. Листинг 11.9. Код для добавления в файл конфигурации шеЬраск.сопНд^е сопзк { 1пдес1:Мап1{ез1: } = геди!ге (' иогкЬох-иеЬраск-р1ид1п') тобиТе.ехрогкз = { р1ид1пз: [ пем 1п]ескМап1ГезЁ({ зыЗгс: './згс/зегУ1се-могкег.]з', }), ], } Согласно указаниям данной конфигурации сборщик пакетов ХУеЬраск создаст сер- вис-воркера из файла 8гс/8етсе-\уогкег.]8. Также он создаст в собранном приложе- нии файл а88е1-тагнГе81.]80п, содержащий список всех файлов приложения. Инфор- мация в этом файле будет использоваться сервис-воркером при предварительном кешировании приложения. Теперь можно создать само приложение, исполнив следующую команду: $ уагп гип ЬиИс! В результате в папке Ьш1с1 будет создан файл сервис-воркера 8егуюе-\уогкег.]8, а также файл а88е1-татГе8к]8оп (листинг 11.10). ! Листинг 11.10. Содержимое папки ЬыИ после создания приложения аззек-тапгГезк.]зоп 1одо192.рпд зегухсе-могкег.^з.тар Гау1соп.1со тапЦгезк.]зоп зкаЁхс гпдех.Ькт! гоЬокз.кхС 1одо512.рпд зегухсе-ыогкег.]з Файл а88е1-тап1Ге81.]8оп будет содержать код, наподобие показанного в листин- ге 11.11. Листинг 11.11. Содержимое файла ааае^тапИеа^боп { "Шез": { "тахп.сзз": "/зЁакхс/сзз/тахп.8с8Ь27сЕ.сЬипк.сзз", "тахп.дз": "/зкакхс/^з/тагп.Е5Ь6бсс7.сЬипк.^з",
"тайп. ]з .шар": "/зкаИс/]з/та!п. к5Ь66сс7. скипк. зз .тар", "гипкгте-тагп.]з": "/зкак1с/]з/гипк1те-та1п.Ь175с5с19.дз", "гипкгте-тагп. ]з .тар": "/зкакгс/]з/гипГ1те-тад.п.Ь175с5й9.33.тар", "зкак1с/]з/2.сПОбакЬЬ.скипк.]з": "/зЁак1с/]з/2.й106акЬ5.скипк.]з", "зкакгс/дз/2.6106а1Ь5.скипк.]з.тар": "/зкак^с/^5/2.61ОбаГЬ5.скипк.^з.тар", "зкакгс/дз/З.9е79Ь289.скипк.]з": "/зкак1с/]з/3.9е79Ь289.скипк.]з", "зкак1с/]з/3.9е79Ь289.скипк.^з.тар": "/зкакгс/дз/З.9е79Ь289.скипк.дз.тар", "гпбех.ккт!": ”/1п6ех.ккт1", "зег71се-могкег.д з": "/зегухсе-иогкег.]з", "зегухсе-могкег.]з.тар": "/зегугсе-иогкег.]з.тар", "зкакхс/сзз/тагп.8с8Ь27сГ.скипк.сзз.тар": "/зкаМс/сзз/тагп. 8с8Ь27ск. скипк.сзз.тар", "зкакгс/]з/2. 6106акЬ5. скипк.з з. ЫСЕ№Е. кхк": " /зкаЫс/ ] з /2.610 бакЬ5. скипк. ] з. ЫСЕЫ8Е. кхк ", "зкак1с/те61а/1одо. 6се24с58. ЗVд": "/зкак1с/те61а/1одо. бсе24с58. зуд" }, "епкгурогпкз": [ "зкак1с/]з/гипк1те-та1п.Ы75с569. ^з", "зкакгс/] з/2. 6106а1Ь5. скипк. д з", "зкакхс/сзз/тахп.8с8Ь27с{.скипк.сзз", "зкакгс/д з/тагп. Г5Ь6бсс7. скипк. ] з" ] } Теперь можно исполнять приложение. Но мы не можем просто запустить на испол- нение сервер для разработки как обычно: $ уагп гип зкагк Такой запуск приложения возможен только в режиме разработки, и сервис-воркеры не запустятся. Но нам нужно запустить сервер на содержимом папки ЪшИ. Легче всего сделать это, установив пакет зегуе глобально, а затем исполняя его по папке ЬшИ, как показано в листинге 11.12. Листинг 11.12. Установка и запуск пакета зегуе $ прт 1пзка11 -з зегуе $ зегуе -з ЬиНс1/ I Зегугпд! - Ьоса1: кСЁр://1оса1козг:5000 - Оп Уоиг Мекногк: кккр://192.168.1.14:5000 Сергей 1оса1 аййгезз ко сИрЬоагс!!
Опция -з предназначена для исполнения одностраничных приложений. Если сервер не сможет найти требуемый файл, то он возвратит файл Ьш1с1/тдех.Ьйп1. Теперь запустим браузер и откроем страницу по адресу ЬНр://1оса1Ьоз1:5000. Запустив таким образом приложение, откройте окно инструментов разработчика, а в нем вкладку АррКсайоп. Здесь в разделе 8егу>се ХУогкегз должно отображаться исполнение сценария 8егу1се-\уогкег.)8 (рис. 11.3). |ц\ » О. ЗеагсЬ О О 1оса№оз1:5000 Ес1|1 5 гс/Арр. ] 5 апс! зауе (о ге1оас1 (У ф 1пцме1ог Сопао1а О (Шдищаг () 81уйЕШ1ог О РагГоппапса О Малину АррИсвИоп » [П X б? Зегике №икег$ В Магён! Беттсе УУогквгз 1оса№ов1:6000 Црйаай в Мау 2021,16:63:04 # МГк'кв-^Лгкаг.)»1-" в ПипгНпд Л* Орал аЬоик ЛвЬиДОпф Гог ЗегИсе Когквга кот о(Ьвг ОотЫла ипгадШаг Рис. 11.3. Приложение с установленным и исполняющимся сервис-воркером Сервис-воркер загружает все файлы приложения в локальный кеш, в результате чего при следующей загрузке страницы данные файлы будут взяты из локального кеша, а не по сети с сервера. Это можно наблюдать, если в окне инструментов раз- работчика переключиться на вкладку №Ь*огк, а затем перезагрузить страницу (рис. 11.4). Сервис-воркер предоставит все сетевые ответы, за исключением тех, которые вы- ходят за рамки его области видимости. Любой файл уровня сайта, а не страницы, например значки /ахчсоп, будет и дальше загружаться обычным способом с сер- вера. Итак, сервис-воркер возвращает файлы из локального кеша. В браузере СЬготе содержимое локального кеша можно просмотреть на вкладке АррПсайоп, а в Р не Гох — на вкладке 8<ога§е (рис. 11.5).
О? О (паросюг И Соп«о1е О ОаЬиддаг {} 5»у1в ЕбИог 0 V Н11ег Ийка ан НТМЦ СЗЗ ХНА Роп1> кладе» Ме<На №5 (Жмг 51„ М._ Оота1п ГПа ЕЗ СЕТ Й К>саЖоМ:50_. 1 ез СЕТ й кса1Ко»« 50™ гла!п 8с8Ь2 ( сКипк см ЕЗ СЕТ й к>са1По*(:50.. 2 <110ва!Ь5 сЪипЦв ЕЗ СЕТ Й кха1Ьо«1.50 «па1пТ5Ь6всс7.с1шпк.}с ЕЗ СЕТ й кюа!кювХ 50™ кодо.Все24с5в «уд ЕЗ СЕТ й кс НМ 50 >одо102 род ЕЗ СЕТ й ЬсайювТбО (аукопко С]) РаНогтапс» О Матогу НаШгагк » |Я •* X II О. ® □ ОваЬ • СасЬа НоТЪгаНЛпд $ $ 1пН1а™ Т-. ТОТпвиггоЛ 5» < Лоси™ К_. еагИсе «гаНиг 2<В мук™ СОТ •отукаакхкм во. КСПр» )» авгука итЛшг 1В[ сапка ншкаг 2.Г ктд «уд млНса мюЛсаг 25 кпд р™ свешай 52 кпд г сас>1а<1 37 Рис. 11.4. После обновления страницы все файлы предоставляются сервис-воркером [У О 1лврас1<и |Г1 Сои вок О ОеЬиддег {} 51у1е Ес1№ог О Рвг№гтапс> ф Метогу Зигада )>> У СасКа 51огада V РШвг Неп» у @ ЪНр /ДосаЖовС 5000 Яи№- * 5ШиС Г~1 игагкЬйк рг< сасЪа~*2--ЬОр-;/ЬсаИюсЬ!>М>0/ Я Ы1р7/1оса1Ко51 5000Лп<1«х №пГ?_ЖЦ_Н ОК * 0 СооИаа М1р7/1оса11и>М 5000/СШ1С/С*«/ПЬ'П.ВС _ ОК * ЕЗ 1тй»4 ОВ * Г~I Соса! 31огвда ► П $«м1оп 51огада М1р7/Ьса1ЬоМ:6000/Ма11с/№2 4106аН> ОК Ънр //1оса1ЬоМ-50(Ю/Яа11С а/3 Ве7М>2 ОК ЬНр.//1оса1Ьо«».5000;*1аис>|«/та1П »5Ь6.„ ОК ЬНр-ДОосаЖо т 5000 ма№ /гилНте т „ ОК М1р.У/1оса1ЬоМ.5000/*1а1|С/те<11а/кюо.8. ОК Рис. 11.5. Содержимое локального кеша браузера ЯгеГох Кеш содержит копии не всех файлов приложения, а только тех, которые были за- прошены приложением. Таким образом предотвращается загрузка ненужных фай- лов, и файлы загружаются в кеш только тогда, когда браузер или код приложения запросит их. Поэтому при первоначальной загрузке приложения кеш может быть пустым. За- грузка зависит от того, когда активируется сервис-воркер. Если страница загружена до активирования сервис-воркера, то он не будет перехватывать сетевые запросы и кешировать ответы на них. В результате, чтобы началось кеширование, нужно будет перезагрузить страницу. Чтобы убедиться в том, что файлы действительно поставляются сервис-воркером, можно остановить сервер и обновить веб-страницу. Вы должны увидеть, что даже при отсутствующем сервере страница загружается как обычно (рис. 11.6). Теперь приложение Кеас1 следует рассматривать как локальное, а не сетевое при- ложение, поскольку оно обслуживается сервис-воркером, а не сервером бэкенда. Оно даже позволит выполнить переход на вторую страницу (рис. 11.7). Использование в приложении разделения кода может отрицательно сказаться на ,.А|к офлайновой функциональности. Если код для отображения второй страницы хра- нился в отдельном файле ^а8спр1, который не был загружен при первоначаль- X ном запуске приложения, браузер не сможет возвратить его из локального кеша. Он станет доступным лишь после посещения браузером страницы в режиме он- лайн, когда возобновится доступ к серверу.
<-) -> С © О 1осаЖо81:5000 О, ЗеагсИ 1И\ » Еда 5гс/Арр. ]5 апс! зауе 1о ге!оас1. К о 1шр*ст<х О Сопмй о Мод* о 81у1« ЕсМсг О РвПоппапм О Ммпогу №№могк » а •• X Й ?РтегЦЯ1* II 0, в С Оч»Ь|» с*сЬе Но ТТкоКИпц; о А1 НТМЬ СЗЗ 79 ХНЯ Еогй« кпадм Мм»а ИЗ ОШег М— Оогап ЕЛ» 1пШ* Т... Тгмийегн! зъ < ЕД БЕТ ЙкхаПшНЗО / Ооси Ь . *шу!св жЛ* 20 I НЭ БЕТ В ккаВкпЕВО- т*1п &с8Ь27с( сЬипк см Му1в ей •*»1еа м«1ии во. < БД БЕТ вЬеЫЬмНЮ 2 «М0в»ПЛ сЬшк.^ «ег1р1 мпЛе» мгккг № < БД БЕТ ДЬс4кМП.Б0- ПМ«п75ЪМсс7с1шкф »ег1р1 1» М<Ч1С*1М>НМГ 2.1, 1 ЕД БЕТ В ЬсЫКоИЗО- ЮдоВе»24с5&»д кпд >уд млЛсомюЛиг 23 । ЕД БЕТ 0 ЬеоЖаМ 60— 1адо1В2 (М>д кпд р_ с*сЪ*О 52 < ЯТ) БЕТ 0 кдеЙимЛ 50_ Тамкоп5со кпд к. 37 1 0 7гж>им» 177 84к8/1ММкВ1гап«й<гм! ЯпШгЗМпи ООМСмшМЕоМкО’ЗВ2 Ьвй 402 т» Рис. 11.6. Даже при отсутствии сервера страница обновляется без проблем №е!соте Ш раде 2! Яе1ит Рис. 11.7. При отсутствии сервера можно даже переходить с одной страницы на другую Пока мы находимся на второй странице, мы можем разобраться с текущей пробле- мой с сервис-воркером. Убедитесь в том, что сервер не работает, и перейдите на вторую страницу. Страница должна загрузиться нормально. Далее перезагрузите страницу. Теперь вместо второй страницы браузер должен отобразить страницу ошибки (рис. 11.8).
Д РгоЬйт кжйлд С ф ® 1оса»ЬозГ 5000/рад*2 - ® * Ч ЗеагсИ К\ О □ □ В □ » ипаЫе Ю соппес! ЯгаТох сап'1 еаиЫкЬ а сопглсНоп 1о Же еегуег а* 1оса1Ноя1:5000. ТЬе *)1е соиИ Ье ГетрогапТу игммаПаЫе от (оо Ьиву. Тгу адат т а (еж тотепТа. Н уои аге ипаЫе <о 1оай апу радев, сЬеск уоиг сопчхЛег'е пе№огк соппесЯоп И уоиг сотрсЛег от леЬмгк 1а рго!вс1вД Ьу а ЯгемаН от ргоху такс виге Жа1 Яге(ох « регт№е<1 Ж всеем Же ЖеЬ. Рис. 11.8. При отсутствии сервера вторая страница не будет перезагружаться Мы можем перезагружать домашнюю страницу в офлайновом режиме, так в чем же проблема с перезагрузкой второй страницы? Причина в том, что мы имеем дело с одностраничным приложением. При переходе на вторую страницу браузер не за- гружает новую веб-страницу с сервера. Вместо этого он использует интерфейс Н18Югу АР1, чтобы обновить 1ЖЬ в строке адреса, а затем модифицировать модель ООМ, чтобы отобразить вторую страницу. Но при перезагрузке страницы браузер отправляет серверу новый запрос ресурса по адресу Ы1р://1оса1Ьо81:5000/ра§е2. Когда сервер доступен, он возвращает содержи- мое файла тдех.Ьгт! для всех запросов страниц, а далее уже маршрутизатор Кеас1 отрисовывает компоненты, чтобы создать представление второй страницы. Но при недоступном сервере этот процесс разваливается. Сервис-воркер не сможет ответить на запрос ресурса Ы1р:/Лоса1Ьо$(:5000/ра^е2, используя кешированные данные, поскольку кеш не содержит ничего для второй страницы. Поэтому сервис- воркер перенаправляет этот запрос серверу, который, как было сказано, в настоя- щее время недоступен. В результате возвращается страница с сообщением об ошибке. Эту проблему можно исправить, добавив немного дополнительного кода в файл 8еп/1се-\уогкег.]8, как показано в листинге ПЛЗ3. 1 Данный код основан на коде примера сервис-воркера в библиотеке сга-(:етр1аЁе-риа, который мы рас- смотрим в следующем разделе.
; Листинг 11ЛЗ. Файл «егу/се-и'огкег./^ с дополнительным кодом 1трог{ { сИеп{зС1а1т } {гот ' могкЬох-соге' 1.трог{ { сгеаТеНапсИе гВоипс!То№1,, ргесасИеАпсШоиЁе, } {гот ' иогкЬох-ргесасЫпд' ипрогТ { гед1з{егКои{е } {гот 'иогкЬох-гоиМпд' сИеп{зС1а1т() ргесаскеАпЖоиТе (зе!{.__ЮВ_МАЫ1ЕЕЗТ) сопз{ {ИеЕх{епз1опКедехр = пем КедЕхр (1 / [л/?] +\. [л/] +$') гедхзТегкоиТе(({ гедиезТ, иг! }) => ( 1{ (гедиезТ.тобе !== 'пау1да{е') { геЁигп {а!зе } 1{ (иг1.ра{кпате.з{аг{зИ1{к(’/_')) { ге{игп {а!зе } 1{ (игХ.раТЬпате.таЕсЬ({НеЕхТепзхопВедехр)) { геТигп {а!зе } геТигп {гие }, сгеаЁеНапсИегВоипйТоиКЬ (ргосезз. епу. Р1]ВЫС_икь + ' /1пс1ех. И{т1')) Теперь мы регистрируем явный маршрут, задействовав модуль иогкЬох-гоиипд. Маршрут определяет, как сервис-воркер будет обрабатывать запросы для набора путей. Мы регистрируем новый маршрут, используя функцию фильтрации и обра- ботчик из кода предыдущего примера. Функция фильтрации передается в первом значении функции гедхзъегВоите. Если этот маршрут относится к данному запросу, то она возвратит значение {гие. Функция фильтрации в листинге 11.13 обрабатыва- ет все запросы перехода на новую веб-страницу. Таким образом, если открыть в браузере страницу по адресу ЬНр:/Лоса1Ьо81:5000/ или ЫТр://1оса1Ьо81:5000/ра^е2, то этот маршрут возвратит одну и ту же копию файла тдех.Ьйп!. Функция сгеаЕеНапбегВоипбТоикь создаст обработчик для обработки любого из этих запросов так, будто они были запросами ресурса по адресу 1Шр://1оса1Ьо$1: 5000/ш<1ех.Ыт1. Если перезагрузить приложение, когда открыта вторая страница, то сервис-воркер должен загрузить содержимое НТМЬ таким же образом, как он это делает, когда открыта домашняя страница. Давайте попробуем проделать это. Сохраним изменения в файле 8етсе-^огкег.]8 и снова выполним сборку приложения: $ уагп гип ЬиИс! Далее обеспечим наличие сервера: $ зегуе -з ЬиНд/
Если теперь открыть в браузере страницу по адресу ЬНр://1оса1йо$1:5000, то должна отобразиться домашняя страница приложения. Открыв окно инструментов разра- ботчика, мы должны увидеть, что была загружена новая версия сервис-воркера, но при этом его старая версия продолжает исполняться (рис. 11.9). Рис. 11.9. В приложении присутствует как старая, так и новая версия сервис-воркера Предыдущая версия сервис-воркера продолжает управлять приложением. Браузер установил новую версию воркера, но она находится в состоянии ожидания. Она не получит управления до тех пор, пока не будет удален старый сервис-воркер. Чтобы это случилось, нужно закрыть приложение, а затем снова открыть его (рис. 11.10). Рис. 11.10. Новый сервис-воркер активируется перезапуском приложения
Раде 2 Шсоте 1о раде 2! Йе1ит Рис. 11.11. После регистрации обработчика маршрута вторая страница без проблем перезагружается в офлайновом режиме Если теперь остановить локальный сервер и перейти на вторую страницу, то она должна перезагружаться без каких бы то ни было проблем (рис. 11.11). Обсуждение В этом разделе мы рассмотрели довольно глубоко процесс создания, регистрации и использования сервис-воркеров. В следующем рецепте мы рассмотрим, как можно автоматически генерировать большую часть этого кода на этапе создания приложе- ния. Но тем не менее полезно разобраться со всеми неприглядными подробностями работы сервис-воркеров. Это поможет вам понять жизненный цикл сервис-воркера: как происходит его установка браузером и как он активируется. Мы открыли для себя, что сервис-воркеры могут сбить с толку любого, кто тести- рует код вручную. Если браузер продолжает исполнять старую версию сервис- воркера, то, возможно, он будет продолжать исполнять старую версию приложе- ния. Эта неразбериха потенциально повлечет за собой проблему тестирования, по- скольку прежняя версия приложения может содержать какую-либо старую ошибку. Но поняв процесс загрузки новых сервис-воркеров и удаления старых, вы сможете быстро диагностировать проблему. Устарелые сервис-воркеры не представляют проблем для автоматизированных браузерных тестов, которые обычно начинают исполняться с чистого состояния, без кешированного содержимого или исполняющихся сервис-воркеров. Прогрессивные веб-приложения с сервис-воркерами представляют собой своеоб- разный гибрид локального и удаленного приложений. Сервер становится сервером распределения для локально установленного приложения. При обновлении прило- жения оно устанавливает в браузере свою новую версию, которая обычно не будет доступной, пока браузер не откроет ее. Теперь, когда мы подробно исследовали сервис-воркеры, можно перейти к рас- смотрению, как их можно быстро добавить в новое приложение. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу М1р$://огеП.1у/$и224.
11.2. Создание прогрессивных веб-приложений посредством Сгеа1е Кеас1 Арр ЗАДАЧА Для работы сервис-воркеров в приложении необходимо выполнить два условия. Первое, нужно иметь в наличии сам сервис-воркер. В разделе 11.1 мы рассмотрели, как создавать и управлять сервис-воркерами при помощи библиотеки ХУогкЬох. Второе, требуется код для регистрации сервис-воркера в приложении. Хотя созда- ние такого кода сопряжено со сложностями, однажды созданный регистрационный код можно использовать повторно для регистрации сервис-воркеров в других при- ложениях, внося в него небольшие модификации. Но по мере развития особенностей применения сервис-воркеров было бы неплохо избавиться от необходимости создавать регистрационный код для них самостоя- тельно. Как мы можем это сделать? РЕШЕНИЕ Поставленную задачу можно решить с помощью шаблонов средства сгеате-геаст- арр для создания приложений, содержащих сервис-воркеры. Даже если вы не планируете использовать средство сгеате-геаст-арр, лучше сначала создать приложение с его помощью, а затем добавить его код сервис-воркера в свое приложение. Мы вкратце рассмотрели тему шаблонов приложений в главе 1, где создавали при- ложения с использованием ТуреЗспр! при помощи средства сгеаре-геаст-арр. Шаб- лоны — это стандартный код, присоединяемый средством сгеасе-геась-арр при соз- дании нового приложения. Создать прогрессивное веб-приложение можно, выполнив следующую команду: $ прх сгеаРе-геасЁ-арр аррпате —РетрТаРе сга-Ретр1аГе-рма —* I Чтобы создать приложение с использованием ТуреЗспр!, замените сга-Тегср1аЁе- ' рыа на сга-гетр1аГе-риа-Ёурезсг1рЁ. В результате исполнения этой команды будет сгенерировано приложение КеасС в папке аррпате. Это будет практически точно такое же приложение, как и любое другое приложение сгеа^е-геаср-арр, но с установленными в нем библиотеками АУогкЬох, а также двумя дополнительными файлами исходного кода. В папке 8гс будет находиться файл сервис-воркера 8егу1се-^огкег.]8, содержимое которого при- ведено в листинге 11.14. I Листинг 11.14. Содержимое файла сервис-воркера еед</се-игогкег./$ ; ипрогр { сИепРзСТанп } Г гот ' иогкЬох-соге' хтрогр { Ехр1гаР1опР1ид1п } Ггот 'могкЬох-ехръгаЁгоп'
хтрогк { ргесаскеАпйВоиЕе, сгеаЕеНапсПегВоипсГГоШЕ, } Егот ' иогкЬох-ргесасЫпд' хтрогВ { гед1зЕегР.оиЕе } Егот 'могкЬох-гоиЕхпд' ЕтрогЕ { ЗЕаЕеИпНеЕ^аИдаЕе } Егот 'ыогкЬох-зЕгаЕедЕез' с!ЕепЕзС1аЕт() ргесаске/ЕпсЕВоиЕе (зе1Е._ИВ МАК1ЕЕЗТ) сопзЕ ЕНеЕхЕепзЕопВедехр = пей РедЕхр (' / [А/?]+\. [л/]+$') гедЕзЕегВоиЕе(({ гедиезЕ, иг! }) => { ЕЕ (гедиезЕ. то<Ее !== 'пауЕдаЕе') { геЕигп Еа1зе } 1Е (игЕ.раЕкпате.зЕагЕзИЕЕк('/_')) { геЕигп ЕаЕзе } ЕЕ (иг1.раЕкпате.таЕсЬ(ЕИеЕхЕепз1опКедехр)) { геЕигп ЕаЕзе } геЕигп Егие }, сгеаЕеНапсЦегВоипдТоВВЕ (ргосезз. ет/. РЕВЫС ИВЬ + '/Епс1ех.ИЕтЕ') ) гедЕзЕегВоиЕе( ({ иг! }) => игЕ.огЕдЕп === зе1Е.1осаЕЕоп.огЕдЕп && игЕ.раЕкпате.епдзИЕЕк (' .рпд'), пем ЗЕаЕеИИНеР.еуаПбаЕе ({ саскеИате: 'Етадез1, рЕидЕпз: [пем ЕхрЕгаЕЕопРЕидЕп({ тахЕпЕгЕез: 50 })], }) ) зеЕЕ.аддЕуепЕЪЕзЕепег('теззаде', (еуепЕ) => { ЕЕ ^епЕ.йаЕа && еуепЕ.с!аЕа.Еуре === 'ЗК1Р ИА1Т1ЫС') { зеЕЕ.зкЕрИаЕЕЕпд() ) }) Этот сервис-воркер очень похож на сервис-воркер, который мы создали в раз- деле 11.1. Папка зге также содержит еще один новый файл — 8егУ1сеАУогкегКе§181габоп.]8. По- скольку его размер очень велик, мы не приводим его содержимое здесь. Назначение этого файла такое же, как и назначение файла сценария ге§181ег\Могкег.]8, который мы создали в разделе 11.1, — он регистрирует наш сервис-воркер в качестве кон- троллера для приложения. Файл 8егу1се\УогкегКе§181гайоп.]8 будет очень полезен и для приложений, созданных без применения средства сгеаЕе-геасЕ-арр. Он содержит несколько дополнительных возможностей, которые отсутствовали в файле регист- рации, созданном нами в разделе 11.1. Например, предположим, что наше прило-
жение исполняется на 1оса1ко81. В таком случае оно аннулирует регистрацию всех сервис-воркеров, которые выглядят как принадлежащие другим приложениям. Это полезная возможность, если вы одновременно работаете над несколькими прило- жениями Кеас1. Хотя в приложении автоматически создаются сервис-воркер и код для его регист- рации, они не сконфигурированы для работы. Более того, в файле 1пдех.)8 приложе- ние на самом деле отменяет регистрацию всех сервис-воркеров, как показано в лис- тинге 11.15. Листинг 11.15. Отмена регистрации сервис-воркеров в файле I пс1ех.|5 । троге Кеасе Ггот 'геасе' троге КеасеВОМ егот ' геасе-сЗот' троге ' ./пкЗех.сзз' троге Арр Ггот './Арр' троге * аз зегу1сеИогкегКед1зегае1оп егот './зегУ1сеИогкегКед1зегае1оп' троге герогеИеЬ71еа!з егот './герогеИеЬ71еа!з' ВеасеЭОМ.гепсЗег( <Кеасе. 5ег1сеМос1е> <Арр /> </Кеасе.5ег1сеМос1е>, (Зоситепе. дееЕ1етепеВу1с1 (' гоое') ) зегу1сеЮогкегВед1зегае1оп.ипгедхзеег() герогеИеЬ\71еа1з() Поэтому, чтобы разрешить сценарий 8егу1се-\уогкег.]§, функцию иогкегВедхзегаехоп. ипгедхзеег нужно заменить функцией зегу3.сеИогкегКед1зегае1оп.гед1зеег. Функция зегу1сеИогкегВед1зегае1оп.гед15еег позволяет передавать в регистрацион- ный процесс функции обратного вызова, чтобы можно было отслеживать текущее состояние установки сервис-воркера. В частности, передается объект с функциями опТпзеа! и опирсЗаее, как показано в листинге 11.15. Листинг 11.15. Код для отслеживания текущего состояния сервис-воркера зегу1сеИогкегКед1зегае1оп.гедхзеег({ опТпзеаИ: (гедхзегаехоп) => { сопзоХе. 1од (' Зегухсе иогкег 3.пзеа11ес3') 1, опирсЗаее: (гедхзегаехоп) => { сопзо!е.1од ('Бегухсе могкег ирсЗаеесЗ') }, }) Функции обратного вызова полезны в тех случаях, когда нужно отложить исполне- ние какого-либо кода до тех пор, пока браузер не установит сервис-воркер, или
если необходимо исполнить код, когда новый сервис-воркер является обновлением предыдущего. Вызов функции опЫрбане означает, что новый сервис-воркер ожидает, пока будет удален предыдущий воркер. Обсуждение Материал раздела 11.1 поможет вам понять принцип работы сервис-воркеров. При разработке настоящего приложения код, созданный по шаблонам, будет намного более детализирован и насыщен возможностями. Исходный код данного рецепта можно загрузить на веб-сайте ОйНпЬ по адресу ЬЫр8://огеП.1у/ЬНАС9. 11.3. Кеширование сторонних ресурсов ЗАДАЧА Многие используемые в современных приложениях ресурсы предоставляются сто- ронними серверами, например платежные библиотеки, шрифты, изображения и т. п. Сторонние ресурсы могут потреблять большой объем пропускной способно- сти сети, и со временем их размер может увеличиваться. Если они предоставляются медленными серверами, то ваше приложение замедлится и у вас не будет никакого контроля над этим4. Можно ли использовать сервис-воркеры для кеширования сторонних ресурсов? РЕШЕНИЕ Сервис-воркеры имеют ограниченную область видимости, поскольку им разреша- ется управлять только страницами, имеющими такой же путь 1ЖЕ. Этим объясня- ется тот факт, что сервис-воркеры обычно размещаются в корневой папке прило- жения, что позволяет им управлять всеми его страницами. Но подобного ограничения не существует на 11КЕ-адреса, с которыми они могут устанавливать контакт. Они могут общаться с любой конечной точкой, с которой может общаться страница или код приложения. Это означает, что мы можем кеши- ровать ресурсы, поступающие со сторонних серверов. На рис. 11.12 показано окно приложения, использующего шрифт, загруженный с веб-сайта Ооо§1е Роп{8. Этот шрифт был добавлен в приложение при помощи следующих двух строк кода в заголовке страницы: 4 Недавно мы работали над проектом, полагавшимся на стороннюю платежную библиотеку. При тестирова- нии производительности приложения было обнаружено, что эта библиотека была намного медленнее, чем самый медленный компонент приложения, не просто из-за своего большого размера, но потому, что ее сер- веру часто требовалось несколько сотен миллисекунд, чтобы начать загружать код.
5ГС/Арр. ]5 н| /»»< *С 1г1<>1, |< № Т« «* Рис. 11.12. Это приложение использует шрифт Соод!е <1хпк ге!="ргесоппеср" ЪгеГ="1тРРр5://РопРз.д5РаРхс.сот"> <1хпк ЪгеР="к1РРрз: //РопРз. доодРеархз. сот/с5з2?Ратх1у=ГазсхпаРе&с1хзр1ау=8мар" ге1="зРу1езЬееР"> Первая ссылка импортирует шрифт веб-страницы, а вторая — связанную таблицу стилей. Чтобы этот шрифт можно было кешировать в приложении, сначала нужно зареги- стрировать сервис-воркер. Поскольку наше демонстрационное приложение было создано на основе шаблона библиотеки сга-Ретр1аРе-ръ/а, то для этого нужно лишь вызвать функцию гедхзрег в файле 1пс1ех.]8, как показано в листинге 11.16. Листинг 11.16. Вызов функции гедхзЪег для регистрации сервис-воркера хтрогр ВеасР Ргот 'геасР' хтрогр КеасРООМ Егот 'геасР-дот' хтрогр './хпдех.сзз' хтрогр Арр Егот './Арр' хтрогр * аз зегухсейогкегКедхзРгаРхоп Егот './зегухсеИогкегКедхзРгаРхоп' хтрогр герогРИеЬУхРаРз Егот './герогРКеЬ7хРа1з' КеасРООМ.гепдег( <КеасР. 5РгхсРМос1е> <Арр /> </КеасР. ЗРгхсРМос1е>, боситепР. деРЕ1етепрВу1с! (' гооР') ) зегУхсеИогкегВедхзЕгаЕхоп. гедхзРег () герогРИеЬУхРаРз()
Далее мы добавим в сценарий 8егУ1се-5Уогкег.]8, содержащий сервис-воркер прило- жения, несколько маршрутов. Сервис-воркер использует библиотеку \УогкЬох. Нам нужно кешировать таблицу стилей и загружаемый шрифт. Из раздела 11.1 мы узнали, что можно выполнять предварительное кеширование кода приложения. Это настолько распространенная задача, что библиотека ХУогкЬох позволяет делать это одной строкой кода: ргесасЬеАпс1КоиЕе (зе1Е._ИВ_МАЬЛЕЕЗТ) Эта команда создает маршрут, по которому будет локально кешироваться код при- ложения. Но для кеширования сторонних ресурсов нужно еще немного поработать. Сначала создадим маршрут для кеширования таблицы стилей (листинг 11.17). | Листинг 11.17. Создание маршрута для кеширования таблицы стилей гед151:егКои{:е ( ({ иг1 }) => иг1.ог1д!п === ' Ыл:рз://Еопгз.доод1еар15.сот' // ТОЕЮ Абб ЬапсЦег При вызове функции гедхзЕегКоиЕе ей нужно передать фильтрующую функцию и обработчик. Фильтрующая функция принимает объект запроса и возвращает значе- ние ггие, если обработчик должен его обработать. Обработчик представляет собой функцию, которая решает, каким образом выполнить запрос. Он может взять тре- буемые данные из кеша, передать запрос в сеть или использовать какую-либо ком- бинацию этих методов. Обработчики представляют собой довольно сложные функции, но они обычно дей- ствуют согласно определенной стандартной стратегии, например проверяют нали- чие требуемых данных в кеше, прежде чем загружать их из сети. Библиотека ХУогкЬох содержит функции, реализующие несколько стратегий. При загрузке таблицы стилей мы применяем стратегию зЬа1е-иЫ1е-геуа11баЪе (ЬНр8://огеП.1у/С11КЗ). Согласно этой стратегии, когда браузер хочет загрузить таблицу стилей 6оо§1е, мы отправляем запрос для данной таблицы стилей, а также проверяем наличие копии ее файла в локальном кеше. При отсутствии таблицы стилей в кеше мы ожидаем возвращения ответа на ее запрос из Сети. Эта стратегия полезна в случае частых запросов ресурса, при этом не придавая важности наличию его самой последней версии. Наличие кешируемой версии таблицы стилей будет предпочтительнее, поскольку это будет быстрее. Но мы также всегда запрашиваем новую версию таблицы стилей из Сети. Возвращенный ответ от Соо^е сохраняется в кеше, поэтому, даже если в этот раз мы не получим самую последнюю версию таблицы стилей, мы ее получим при ее следующей загрузке. Код обработчика для стратегии зьаХе-ыЬПе-геуаИааъе приведен в листинге 11.18.
Листинг 11.18. Реализация обработчика стратегии гЪа1е-кМ1е-геуа11с1а1:е гед!з1:егКои1:е ( ({ иг1 }) => иг1.ог1д1п === ' Ь1:11рз://Еоп125.доод1еар1з.сот', пей 5Ёа1еИЫ1еКеуа11даЁе ({ саскеЫате: 'з^уГезЬееЁз', }) ) Функция 8Ьа1еИМ1еНеуа11ааЪе возвращает функцию обработчика, которая сохраняет таблицу стилей в кеше з^уТезЪеегз. При запросе сторонних ресурсов может случиться, что запрос завершается неус- у пешно с сообщением об ошибке СОР8 (сгозз-опдт гезоигсе зЬаппд — совместное использование ресурсов между разными источниками). Подобная ошибка может —X- возникать даже при возвращении стороннего ресурса с действительным заголов- ком СОВ8, поскольку запрос сет исходит из кода Зауа8спр1, а не из кода НТМЬ- страницы. Эту ошибку можно исправить, присвоив значение апопутоиз атрибуту сгоззог!д1п элемента НТМ1_, использующего ресурс, например ссылке Ипк для загрузки таблицы стилей. С помощью этой же стратегии можно было бы загружать и шрифт Соо§1е. Но фай- лы шрифтов могут быть большого размера, а стратегия з1а1е-ыЫ1е-геуаИдате всегда загружает самую последнюю версию ресурса, даже если необходимо только обно- вить локальный кеш. Поэтому вместо нее мы задействуем стратегию сасЬе-Пгзт (ИНр8://огеП.1у/с8аа5). При этом подходе мы сначала проверяем наличие ресурса в кеше и при положи- тельном результате берем версию ресурса из кеша. В противном же случае отправ- ляется запрос на получение ресурса из Сети. Такая стратегия предпочтительна для ресурсов большого размера. Но она имеет свою отрицательную сторону: новая вер- сия ресурса загружается только при его отсутствии в кеше. Это означает, что новая версия может никогда не загрузиться. По этой причине стратегия сасЬе-Пгзг. обычно настраивается на запрос ресурсов из кеша только в течение определенного времени. Если обработчик находит ресурс в кеше, но он слишком старый, то обработчик запрашивает ресурс из сети и кеши- рует полученную новую версию. Сохраненный в кеше ресурс будет использоваться до тех пор, пока не истечет его время тайм-аута. Таким образом, в случае каких-либо временных проблем со сто- ронним сервером и получения ошибки 5 005, мы не хотим кешировать этот резуль- тат. Поэтому, прежде чем сохранять ответ в кеше, нам также нужно проверить ста- тус этого ответа. В листинге 11.19 приведен код для регистрации маршрута для кеширования шриф- та 6оо^1е. 51п(егпа1 Зегуег Еггог — внутренняя ошибка сервера
! Листинг 11.19. Регистрация маршрута для кеширования шрифта 6оод1е гедгзЁегКоиЁе( ({ иг1 }) => иг1.ог1д1п === 'ЬЁЁрз://ГопЁз.дз^аЁтс.сош', пем СасИеПгзЁ ({ саскеЫате: 'Гоп^з1, р1ид1пз: [ пей СасЪеаЫеКезропзеР1ид1п ({ зЁаЁизез: [0, 200], }), пей Ехр1гаЁ1опР1ид1п({ тахАдеЗесопбз: 60 * 60 * 24 * 7, тахЕпЁггез: 5, Н, ], }) Этот код сохраняет вплоть до пяти файлов шрифтов в локальном кеше Гопг:з. Кеши- рованные копии обладают тайм-аутом длительностью в одну неделю, а ответ ке- шируется только если он имеет статус 200 или о. Статус о означает проблему СОК8 с запросом, и в этом случае мы кешируем ответ. Ошибку СОК8 нельзя устранить, не выполнив соответствующие модификации кода, поэтому, сохраняя ответ в кеше, мы избегаем отправки дальнейших запросов, которые обречены на неуспех. Обсуждение Кеширование сторонних ресурсов может существенно улучшить производитель- ность приложения, но, что намного более важно, сделает ресурсы доступными в офлайновом режиме. Если приложение не может получить какой-либо космети- ческий ресурс типа файла шрифта, то это не слишком важно. Но если сторонний ресурс необходим для генерирования платежной формы, то было бы целесообраз- ным продолжить работу, даже если сетевое подключение временно отсутствует. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ьир8://огеН.1у/ОаЕУС. 11.4. Автоматическая перезагрузка воркеров ЗАДАЧА Метод обновления сервис-воркеров может сбивать с толку любого, кто применяет или тестирует использующее их приложение. Приложение загружает новую вер- сию обновленного сервис-воркера и присваивает ему статус 1п5<а11ес1, как показано на рис. 11.13.
к О <пцжи>г □ Сото** о Мшотог {} 5)у1оЕ<№и О РогТопт О Мелину М 0 5(огодо И АррВсоиол » Я X 5? Зетия «<иквг» Зегу се Жогкеге В МопИоМ ЮсаШооГЛООО Црййтей 9 Моу 2021, Ю 5Б 26 Цпгчд^Шг б? нпЛсе-унхквг )»'— • Виппкш -» в*гЛс»-*гогкйг )я • 1П5111Ш Л С реп «1ххЛ йеЬо^пд Тог &*пде4 У^огкегв йот от скип Рис. 11.13. После установки обновленного сервис-воркера его старая версия продолжает исполняться Старая версия сервис-воркера будет удалена только после того, как пользователь закроет приложение, а затем снова откроет его. В результате старый сервис-воркер удаляется, а новый выходит из режима ожидания и начинает исполняться (рис. 11.14). Рис. 11.14. Новый сервис-воркер начинает исполняться только после закрытия и повторного запуска приложения Сервис-воркер может кешировать код приложения, поэтому если он не перестанет исполняться, то не загрузит самый последний код с сервера. Поэтому может слу- читься, что будет активна старая версия всего клиентского приложения. Чтобы
запустить новую версию приложения, нужно перезагрузить страницу (установить новый сервис-воркер), а затем закрыть и снова открыть вкладку (удалить старую версию сервис-воркера и запустить на исполнение новую). Тестировщики быстро привыкают к этой слегка запутанной последовательности, чего нельзя сказать об обычных пользователях. В действительности то обстоятель- ство, что код обновится только на следующий раз, после того как станет доступ- ным, обычно не представляет большой проблемы. Но это может вызвать трудности в случае значительных модификаций кода, например при обновлении АР1-интер- фейса6. В некоторых случаях новый код требуется сразу же. Существует ли какой-либо способ немедленного удаления старых сервис-воркеров и обновления новой версии приложения? РЕШЕНИЕ Чтобы переключиться на новый сервис-воркер, требуется выполнить две операции: зарегистрировать сервис-воркер и перезагрузить страницу. Для приложений, соз- данных при помощи средства сгеаЁе-геасЁ-арр или использующих код из шаб- лона сга-Ёетр1аЁе-рма7, сервис-воркер регистрируется посредством функции зегуЁсеИогкегКедЁзЁгаЁЁоп.гедгзЁег. Для этого может использоваться код в файле 1пдех.]8, наподобие приведенного в листинге 11.20. Листинг 11.20. Код файла <пс1ех.|$, используемый для регистрации сервис-воркера йпрогЁ КеасЁ Ёгопп 'геасЁ' ЁтрогЁ КеасЁБОМ Ёгот 'геасЁ-бот' йпрогЁ ' . /1пс1ех. сзз' йпрогЁ Арр Ёгот './Арр' йпрогЁ * аз зегуЁсеИогкегКедЁзЁгаЁЁоп Ёгот './зегуЁсеИогкегКедЁзЁгаЁЁоп' йпрогЁ герогЁИеЬ\71Ёа!з Ггот '. /герогЁИеЬУЁЁаЁз' КеасЁВОМ.гепбег( <КеасЁ. 5Ёг1сЁМос1е> <Арр /> </ВеасЁ. 5ЁГ1СЁМос1е>, доситепЁ. деЁЕ1етепЁВу1с1 (' гооё ') ) зегу1сеИогкегВед15ЁгаЁ1оп.гедЁзЁег() герогЁИеЁЛ71Ёа1з () Даже созданный вручную код регистрации сервис-воркера, скорее всего, будет по- хожим на этот код. 6 Этого не происходит при использовании семантического управления версиями конечных точек АР1- интерфейса. 7 См. раздел 11.2.
Функции зегу1сеКогкегКед1з11гаМоп.гедхз^ег можно передавать две функции обрат- ного вызова, которые информируют об установке или обновлении сервис-воркера, как показано в листинге 11.21. Листинг 11^21. Передача параметров функции БегухсеИбгкегРед1зЪгаб1оп.гвд±зЪаг зегухсеИогкегКедхзбгабхоп.гедхзбег({ оп1пзба11: (гедбзбгаббоп) => {}, опСрдабе: (гедбзбгаббоп) => {}, }) Эти функции обратного вызова принимают в качестве параметра объект регистра- ции, который представляет собой обертку для сервис-воркера, только что установ- ленного или обновленного браузером. Установка сервис-воркера происходит при его загрузке. Но если старый сервис- воркер продолжает исполняться, то новый воркер будет в режиме ожидания до тех пор, пока не прекратится исполнение старого. Если сервис-воркер находится в режиме ожидания, вызывается функция опирсШе. Мы хотим автоматически удалять старый сервис-воркер всякий раз при вызове функции огШрдабе. Это позволит новому сервис-воркеру начать исполняться. Сервис-воркер— это специальный тип веб-воркера. Веб-воркер представляет со- бой фрагмент кода 1ауа8спр1, который выполняется в отдельном потоке от кода 5а\а8спр(, исполняющегося в веб-странице. Взаимодействие с веб-воркерами осу- ществляется посредством передачи им асинхронных сообщений. Сервис-воркеры могут перехватывать сетевые запросы, поскольку браузер преобразует эти запросы в сообщения. Таким образом, можно дать указание сервис-воркеру исполнить любой фрагмент кода, отправив ему сообщение. Чтобы сервис-воркер мог отвечать на сообщения, его можно снабдить слушателем событий получения сообщений: зеХб.асМЕуепбЫзбепег ('теззаде1, (еуепб) => { // Сюда вставляется код обработки сообщений }) Переменная зеИ содержит глобальный контекст для сервис-воркера (наподобие переменой мхпаои для кода страницы). Код страницы может отправить сообщение новому сервис-воркеру, сообщая ему, что нужно выйти из режима ожидания и заменить старый сервис-воркер, как пока- зано в листинге 11.22. Листинг 11.22. Отправка сообщения сервис-воркеру зегу1сеИогкегНед1збгаб1оп. гедбзбег ({ опСрдабе: (гедХзбгабгоп) => { гедХзбгабХоп.маХбХпд.розбМеззаде({ буре: 1ЗК1Р_КА1Т1ЫС' }) Ь })
Код гед1зЕгаЕ1оп.иаЕ1пд — ЭТО ссылка на сервис-воркера, а КОД ГедтзЕгаЕЕоп. матЁхпд.розимеззаде отправляет воркеру сообщение. После установки браузером новой версии сервис-воркера, но со старой версией воркера все еще исполняющейся, код приложения отправляет новому воркеру со- общение 5К1Р_ИА1Т1Ы6. Сервис-воркеры оснащены встроенной функцией зкЕрИаЕшд, которая удаляет ста- рый воркер, и позволяет новому начать исполняться. Таким образом, мы можем вызвать эту функцию в сервис-воркере, когда он получит сообщение 5К1Р_ИА1Т1М6, как показано в листинге 11.23. Листинг 11,23. Вызов функции екхрЯаЕЕпд в сервис-воркере зеЕЕ.аскПЕЛ/епЕЫзЕепег ('теззаде', ^епЕ) => { 1Е (еуепЕ.йаЕа && еVепЕ.(^аЕа.Еуре === '5К1Р_ИА1Т1ЫС) { зе!Е.зк1рКа1Е1пд() } }) Если теперь обновить приложение, то новый сервис-воркер сразу же заменит ста- рый. Теперь осталось выполнить последний шаг: нужно перезагрузить страницу, чтобы можно было загрузить новый код приложения через новый сервис-воркер. Это означает, что обновленная версия файла тбех.^ приложения будет иметь содержи- мое, приведенное в листинге 11.24. Листинг 11.24. Содержимое обновленного файла тс1ех.]8 йпрогЕ ВеасЕ Егот 'геасЕ' ЕтрогЕ КеасЕООМ Егот ’геасЕ-с!от' ппрогЕ './Епйех.сзз' ЕтрогЕ Арр Егот ' . /Арр' хтрогЕ * аз зегу1сеИогкегВед1зЕгаЕ1оп Егот './зегу1сеИогкегВед1зЕгаЕ1оп' 1трогЕ герогЕИеЬ71Еа1з Егот './герогЕИеЬУ1Еа1з' ВеасЕООМ.гепйег( <ВеасЕ. 5Егз.сЕМос1е> <Арр /> </КеасЕ. ЗЕг1сЕМос1е>, йоситепЕ. деЕЕ1етепЕВу!с1 (1 гооЕ') 5егу1сеИогкегВед1зЕгаЕ1оп.гедхзЕег({ огШрйаЕе: (гедЕзЕгаЕЕоп) => { гед1зЕгаЕ1оп.иа1Е1пд.розЕМеззаде({ Еуре: 15К1Р_ИА1Т1МС }) К1ПС10Ы. 1осаЕ1оп. ге1оа6 () ь }) герогЕИеЬ71Еа!з ()
После установки этой новой версии кода приложение будет автоматически обнов- ляться при каждом его изменении. Теперь на вкладке АррПсаНоп окна инструмен- тов разработчика вместо работающего старого сервис-воркера и ожидающего своей очереди нового будет отображаться только работающий новый воркер (рис. 11.15). Е<Й 5гс,Арр ^5 апд геоаа К о 1гир*с1ог (Ц СопаоН О О«Ьидо*г Яу1* ЕЛПог О РаЛогтапс» О М»тогу Н НапмИс 0 31огада Щ АДОкаьоп » |Я — X 5етс1 И бите В МагИаа! Вегасе кУогкегь к>са*1<нге000 ЦрйаШ 9 Мау 2021.10 Б9 Б4 * »«г*ке^огкег}я * ЯиппЬд Орал йЬосТ Тог $еп/ св УйзНееп 1гот 01 Ьм йотатв Рис. 11.15. Теперь новый сервис-воркер сразу же заменяет старый Обсуждение Перезагрузка страницы вызывает "моргание" страницы при загрузке нового кода. В случае большого приложения это может вызывать неприятное восприятие у пользователя, поэтому целесообразно спросить у пользователя, хочет ли он выполнить обновление приложения, прежде чем перезагружать его. Этот подход применяется компанией Стай для всех значительных обновлений. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу ЬИр8://огеП.1у/ЬЬа17. 11.5. Добавление извещений ЗАДАЧА Одно из преимуществ сервис-воркеров в частности и веб-воркеров в целом состоит в том, что они не прекращают исполняться, даже если пользователь ушел со стра- ницы. Воркер будет продолжать исполняться в фоновом режиме, пока работает браузер. Поэтому в случае медленного сервис-воркера можно уйти со страницы или даже закрыть вкладку, но воркер будет продолжать исполняться, пока не выполнит свою задачу.
Но как быть, если пользователь хочет знать, завершилось ли наконец выполнение фоновой задачи? Ведь сервис-воркеры не имеют никакого визуального интерфейса. Хотя они могут управлять веб-страницами, но не способны обновлять их. Веб- страница и сервис-воркер могут взаимодействовать посредством сообщений. С учетом отсутствия у сервис-воркеров визуального интерфейса, как они могут со- общать пользователю, когда произойдет что-либо важное? РЕШЕНИЕ Для решения данной задачи мы создадим возможность получения уведомлений от сервис-воркера. По нажатии кнопки наше демонстрационное приложение (рис. 11.16) начнет исполнять длительный процесс, занимающий около 20 секунд до завер- шения. ! С? йк © О ^5 1оса№к>б1:5000 — О ^0. ЗеагсЬ__________________| М\ О 3 ЕЗ В И » —Ч Е<М 5 гс/Арр. ] 5 апс! зауе (о ге1оай Рис. 11.16. Нажатие кнопки Оо з!оуу 1Ыпд запускает на исполнение длительный процесс Чтобы приложение смогло отправлять уведомление о завершении этого процесса, пользователь должен предоставить соответствующее разрешение (рис. 11.17). Если отказать в таком разрешении, то задача все равно будет исполняться в фоновом режиме, но пользователь не будет уведомлен о ее завершении. Извещения пользуются дурной славой. Обычно веб-сайт отправляет их пользова- телю в виде непрошеных рекламных сообщений. В целом лучше не спешить спра- шивать у пользователя разрешение, пока ему не будет очевидно, зачем оно требу- ется. Избегайте спрашивать разрешение для отправки сообщений сразу же после загрузки страницы, поскольку пользователь будет без понятия, почему вы хотите отправлять их.
Рис. 11.17. Для получения уведомлений о завершении исполнения задачи нужно разрешить их Сервис-воркер начнет исполнять некий код, который просто делает паузу длитель- ностью в 20 секунд, после чего отображается уведомление о завершении его рабо- ты (рис. 11.18). 51ои (Гилд 1!п15Ьес1! 1оса1Ьоз1:5000 Ио» де! оп шЛН уоиг 1Ив Рис. 11.18. Уведомление о завершении исполнения задачи Начнем разбираться, что представляет собой этот код. В компонент Арр мы добав- ляем кнопку для запуска фоновой задачи, но при этом делаем ее видимой, лишь удостоверившись, что браузер поддерживает сервис-воркеры (листинг 11.25). Листинг 11.25. Код кнопки запуска задачи ЁипсЁ1оп Арр() { сопзё зЁагЁТазк = () => { // Код для запуска задачи } геЁигп ( <с11у с1аззМате="Арр"> <Ьеас1ег с1аззКате="Арр-Ьеас1ег"> <1тд згс={1одо} с1аззМате="Арр-1одо" а1Ё="1одо" />
<Р> Е<Лё <сос!е>5гс/Арр. дз</сос1е> апб зауе ёо ге!оас1. </р> {'зегухсеИогкег' Ёп пауЁдаЁог && ( СЬиЁЁоп опСИск={зЁагЁТазк}>Оо з1ои ЁЫпд</ЬиЁЁоп> ) } </кеас!ег> </сНу> ) } Нажатие пользователем этой кнопки вызывает функцию зЁагЁТазк. В ней можно отображать сообщение с запросом разрешения показывать уведомления (лис- тинг 11.26). Листинг 11.26. Код функции вЁагЪТаак сопзё зЁагЁТазк =()=>{ ЫоЁШсаЁхоп.гедиезЁРеппЁззЁоп((репп1зз1оп) => { пау1даЁог.зегу1сеИогкег.геас1у.ЁЪеп( () => { сопзё поЁЁЁуМе = репп1зз1оп === 'дгапЁей' // Здесь начинается код задачи }) }) } Если пользователь дает разрешение, то строке регт1зз1оп присваивается значение дгапЁей, в результате чего константа поЁИуМе получает значение Ёгие. Мы можем исполнять задачу в сервис-воркере и сообщить ему, можно ли ему отправлять уве- домление о завершении выполнения этой задачи. Но мы не можем общаться с сервис-воркерами напрямую. Вместо этого нам нужно отправлять сообщения, поскольку сервис-воркеры исполняются в отдельном потоке от кода веб-страницы. Текущего сервис-воркера, управляющего страницей, можно определить при помо- щи свойства пауЁдаЁог.зегутсе юогкег.сопЁгоПег. Теперь мы можем отправить этому сервис-воркеру сообщение, как показано в листинге 11.27. Листинг 11.27. Отправка сообщения сервис-воркеру сопзё зЁагЁТазк =()=>{ ЫоЁ1Ё1саЁ1оп.гедиезЁРепп1зз1оп((регшЁззхоп) => { паУ1даЁог.зегУ1сеКогкег.геас1у.Ёкеп( () => { сопзё поЁЁЁуМе = репп1531оп === 'дгапЁесГ паV^даЁо^. зегуЁсейогкег. сопЁгоИег. розЁМеззаде ({ Ёуре: '00_ЗЬ0И_ТН1ЫС ,
поЁтЁуМе, }) }) }) } В нашем демонстрационном сообщении код сервис-воркера находится в файле 8етсе-\уогкег.]$. Его можно оснастить возможностью получать сообщения, доба- вив в него код обработчика события получения сообщения, показанного в листин- ге 11.28. Листинг 11.28. Код обработчика события получения сообщения зеЁЁ.адбЕуепЁТлзЁепег ('теззаде', (еуепЁ) => { 1Ё (еуепЁ.даЁа && еуепЁ. с)аЁа. Ёуре === ' ВО_8ЬОИ_ТН1МС') { // Сюда вставляется код для выполнения длительной задачи } }) В контексте сервис-воркера выражение зеи обозначает объект с глобальной об- ластью видимости. Оно эквивалентно выражению ишбои в коде веб-страницы. Следующим шагом эмулируем длительную задачу, вызывая функцию зе^тлпеоиЁ, которая просто ожидает в течение 20 секунд, после чего отправляет сообщение на консоль ДауаЗспр! в окне инструментов разработчика (листинг 11.19)8. I Листинг 11.29. Код эмулирования длительной задачи зеТЁ.аддЕуепЁЫзЁепег ('теззаде', (еуепЁ) => { 1Г (еуепЁ.даЁа && еуепЁ.даЁа.Ёуре === 'В0_5Ь0И_ТН1ЫС) { зеЁТЁюеоиЁ(() => { сопзо1е.1од (' 31ои ЁЫпд ЁЁптзИес!! ') // ТОГО: 5епс1 поЁ1Ё1саЁ1оп Неге }, 20000) ) )) Все, что осталось теперь сделать, — отобразить уведомление. Это можно реализо- вать при помощи метода зЬомЫоёШсэёхоп объекта гед1зЁгаЁ1оп сервис-воркера, как показано в листинге 11.30. 8 Сообщение сможет отобразиться только в браузере СЬготе. В браузере ГпеГох оно не будет отображаться, поскольку этот браузер не разрешает сервис-воркерам доступ к консоли 1ауа§спр1.
Листинг 11.30. Код для отображения уведомления зеТГ.адйЕуеп^ЫзЬепег ('теззаде', (еуеп!) => { И (еуеп1.с!аЁа && еуеп1.с!а1:а.1:уре === '00_5ЬСЖ_ТН1МС') { зеИЧтеои! (() => { сопзо1е.1од( ’51ом ЁЫпд ПпгзЪес!! ') 1Г (еуеп! ,с!а1а .поШуМе) { зе1^.гед1зЁгаМоп.зЪоиЫо11Г1са11оп('31ои 1к1пд Ип1зкед!', { Ьоду: 'Кои де! оп ы11± уоиг Ше', 1соп: '/1одо512.рпд', У1ЬгаГе: [100, 100, 100, 200, 200, 200, 100, 100, 100], // 1;ад: 'зоте-тб-И-уои-до-по^-иап^-дирИса^ез' }) } }, 20000) ) )) Обратите внимание на то, что прежде чем отображать уведомление, мы проверяем значение константы еуепь.ба^а.поЫеуМе, которую добавили в сообщение в коде веб- страницы. Уведомление принимает в качестве параметров заголовок и объект опций. Опции позволяют модифицировать поведение уведомления. В данном случае опции состо- ят из текста Ьобу, значка хсоп и последовательности вибраций угЬгаЁе. Если устрой- ство пользователя поддерживает опцию вибрации, то при отображении уведомле- ния устройство также должно вибрировать последовательность точка-точка-точка- тире-тире-тире-точка-точка-точка. Существует также и опция метки 1ад, которую мы закомментировали в листин- ге 11.30. Опция метки служит для однозначной идентификации уведомления, что- бы не допустить многократной отправки пользователю одного и того же уведомле- ния. Если эту опцию отключить, то каждый вызов функции зЪоиЫоъШсаиоп будет приводить к отображению уведомления. Чтобы испытать наш код в действии, нужно сначала выполнить сборку приложе- ния, поскольку сервис-воркеры могут исполняться только в рабочей версии прило- жения: $ уагп гип ЬиИс! Далее нам необходимо запустить сервер на содержимом сгенерированной папки Ьш1<1. Для этого установим модуль зегуе, а затем запустим его на исполнение: $ зегуе -з ЬиПс! Теперь откроем приложение по адресу ЬКр://1оса1Ьо$<:5000 и запустим длительную задачу, нажав соответствующую кнопку. После этого можно перейти на какую- либо другую страницу или вообще закрыть вкладку приложения, но запущенная
длительная задача будет продолжать исполняться. Остановить ее до истечения времени тайм-аута можно, только закрыв браузер. По истечении 20 секунд после запуска задачи в браузере должно отобразиться уве- домление наподобие показанного на рис. 11.19. 51еш Нйпд йп15Ье(1! к>са!Ьо11:5000 №>*г де1 сп уоиг Ше Рис. 11.19. Уведомление о завершении длительной задачи, отображаемое на компьютере Мас Наверное, многим хотелось бы исполнить это приложение на мобильном устройст- ве, чтобы проверить работу вибрации в уведомлении. Касательно этого следует иметь в виду, что сервис-воркеры активируются только при обращении к серверу -“АЛ > на 1оса1Ьо8( или через протокол НТТРЗ. Чтобы проверить приложение через прото- кол НТТРЗ, см. раздел 7.3 для запуска его на сервере. С учетом того, что уведомления могут отображаться после закрытия соответст- вующей страницы, целесообразно предоставить пользователю простой способ воз- врата обратно к приложению. Это можно реализовать, добавив в наш сервис-воркер обработчик события щелчка по уведомлению. Тогда при щелчке по уведомлению браузер отправит сервис-воркеру событие побШсаНопсИск, которое можно обрабо- тать при помощи обработчика, приведеного в листинге 11.31. Листинг 11.31. Обработчик события поЫГ±саЪ1опс1±ск зе1Г.ас1с1Еуеп1:Ь151:епег ('побтЕхсабхопсТхск', (еVеп^:) => { еуепк. поШкаНоп. с1озе () // ЗАДАЧА Возвращаемся обратно к приложению }) Уведомление МОЖНО закрыть, вызвав метод еуепб.поР1Исаб1оп.с1озе. Но как отпра- вить пользователя обратно к приложению КеасГ? Сервис-воркер может управлять несколькими вкладками браузера, которые назы- ваются его клиентами. Это вкладки, сетевые запросы которых перехватываются сервис-воркером. Список клиентов сервис-воркера можно получить из его объекта зеИ.сИепбз. Посредством метода орепИхпбои этого объекта можно открыть новую вкладку в браузере, как показано в листинге 11.32. Листинг 11.32. Открытие новой вкладки посредством метода орепЖпДок ! зеН.адбЕуепбЫзбепег ('поЫПсаМопсИск', (еуепб) => { еуепб. побШсабтоп. с!озе () И (зе1Е. сИепбз. орепИгпбои) ( зе1б.с11епб5.орепИ1пс1ои('/') } })
Теперь при щелчке мышью по уведомлению браузер откроет домашнюю страницу приложения Кеас1. Но мы можем сделать нечто лучшее. Если пользователь переключился на другую вкладку браузера, но приложение В.еас1 продолжает исполняться, мы можем пере- вести фокус на вкладку, отправившую уведомление. Для этого нам нужно получить массив всех вкладок, управляемых нашим сервис- воркером, и посмотреть, нет ли среди них с совпадающим путем. Если есть, то мы переключаем фокус на эту вкладку, как показано в листинге 11.33. | Листинг 11.33. Переключение фокуса на вкладку, издавшую уведомление зеРР.ас!с1ЕуепРР.РзРепег (1 поРРРРсаРРопсРРск', (еVепР) => { еVепР.поР^Р^саР^оп.сРозе () еуепР.иаРРЕпРРР ( зеРР.сРРепРз .таРскАРР ({ куре: ' мРпйои', }) .РЬеп((сРРепРЬРзР) => { сопзР геРигпРаРк = '/' сопзР РаЬ = сРРепРЫзР.РРпсЦ (Р) => { геРигп Р.иг! === зеРР.РосаРЬоп.огРдРп + геРигпРаРЬ }) 1Р (РаЬ && 'Росиз' 1п РаЬ) ( РаЬ.Росиз() } е!зе 1Р (зеРР.сРРепРз.ореп.ИРпдои) { зеРР. сРРепРз. орепИРпс!ом (геРигпРаРк) } }) ) }) Щелчок* по уведомлению возвращает нас на уже открытую вкладку, а не создает новую (рис. 11.20). Обсуждение Уведомления предоставляют отличный способ для информирования пользователя о важных событиях. Критическим аспектом использования уведомлений является разъяснение пользователю, почему он должен согласиться на получение уведомле- ний, а затем отправлять их только в случае важных событий. Исходный код данного рецепта можно загрузить на веб-сайте СйНиЬ по адресу Ьйр8://оге11.1у/7,ксгК.
Рис. 11.20. Щелчок по уведомлению переключает фокус на вкладку приложения, если она все еще открыта 11.6. Модификации в режиме офлайн посредством фоновой синхронизации ЗАДАЧА Предположим, что ваше приложение используется там, где отсутствует подключе- ние к сети, например, в поезде метро9. Предварительное кеширование кода означа- ет, что в таком случае с открытием приложения не будет возникать никаких про- блем. Также пользователь сможет перемещаться между страницами, и все будет выглядеть работающим должным образом. Но что произойдет, если пользователь выполнит какую-либо операцию, отправ- ляющую данные на сервер? Например, что если он попытается отправить сообще- ние? РЕШЕНИЕ Фоновая синхронизация — это способ постановки в очередь сетевых запросов при недоступности сервера с последующей их отправкой в дальнейшем, когда сервер становится доступным. Надо сказать, что все больше и больше метрополитенов оснащаются мобильными ретрансляционными станциями.
Рис. 11.21. При нажатии пользователем кнопки 5епс1 <1а(а (о зеп/ег демонстрационное приложение отравляет данные на сервер Наше демонстрационное приложение будет отправлять данные на сервер бэкенда при нажатии пользователем соответствующей кнопки (рис. 11.21). Прежде чем запускать приложение, нужно выполнить его сборку: $ уагп гип ЬиИс! Наш демонстрационный проект содержит сервер в файле зегуег/тс1ех.)8 (лис- тинг 11.34). Листинг 11.34. Код сервера для проекта сопзГ ехргезз = гедихге('ехргезз') сопзГ арр = ехргезз () арр.изе (ехргезз. дзопО ) арр.изе(ехргезз.зГаГ1с(’ЬиНд')) арр.розГ (' /епйроГпГ', (гедиезГ, гезропзе) => { сопзо!е.1од('Зегуег гесегуес! даГа', гедиезГ.Ьос!у) гезропзе. зепс! (' ОК') }) арр. ИзГеп (8000, () => сопзо!е. 1од (' ЬаипсИес! оп рогГ 8000'')) Сервер раздает содержимое из папки Ьипа, в которую публикуется сгенерирован- ный код. Он также отображает данные запросов розт, отправляемых по адресу ЬИр://1оса!1ю$1:8000/еп(1ро1п1.
Сервер запускается на исполнение следующей командой: $ пойе зептег Далее откроем в браузере страницу по адресу ЬНр://1оса1Ьоз1:8000 и несколько раз нажмем кнопку для отправки данных на сервер. В окне сервера должны отобра- жаться данные, показанные в листинге 11.35. Листинг 11.35. Данные, отправляемые на сервер бэкенда । $ поде зегуег ЬаипсЬед оп рогЕ 8000! Зегуег гесезлгес! даЕа { Е1ше1з: '2021-05-09Т18:59:37.2802* } Зегуег гесе^ед даЕа { ЕЕгпеТз: ’2021-05-09Т18:59:37.7202’ } Зегуег гесез^ед даЕа { ЕипеТз: 12021-05-09Т18:59:38.0642' } Зегуег гесехуед даЕа { Ез.те1з: '2021-05-09Т18:59:38.3522' } В листинге 11.36 приведен код, который отправляет эти данные на сервер. В нем используется функция ЕеЕсИ, которая при нажатии кнопки отправляет на сервер текущее время методом розт. Л истинг 11.36. Код для отправки данных на сервер 1трогЕ КеасЕ Егот 'геасЕ' з_трогЕ 1одо Егот './Еодо.зуд' ЕтрогЕ './Арр.сзз’ ЕипсЕЕоп Арр() { сопзЕ зепсЮаЕа =()=>{ сопзЕ орЕхопз = { теЕИоб: 'РОЗТ', Ьобу: д5ОЫ.зЕгз.пд1Еу({ Е1те1з: пей СаЕе() }), Иеадегз: { ' СопЕепЕ-Туре': ' арр1з.саЕз.оп/д зоп1, ЕеЕсЬ (' /епдроз.пЕ', орЕЕопз) геЕигп ( <617 с1аззЫате="Арр"> сЬеадег с1аззЫате="Арр-кеадег"> <з.тд згс={1одо} с1аззЫате="Арр-1одо'' а1Е="1одо" /> <р> ЕсЦЕ <собе>згс/Арр.дз</соде> апд зауе Ео ге1оад. </р> <ЬиЕЕоп опСИск={зепд0аЕа}>Зепд даЕа Ео зегуег</ЬиЕЕоп> </Ъеадег> </сНу> ехрогЕ с!еЕаи1Е Арр
Если теперь остановить сервер, то нажатие кнопки будет генерировать последова- тельность сообщений об ошибке сетевого запроса, как показано на рис. 11.22. Остановка сервера эмулирует ситуацию, когда пользователь временно теряет под- ключение к сети и пытается отправить данные из приложения. Рис. 11.22. При отсутствии сервера бэкенда сетевые запросы завершаются неудачно Указанную проблему можно исправить при помощи сервис-воркеров. Сервис- воркер способен перехватывать сетевые запросы, создаваемые веб-страницей про- грессивного веб-приложения. В предыдущих рецептах этой главы мы рассмотрели использование сервис-воркеров для обработки проблем с подключением к сети с помощью возврата версии файлов из локального кеша. В нынешней ситуации нам нужно обрабатывать данные, идущие в противоположном направлении: от браузера к серверу. Нам требуется сохранить в кеше запросы розт, которые приложение пытается пере- дать на сервер, а после восстановления контакта с сервером отравить ему эти за- просы. Для этого мы воспользуемся библиотекой иогкЬох-Ьаскдгоипд-зупс. Фоновая синхро- низация представляет собой АРЬинтерфейс для перенаправления сетевых запросов в очередь в тех случаях, когда отсутствует связь с сервером. Это сложный АР1- интерфейс, и не все браузеры поддерживают его. Библиотека иогкЬох-Ьаскдгоипд-зупс значительно облегчает работу с АР1-интерфей- сом фоновой синхронизации, а также позволяет применять его на браузерах, кото- рые не поддерживают его самостоятельно, например ЕпеГох. В нашем демонстрационном приложении код сервис-воркера находится в файле 8егУ1се-ууогкег.]8. Чтобы реализовать в сервис-воркере возможность фоновой син- хронизации, добавим в этот файл код, приведенный в листинге 11.37.
Листинг 11.37. Код для добавления возможности фоновой синхронизации ДпрогГ { МеГмогкОп1у } Ггот 'могкЬох-зГгаГедГез' 1трогГ { Васкдгоипб8упсР1ид1п } Ггот 'могкЬох-Ьаскдгоипб-зупс' // Сюда вставляется прочий код сервис-воркера... гедГзГегВоиГе( //епфоГпГ/, пен КеСиогк0п1у({ р1ид!пз: [ пей ВаскдгоипдЗупс₽1ид1п('епбРотпГОиеие!', { тахВеГепГ1опТ1те: 24 * 60, }), ], }), 'РОЗТ' ) Этот код регистрирует в сервис-воркере новый маршрут, указывая, как обрабаты- вать сетевые запросы по конкретным 1ЖЬ-адресам. В данном случае мы создаем маршрут для обработки всех запросов по адресу ЬПр:/Лоса1Ьо8Е8000/епдро1п(. Со- поставление адресов заданному пути осуществляется с помощью регулярного вы- ражения. Затем используется стратегия №1\уогк Оп1у (ЬИр8://оге11.1у/гЬдЬд), при которой браузер отправляет все запросы сервис-воркеру, а все ответы берутся из Сети. Но эта стратегия сконфигурирована для работы с подключаемым модулем фоновой синхронизации васкдгоипдЗупсРГидГп. Третий параметр в маршруте указыва- ет, что интерес представляют только запросы розт к концевой точке. Когда приложение отправляет запрос розт по адресу Ы(р://1оса1Ьо8(:8000/епЛро1п1, сервис-воркер перехватывает его. Далее сервис-воркер перенаправляет перехвачен- ный запрос на сервер и при успешной отправке возвращает полученный ответ веб- странице. В случае недоступности сервера сервис-воркер возвращает веб-странице сообщение об ошибке, а затем добавляет запрос в очередь епдРоТпгоиеие!, чтобы отправить его повторно. Библиотека У/огкЬох сохраняет очереди в индексируемых базах данных в браузере. Присвоенное переменной тахВеГепПопТше значение 24 * 60 сохраняет запросы в ба- зе данных вплоть до 24 часов. Библиотека могкЬох-Ьаскдгоипд-зупс повторно отправляет поставленные в очередь запросы при любой возможности, когда она полагает, что сервер может быть дос- тупным, например при возобновлении сетевого подключения. Также попытки повторной отправки предпринимаются каждые несколько минут. Если теперь перезапустить сервер и подождать около пяти минут, мы должны уви- деть, что неудачно завершенные запросы отображаются в окне сервера (лис- тинг 11.38).
Листинг 11.38. Отображение в сервере запросов, ранее завершившихся ошибкой $ пос1е зегуег ЬаипсКеб оп рог! 8000! Бегуег гесехуес! баба { !1те1з: Зегуег гесехуес! с1а!а { Мте1з: Яегтег гесегуес! йа!а { !1те1з: '2021-05-09Т21:26:11.0682' } '2021-05-09721:02:44.6472' } '2021-05-09721:02:45.6472' } Браузер СЬготе можно заставить отправлять запросы немедленно. Для этого в па- нели инструментов разработчика откройте вкладку АррПсайоп, выберите в ней требуемый сервис-воркер, а затем отправьте сообщение синхронизации по адресу могкЬох-Ьаскдгоипс1-зупс:епРРо1п!(2иеие1, как показано на рис. 1 1.23. Рис. 11.23. Принудительная синхронизация в браузере СЬготе Обсуждение Фоновая синхронизация — чрезвычайно мощная возможность, но ее реализацию следует хорошо продумать. Порядок, в котором код клиента отправляет запросы, не обязательно будет совпадать с порядком, в котором эти запросы обрабатываются сервером. Точный порядок, скорее всего, не будет иметь значения в случае создания посред- ством запросов розт простого набора ресурсов. Например, при покупке книг в ин- тернет-магазине точный порядок их приобретения не имеет значения. Но при создании зависимых ресурсов или множественных обновлений одного и того же ресурса10 нужно быть осторожным. Если, например, изменить номер кре- 10 При использовании АРЬинтерфейса на основе стиля КЕ8Т обновления, скорее всего, осуществлялись бы посредством запроса РУТ или РАТСН.
дитной карточки на 1111 1111 1111 1111, а затем на 2222 2222 2222 2222, то в зави- симости от порядка этих изменений конечный результат будет совершенно разным. Исходный код данного рецепта можно загрузить на веб-сайте бкНиЬ по адресу ЬНр*://ого1.1у/1\ЬЛЛУ 11.7. Добавляем специализированный установочный пользовательский интерфейс ЗАДАЧА Во многих отношениях прогрессивные веб-приложения ведут себя подобно прило- жениям, установленным локально. Их можно устанавливать вместе с другими при- ложениями на настольных компьютерах или мобильных устройствах. Многие брау- зеры позволяют создать ярлык приложения на текущем устройстве, чтобы его можно было запускать в отдельном окне. На настольных компьютерах есть воз- можность добавить ярлык в меню запуска. На мобильных устройствах ярлык при- ложения можно разместить на домашнем экране. Но многие пользователи просто не знают о возможности устанавливать прогрес- сивные веб-приложения. К тому же зачастую в интерфейсе, используемом в брау- зерах для обозначения возможности установки таких приложений (рис. 11.24), трудно отыскать требуемую опцию. Рис. 11.24. Прогрессивные веб-приложения устанавливаются посредством небольшой кнопки в строке адреса браузера Такой интерфейс объясняется стремлением разработчиков браузеров максимизиро- вать область экрана мобильного устройства, доступного для веб-страницы. Но если есть основания полагать, что локальная установка приложения может быть полез- ной для его пользователей, то его можно снабдить специальным установочным пользовательским интерфейсом. Но как именно это можно сделать?
РЕШЕНИЕ Некоторые браузеры" генерируют событие ЬеЕогеЕпзЕаПрготрЕ при обнаружении полноценного прогрессивного веб-приложения11 12. Это событие можно захватить и использовать для отображения вашего специаль- ного установочного пользовательского интерфейса. Начнем с создания файла Му1п81а11ег.]§, содержащего код компонента Му1пзЕаПег, приведенный в листин- ге 11.39. Листинг 11.39. Код файла Му1пз1а11ег.|$ йпрогЕ Кеасс, { изеЕЁГесЕ, изеЗЕаЕе } Егот 'геасЕ' сопзё Му1пзЕа11ег = ({ сИНдгеп }) => { сопзЕ [1пзЕа11ЕуепЕ, зеЕХпзЕаИЕуепЕ] = изеЗЕаЕеО изеЕЕЕесЕ (О => { ыЕпйом.адсЕуепЕЬЕзЕепег ('ЬеЕогеЕпзЕаИрготрЕ', (еуепЕ) => { еуепЕ.ргеуепЕСеЕаи1Е() зеЕЕпзЕаН^епЕ (еуепЕ) }) Ь []) геЁигп ( {1пзЁа11ЕуепЁ && ( <ЬиЕЕоп опСИск={ азупс () => { 1пзЕа11ЕуепЕ.рготрЕ() аматЕ 1пзЁа11ЕуепЁ.изегСЬоЕсе зеЕТпзЕаИЕуепЕ (пи11) }} 1пзЕа11 ЕЫз арр! </ЬиЕЕоп> )} {сЬНбгеп} ) } ехрогЕ беЕаиХЕ МуТпзЕаИег Компонент муТпзЕаПег выполняет захват события опЬеЕогеЕпзЕаНрготрЕ и сохраняет его в переменной ЕпзЕаНЕуепЕ. Затем при наличии этого события отображается спе- 11 На момент подготовки материала данной книги это событие поддерживали браузеры СЬготе, Ед^е и 8ат- 8ип§ 1пГетеЕ 12 Проверить, удовлетворяет ли ваше приложение требованиям прогрессивного веб-приложения, можно при помощи инструмента ГлёМюизе из набора инструментов разработчика СЬготе. Этот инструмент не только определяет, отвечает ли приложение заданным требованиям, но также в случае их неудовлетворения укажет соответствующие причины.
циальный пользовательский интерфейс, который в данном случае состоит из про- стой кнопки. Далее этот компонент можно вставить в разрабатываемое приложе- ние, например, как показано в листинге 11.40. Листинг 11.40. Пример вставки компонента мухпзЬаПег в приложение ГипсМоп Арр () { ге^игп ( <сНу с1аззЫаше="Арр"> <Му1пзЁа11ег> <Ьеас1ег с1а8зМате="Арр-Ьеас1ег"> <1шд згс={1одо) с1аззМате="Арр-1одо" а1Ё="1одо" /> <Р> ЕсШ: <сос1е>згс/Арр. ]з</сос1е> апс! заVе €о ге1оас1. </р> <а с1аззМате="Арр-11пк" ИгеГ="Ы:Ёрз://геасЁ]з.огд" Ёагде1:="_Ыапк" ге1="поорепег погеГеггег" Ьеагп Кеас1 </а> </Ъеас1ег> </Му1пзЁа11ег> </сНу> Теперь выполним сборку приложения и запустим его на исполнение: $ уагп гип ЬиНд $ зегуе -з ЬиПс! В результате в верхней части окна приложения должна отображаться кнопка для его установки 1п$<а11 Ина арр! (рис. 11.25). Данная кнопка не будет отображаться при исполнении приложения с использова- нием сервера разработки: $ уагп гип з!агЁ Это объясняется тем, что приложение считается прогрессивным веб-приложением только при наличии исполняющегося сервис-воркера, а сервис-воркеры могут функционировать только в рабочем режиме. При нажатии кнопки установки приложения компонент муХпз-ьаПег исполняет метод ЕуепЁ.ргошрЁ, который отображает обычное диалоговое окно установки (рис. 11.26).
В ЯевсгАор ф 1оса1По5Г5000 |1пв(а111йвнррР Ес(|1 5 ГС/Арр. ] 5 апб зауе Го ге1оас1 Ьеагп Деас! Рис. 11.25. Кнопка установки приложения отображается посередине вверху окна приложения Ес1|1 5 гс/Арр. ] 5 ап<1 еауе Го ге1оас! Рис. 11.26. Нажатие кнопки установки приложения открывает диалоговое окно установки
Если устройство уже установило приложение, то браузер не активирует событие опЬе^огетпзТаИргошрТ. Если пользователь решит установить приложение, запускается отдельное окно приложения13. На настольном компьютере может открыться окно проводника \У|П(1о\у$ (Ршбег в компьютере Мас), содержащее значок запуска приложения, которое можно добавить в меню запуска компьютера (рис. 11.27). На мобильных устройствах этот значок будет размещен на домашнем экране. Рис. 11.27. Браузер создает значок запуска для приложения Обсуждение Локальная установка — это замечательная возможность для людей, которые часто пользуются приложением. Судя по нашему опыту, многие пользователи не знают, что для некоторых сайтов доступна опция локальной установки, поэтому целесооб- разно добавить в приложение специальный интерфейс для его установки. Но при этом следует остерегаться, чтобы не создать назойливый интерфейс, если имеются основания полагать, что вероятные посетители вашего сайта будут случайными. Лучше всего не запускать окно запроса установки автоматически при загрузке страницы, поскольку это, скорее всего, будет вызывать раздражение у пользовате- лей и они больше не вернутся на ваш веб-сайт. Исходный код данного рецепта можно загрузить на веб-сайте (ЗйНиЬ по адресу М1р8://оге11.1у/ВЬтрс. 13 Тогда как приложение создает отдельное окно. >то окно удаляется при закрытии веб-браузера. При непо- средственном запуске приложения оно также запускает и веб-брау «ср. если он еще не запущен.
11.8. Предоставление ответов в режиме офлайн ЗАДАЧА Кешировать в приложении все сторонние ресурсы нежелательно, поскольку для этого потребуется слишком много места. Это означает, что иногда ваш код не смо- жет загрузить все требуемые ресурсы. Например, на рис. 11.28 мы видим, как при- ложение, которое мы создали в одной из предыдущих глав, отобразило несколько изображений, взятых со стороннего сайта. Рис. 11.28. Приложение отображает изображения, взятые с сайта ЬМр://р1С5ит.рЬо1о5 Для работы в автономном режиме можно при помощи сервис-воркера сохранить в локальном кеше весь код приложения. Но при этом сохранять так сами изображе- ния со стороннего веб-сайта нежелательно вследствие слишком большого их коли- чества. Это означает, что при отсутствии сетевого подключения работа приложения продолжится, но изображения будут отсутствовать (рис. 11.29). Было бы неплохо заменить отсутствующие сторонние изображения какой-либо ло- кальной версией. Таким образом, при работе в автономном режиме пользователь все равно будет видеть хоть какое-то изображение. Это частный случай общей задачи, в которой желательно заменить заглушкой не- доступный внешний файл большого размера, например видео- или аудиофайл или даже полностью веб-страницу.
Рис. 11.29. Приложение продолжает работать в автономном режиме, но без изображении РЕШЕНИЕ Решим поставленную задачу с помощью пары методов с применением сервис- воркеров, которые сообща будут возвращать локальную замену недоступного изо- бражения из кешированного файла. Предположим, что нам нужно заменить все недоступные изображения подстано- вочной картинкой, изображенной на рис. 11.30. ••• 1 Рис. 11.30. Подстановочная картинка для замены недоступных изображений Первым делом нам нужно проверить, что файл подстановочного изображения дос- тупен в локальном кеше. Мы добавим этот файл в используемые приложением ста- тические файлы, но мы не можем полагаться на то, что подстановочная картинка будет автоматически сохранена в кеше. При предварительном кешировании со- храняются файлы, загруженные с сервера. Подстановочное изображение не будет требоваться до тех пор, пока присутствует сетевое подключение, поэтому нам при- дется прибегнуть к методу так называемого разогрева кеша (сасЬе у/агтпщ) и за- грузить изображение в локальный кеш напрямую.
Далее, сразу же после установки сервис-воркера он исполнит определенный код. Для этого нам нужно добавить в него обработчик установки: зеХГ.асМЕуегЛЬгз^епег ('1пз1:а11', (еуеп!:) => { // Сюда вставляется код для кеширования файла изображения }) Мы можем явно открыть локальный кеш СаИЬаск, а затем добавить в него файл из Сети, как показано в листинге 11.41. ! Листинг 11.41. Добавление в кеш файла из Сети зеТ^.аск^епбЫзбепег (' 1пзба111, (еуепб) => { еуепб.магШпбИ ( сасЬез. ореп (' СаИЬаск') . ЬЬеп ((сасЬе) => { сасЬе. аде! (' /сотгпдЗооп. рпд ’) }) ) }) Данный метод позволяет сохранить файлы в локальном кеше при установке прило- жения. Это полезная возможность при наличии файлов, которые требуются в авто- номном режиме, но не загружаются приложением сразу же. Сохранив подстановочную картинку, нам нужно предоставить ее, когда настоящие изображения недоступны. Для этого необходимо добавить в приложение код, кото- рый будет выполняться, когда сетевые запросы завершаются ошибкой. Этим кодом может быть обработчик блока саьсь, который исполняется при сбое стратегии ХУогкЬох, как показано в листинге 11.42.14 Листинг 11.42. Исполнение обработчика блока саъсн зебСабсИНапсПег(({ еVепб }) => { 1Г (еуепб.гедиезб.дезбхпаЫоп === '1таде') { геЬигп сасЬез.таЬсЬ(1/сотгпдЗооп.рпд') } геСигп Кезропзе.еггог() }) Обработчик блока сагсЬ принимает в качестве параметра объект неуспешного сете- вого запроса. Можно было бы проверить ЕКЬ-адрес этого запроса, но лучше выяс- нить его назначение и учесть это при выборе подстановочного объекта для файла. Если назначением является изображение йпаде, то запрос был создан вследствие попытки загрузки элемента изображения 1тд браузером. В табл. 11.1 приведено несколько других примеров назначений запроса. 14 Дополнительная информация о библиотеке ХУогкЬох предоставляется в других рецептах этой главы.
Таблица 11.1. Примеры назначений запроса Назначение Создатель 1111 Сетевые запросы ^уабспр! "аиФо" Загрузка элемента <аис11о> "доситепГ Переход на веб-страницу "етЬесГ Загрузка элемента <етЬес!> "(опГ' Загрузка шрифта в таблице стилей "(тате" Загрузка элемента сЕгате> "Нгате" Загрузка элемента <1Тгате> "|таде" Загрузка элемента <1тд>, файла /ГаУ1соп.1со, изображения 8\/6 или изображения таблицы стилей "оЬ]ес1" Загрузка элемента <оЬдест> "8СПрГ Загрузка элемента <зсг!рт> "5ег\лсе\л/огкег" Загрузка сервис-воркера "звагес1\л/огкег" Загрузка общего воркера "з1у1е" Загрузка таблицы стилей "уйео" Загрузка элемента <у1<3ео> "\л/огкег" Загрузка воркера При вызове обработчика блока сатск он возвращает из кеша изображение сотт§8ооп.рп§. Чтобы найти этот файл в любом доступом кеше, используется метод саскез.таТсЬ. Теперь, когда у нас есть обработчик блока сатск, нам нужно определить стратегию ХУогкЬох для каждого запроса. При отсутствии такой стратегии сбойный запрос может не активировать этот обработчик. Если задать обработчик по умолчанию, то он будет применять определенную стратегию с каждым запросом, не обрабатывае- мым каким-либо другим способом: зеТ0е1аи1ЪНапс11ег (пем ЫеТкогкОп1у ()) Данная команда обеспечивает перенаправление сервис-воркером всех запросов в Сеть, если только не определен какой-либо более специфичный обработчик. Каждый из тегов <1тд> страницы генерирует запрос с назначением 1таде. Обработ- чик по умолчанию перенаправляет эти запросы стороннему серверу, что вызовет ошибку, поскольку приложение не имеет доступа к Сети. После этого обработчик блока сагсЪ возвращает подстановочное изображение для каждого элемента лпд. Результаты этого процесса показаны на рис. 11.31.
1таде А соипггу гоад 1таде ВаБеЬаН ап<1 тМ I гладе бгазз с!о5е-ир I гладе 5ал Егалс1зсо Й Рис. 11.31. В режиме офлайн все сторонние изображения на странице заменяются подстановочной картинкой Обсуждение Описанный метод будет полезным для работы с большими мультимедиафайлами, которые трудно или невозможно сохранить в локальном кеше. Например, в прило- жении для воспроизведения подкастов недостающий эпизод можно заменить коротким аудиоклипом, информирующим, что данный эпизод будет доступным только при восстановлении подключения к Сети. Разогрев кеша необходимыми файлами может увеличить время, требуемое для установки сервис-воркера. Поэтому в случае разогрева кеша сравнительно боль- шими файлами также нужно добавить следующий код в соответствующий сервис- воркер: гтрогГ * аз паУ]даГ1опРге1оас1 Ггот ' когкЬох-пау1даПоп-рге1оас1' г^1даГ1ОпРге1оас1.епаЫе () Предварительная загрузка навигационных данных (паУ1@а<юп рге1оаб) представляет собой метод оптимизации браузера, заключающийся в исполнении в фоновом режиме сетевых запросов, начавшихся при установке сервис-воркера. Не все брау- зеры поддерживают этот метод, но при его наличии библиотека могкЬох-паухдаПоп- рге1оаб использует его. Исходный код данного рецепта можно загрузить на веб-сайте ОйНиЬ по адресу Ы1р$://огеП.1у/5п№80.

Предметный указатель с сгеа1е-геас!-арр 19,20 С88-Ш-38 164 н Низку 295 I 1та§еМа§1ск 335 1ез149 м Ма1епа1 Оемцп 0 карточка 218 О спецификация 215 О стандарт 71 Ма1епа1-1Л О библиотека 70,214,215, 222,227 О библиотека значков 215 О темы 217 и К’ауЫпк 62 31, 32 прх 20 Р Ргеас! 33, 36 Ргеас! СЫ 35 Р Яах21е 28, 30 8 88К 33 ХУСАС 356 XV еЬ уйак 416 ХУеЬраск 19, 24 ХУеЬраскег 39, 41 уагп 20 УиЫКеу 256
А Анимация О ри!зе 165 О зЬаке 166 О гоотОш 166 Атрибут О аПоу/Сгедепбак 262 О апа-Ьазрорир 368 О апа-ЬиШеп 407 О аз 224 О аиШепОсаПопБсксбоп 258 О аи1оСотр1е(е 297 О аиТоСоггес! 296 О Ьаск^гоипёСсЯог 383 О даи-гезбд 304 О ехс1идеСгес1епба1$ 258 О го1е 363 О 1аЫпс1ех 376, 377 Аттестация 271 О объект 259 Аутентификация О в Интернете 256 О двухфакторная 256 Б Библиотека О @та!епа1-ш 141 О @1езбп§-НЬгагу/геас148 О Атташе.сзз 164 О ахе-соге 384, 386 О Ахюз 192, 253 О ВооШгар 223 О Оау.]з 112 О Епхуте 48 О Росиз Ьоск 406 О 1пГегпо18 37 О ЗезхВотЗО! О ]з-соок!е 286 О ЬоадаЫе Сотропепк 458 О Ма1епа1-1Л 70, 214, 215,222,227 ° установка 215 О МотепГ.]з112 О ргеасТ-сотра! 37 О Касйит 164 О Кеас! Аттабопз 164 О Кеас! ВоокТгар 223 О К.еас1 Кебих 116,119 О Кеас! 8спрк 19 О Кеас! \У1пс1оу/ 227 О геасЬадппп 236 О геасТ-дот/зсгуег 452 О геас1-Ье1те!27 О геасЬтд-едйог 158 О геасЬтесНа 53 О геас1-гои!ег-дот 51, 53, 54,60,66, 69, 75,76 О геас1-зспрк 23, 24 О геасЫгапзйюп-^гоир 76 О Кедих 116, 123,195 О Кебих Регз1з1 124,127 О гедшге-1ех1203 О Кезе1ес! 128, 130 О 8е1етит 300 О БетапОс Ы 214, 241 О 8етапбс 1Л Кеас! 241 О 81тр1е\УеЬАийт 266 О 81поп 347 О зу^г 453 О Тезбп§ ГлЬгагу 300, 301, 304 О ТыеепОпе 169 О Изег Е\еп1 301, 306 О \уеЬаи!Ьп-]зоп 258 О ХУеЬраскег 39,41 О ХУогкЬох 485 О хуогкЬох-Ьаск^гоипд-зупс 503, 504 О ууогкЬох-соге 466 О ууогкЬох-пау1§а1юп-рге1оас1 515 О криптографическая Ореп88Ь 268 Блок са1сЬ О обработчик 513 Бэкенд, служба О /ар1/1о§т 251 О /арИоцои! 251 В Веб-драйвер О сНготеёпуег 327 О ^ескобпуег 327 Г Группа перехода 77
д Дерево, встряхивание 441 Диспетчер пакетов О прт 20 0 уат 20 3 Заглушка ленивая 438 Загрузка предварительная навигационных данных 515 Задача блокирующая 417 Запрос 0 на аттестацию 257 0 на утверждение 262 И Инструмент 0 АпдгсЯд ОеЬи§ Вид^е 290 О сотраге 334, 338, 339 О производительности Е^ЫЬоизе 414,415 Инструменты 0 набор п пхуЬ 37 о КеасГ Оеуе1орег Тоо1з 419 ° \\'огкЬох 465 Интерфейс 0 \УеЬ 8реесЬ 410 0 \У1пдо\у.РиЫ1сКеуСгедепДа1 257 Источник маркера 194 К Карточка Ма1епа1 Оез^п 218 Кеш, разогрев 512, 515 Класс 0 §а!1егу6пд 217 0 §аПегу11етВезспр1юп 217 0 Мода! 369 Клиент 0 СгарЬ()Е 205 О Р!ау§гоипд 204 Ключ УиЫКеу 256 Команда О ЬиПд22 О сЬескА11у386 О сгеа!е-гагх!е-арр 29 О Сургезз 49, 50 ° су.с1оск 351, 352, 355 ° су.соЫатз 318 ° су.^е! 318 ° сулп1егсер( 319, 322 ° су.пеТхлогк 324, 326 ° су.гапдот 349 ° су Дек 352 О дпуег.^е!330 О е]ес123 О ГтдЕ1етеп1330 Компилятор ВаЬе1 34 Компонент О Адтт 234 О АррВаг216 О Арро!оРгоу1дег 206 О ВголузегКоиТег 54 О Со1 224 О СззВазеНпе 215 О С88Тгапзйюп 77, 80 О О1а1о§ 231 О Реед 244 О Р!хед8!хеЕ1з1 228, 229 О Рогт 224 О МеШа 53 О Мода! 369,406 О Му1пз1а11ег.]з 507 О КауЕтк 62 О Реор1еСоп1атег 53-55, 57 О Реор1еЕ1з155 О Роррег141 О РгоГдег 413, 422,427 О РготрТ 69, 71 О РгоУ1дегП8 О Кеас1РосизЕоск 406 О геасТ-тагкдоууп 158 О Кед1гес156 О 8есигеЕои!е 84 О 8есигйуСоп1ех1 84 О 8есип1уРгоу1дег 83, 248 О 8ейсЬ 54 О 8к!р 398,399 О 8к!рВиДоп 389, 390 О 81айсК.ои(ег 457 О ТазкзСоп1ех1з 396 О Тоо1Ьаг216 О ТгапздюпСгоир 77 О Туро^гарЬу 216 О \У1Д1МоЫ1еО1а1оё 230
Конечная точка /$<агО1е§к<ег 257 Контейнер 8изрепзс 439 Контекст 104, 134 0 8есигйуСоШех1 82 Корневой сертификат 269 м Маркер 0 источник 194 0 отмены 194, 202 Маршрут 477 Маршрутизатор 0 ВголузегКоШег 66, 71, 82 0 МетогуКоШег 66 Маршрутизация декларативная 51 Массив гепдегз 425 Метаданные 360 Метод 0 ВуТезИд 304 0 сасЬез.та1сЬ 514 0 бпуег.§е1330 0 с1пуег.\уай 333 0 еуепкпоЦГ1саПоп.с1озе 498 О Еуеп1.рготр1508 О ГтдАПВу 304 О йпдВу 304 О ^е1.Т1те 355 О §е!АПВу 303 О ёе®У зоз О ёе®уТех! 306 О трШ.зепбКеуз 331 О МаОъгапдот 347-349 О орепАУтдо^ 498 О циегуАПВу 303 О циегуВу 303 О КеасЮ0М.Ьус!га1е 455 О КеасЮОМ.гепдег 455 О ге§181ег 42 О зеИШегуа! 113 О зЬоууЫоййсайоп 496 О 88ЕЗЗ О зиЬтй.сНск 331 О йте 432 О ПтеЕпс! 432 О 1оЬоса1е81пп§ 355 О ууэйЕог 307 Методы О Г1Ш1...307 О §е1... 306 О циегу... 306 Модель О ВОМЗЗ О виртуальная ° ВОМ448 Модуль О ахе ВеуТоок 379, 384 О Васк$гоипс18упсР1и§1п 504 О сургез-ахе 384 О ез1пЦ]зх-а11у 372, 373, 378 О РайгРкцрп 171 О ргезе1-геас! 449 О \уогкЬох-гоиОпу 477 О 'логкЬох-иеЬраск-р1и^1п 470 Модульный тест 300 Мутация 203 н Набор инструментов О пу/Ь 37 О Кеас1 Оеуе1орег Тоо1з 419 О ХУогкЬох 465 Навигация, средства 359 Настройка рпуасу.гедисеТ1тегРгес1зюп 433 О Оболочка совместимости 44 Обработчик О блока са(сЬ 513 О сервис-воркер опирда!еГоипд 470 Объединитель 129 Объект О Еггог 134 О зсгееп 303 О аттестации 259 О высказывание 410 О деструктурирование 178 О сервис-воркер ° ге§1з1гайоп 469,496 о зеК.сПепк 498 Опция —1егпр1а1е (урезспр! 23 Ориентиры 371 Очередь епбРошК^иеие! 504 Ошибка СОВ8 486, 487 п Пакет О ез11Ш-р1и21п-геас1-зесип1у 293 О инструментов 1та§еМа§1ск 335
Пакеты О диспетчер » прт 20 ° уат 20 О сборщик ° ХУеЬраск 19, 24,470 Папка Ьшк1 22 Передающая сторона 258 Переменная 0 тахЯе1еп11опТ1те 504 0 КОПЕ ЕКУ409 О 1а1кТоМе 409 О среды Т7 354 Побочные эффекты 196 Подкомпонент О Рогт.СопТго! 224 0 Еогт.Сгоир 224 0 Еогт.ЬаЬе! 224 0 Еогт.Ком/ 224 Показатель 0 СЬ8 418 0 ЕСР416, 442 0 ЕЮ 418 0 ЬСР 417 О 81417 О ТВТ 417 О ТТ1 417,442 Правило О по-опсЬап^е 374 О (аЬтбех 407 Преобразователь 88, 147 О данных 87 Приложение одностраничное 19 Программа Егида 342 Производительность О инструмент Ы^ЬЙюизе 414, 415 Прокси-сервер 8е1епшт Сп<1 334, 337 Протокол О СЬготе ОсуТооЬ 324 0 1ЭсуТоо18 325 0 НТТР8 267 Р Разделение кода 437 Рассказ 46 Редактор Магкбохуп 157 0 руководство 161 Роль 363 0 1181365 0 ПзТйет 364 0 тепи 367 0 тепикет 367 С Сборщик пакетов ХУеЬраск 19, 24,470 Свойство 0 Ги118сгееп 231 0 ^еЮзегСопПгтайоп 71 0 пауща1ог.опЫпе 114 0 пауща(ог.8сгу1се \Уогкег.соп!го11ег 495 0 циегу 53 О 1Ше369 О С881гап8Йюп 161 Сервер О АР1275 О 81огуЬоок 335 Сервис-воркер 462 О обработчик опирсШеГошк! 470 О объект ° ге§181гайоп 469,496 ° зеИ.сНепй 498 О стратегия 463 Сертификат О 88Ь самозаверенный 267 О корневой 269 Синхронизация фоновая 505 Служба О СТте1пх418 О 8еп1гу.ю 134 О ХУеЬРа^еТез! 418 О бэкенд ° /арЫощп 251 ° /ар1/1о§оЩ 251 Событие О ЬеГогетзЩПрготр! 507 О Еосизт 409 О пойНсаИопсНск 498 О опЬеГогет81а11рготр1507, 510 О блокирующее 418 Содержимое страницы основное 360 Сообщение 8К1Р АУА1Т1НС 491 Спецификация Ма1епа1 Ое$щп 215 Средства навигации 359 Средство О сгеа!е-геас1-арр 19, 20, 23,24 О езИп! 372 О Е8НШ292 О СаЬЬу 24 О Ооо§1е Апа1у11С8 461 О КехЬ)8 31,32
Средство (прод.) Ъ Ргеас! 33, 36 О Ка//1е 28, 30 0 81огуЬоок 45,48 Ссылки геГ281 Стандарт О Ма(епа1 Оезщп 71 О У/еЬАиЙш 256, 257, 271 Сторона передающая 258 Стратегия 0 сасЬе-Лг8(486 0 №1луогк Оп1у 504 О 81а1е-\уЫ1е-геуаНс1а1е 485, 486 О сервис-воркер 463 Схема именования ВЕМ 162 Счетчик состояний 184 Темы Ма1епа1-1Л 217 Тест О СиситЬег 300 О модульный 300 Технология С88-1П-38 164 Точка конечная Маг1Кещ^ег 257 Транспилер 23 У Установка 0 библиотеки МаСепаМЛ: 215 0 средство езНш 372 0 шрифта ВоЬоГо 215 Утверждение 271 0 запрос 257, 262 Утилита прх 20 ф Файл 0 С8К269 0 сурге88/р1и§Ш8/1пс1ех.]8 384 0 турпуа!е.сг1 270 0 турпуаТе.кеу 270 0 8егу1се\Уогкегке|р81га11оп.]8 481 О 88Г.]8 449 Формат Зе8149 Фреймворк О арро1о-8егуег 203 О Сурге88 48, 300, 384 О Са1$Ьу 458 О №х1.]8 31 О КаПз 39 О К.еас119 О К.еас1 Адтт 233 О 8е1етит 327 о установка 335 Функция О арр!уМ1дд1е\уаге 199 О ЬиЛдСгарЬрЬРгоУ1дег 235 О сНеп18С1а1т 466 О сотЫпеКедисегз 118 О соп8о1еЛаЫе 343 О сгеа!еНапдегВоипдТо1Л<Е 477 О Оа1е 432 О д18рагсЬ 93 О Ге1сВ 502 О Госи8\'ех1Е1етеп1400 О ГосихРгеуюизЕктегП 401 О ГоглагдКеГ391 О цеЧ263 О ^е1Ое8спрПоп 409 О ^еШзегСопПгтайоп 73, 75 О И 36 О Ызкчу.ризИ 57 О 1аху 437,438,457 О таке81у1с8 217 О тег^е 166 О пауща1ог.сгедепба18.сгеа1е 258 О пау1§а1ог.8епдВеасоп 460 О пауща1ог.8егу1се\Уогкег.соп1го11ег 470 О пауща1ог.8егу1се\Уогкег.ге§181ег 469 О опАттабопЕпд 166 О оп!п81а1482 О опкепдег 424, 429 О опСрс1а1е 482, 483, 490 О регГогтапсе.поху 426, 433, 434 О ргесасЬеАпдКои!е 466 О Ргот18е.а11 330, 445 О К.аФит.кеуГгате8 166 О ге§181ег 467, 482,484 О ге§181егКои!е 485 О ге^181ег\Уогкег 467 О гепдег 302, 303 О гепс!егТо8(пгщ 452 О героП\УеЬУйа18 459 О 8егу1се\УогкегКе^18Ггабоп.ге§181ег 482, 490 О 8еГПтеои1496 О 8к1р\Уа1т§ 491
О $1а1е\М1н1еКеуа11с1аГе 486 О згагГТазк 495 О 1а1кТоМе411 О Гегтта1Ео§ 386 О Тгасе 426, 427 О (гаскег425 О \Уогкегке§181гаНоп.ипге§181ег 482 О селекторная 87, 128 X Хранилище 118 Хук О Низку 295 О рге-соттй 295 О изеО18ра1сЬ 119 О изеЕйес! 179 О изеЬосаНоп 77 О изеМЩайоп 207 О изеРиегу 207 О изеКедисе 91 О изе81у1ез 217 ш Шаблон О сга-1етр1а(е-ри'а 466 О стартовый Оа1зЬу 28 Шрифт КоЪо1о, установка 215 э Эффекты побочные 196

Об авторах Дэвид Гриффитс— автор и инструктор. Он занимается разработкой кода в Кеас1 на профессиональном уровне. Настоящая книга "Кеас1. Сборник рецептов" уже шестая на его счету. Его деятельность была связана с разработкой приложений для стартапов, магазинов розничной торговли, производителей транспортных средств и национальных спортивных организаций. Живет и работает в Великобритании. Дон Гриффитс — автор и инструктор, за спиной которой свыше 20 лет опыта в обла- сти разработки программного обеспечения для настольных компьютеров и Интернета. В список ее работ входят книги по созданию приложений для АпдгоМ, языку програм- мирования КоШп и по статистике. Вместе Дэвид и Дон написали несколько книг серии Неаб РйзГ, включая книги Неас1 НгМ Апс1го1(1 Ое\>е1ортеп1 и Неас1 НгМ КоШп, а также внесли свой вклад в написание книги 97 ТЫп&8 Ечегу Аача Рго^гаттег 8кои1с1 Кпочч ("97 вещей, которые должен знать каждый программист Заха"). Они разработали видеокурс А^йе 8ке!сИрад как способ обучения фундаментальным принципам и методам таким образом, чтобы слушатели были постоянно активными и вовлеченными. Также они разрабатывают онлайновые инструкции для компании О'КеШу в реальном времени.
Об обложке На обложке данной книги изображена большая голубая цапля (Агбеа Иегосйаз). Эти птицы, которых иногда еще называют журавлями, обитают на просторах Северной Америки: размножаются в Канаде, зимуют в Мехико и проводят большую часть года в Соединенных Штатах. Больших голубых цапель часто можно встретить на берегах пресноводных озер и водоемов, рек, а также на окраинах болот, лиманов и прудов. Их также можно увидеть пасущимися на лугах, полях и на других открытых травянистых местностях. Большие голубые цапли — самые большие цапли в Северной Америке. Их характерная особенность — длинные ноги и шея, а также толстый клюв, который часто сравнивают с кинжалом. На расстоянии они кажутся серо-голубыми, а благодаря их оперению они выглядят так, словно покрыты шерстью. В Южной Флориде и на Карибских островах можно встретить вид цапли полностью белого цвета, называющейся большой белой цаплей, хотя относительно их идет определенный спор, являются ли они отдельным видом. Популяции цапель в восточной части США мигрируют на Карибские острова, в Центральную Америку или на север Южной Америки, в одиночку или стаями. Те, которые живут вдоль тихоокеанского побережья, менее склонны к миграции и могут постоянно проживать в одном месте, забираясь даже далеко на север в юго-восточную Аляску. Касательно своей диеты большие голубые цапли являются оппортунистами и едят почти все, что могут поймать или насадить на свой клюв, включая рыбу, амфи- бий, пресмыкающихся, небольших млекопитающих и даже других птиц! В течение периода размножения большие голубые цапли добывают корм на расстоянии нескольких миль от своих гнезд. Колонии из нескольких сотен пар строят гнезда из ве- ток на деревьях, в кустах или на земле вблизи отдаленных болот, островов, прудов или озер. Эти цапли совершают изысканные церемонии ухаживания и брачные танцы, ко- торые могут включать ритуальные поклоны, передачу веток, а также замену друг друга в гнезде. Пары могут оставаться моногамными в течение сезона, но выбирают новых партнеров каждый год. Оба родителя кормят свое потомство срыгиванием. Через два месяца птенцы цапли уже способны летать, а через месяц после первого вылета они покидают гнездо. В США популяции больших голубых цапель увеличивались с 1966 г., за исключением группы больших белых цапель в южной Флориде, где повышенное содержание ртути в местных водах привело к значительному уменьшению численности. Как и большин- ство птиц в США, цапли защищены Законом об охране перелетных птиц. Многие животные на обложках книг издательства О'КеШу находятся под угрозой исчезновения, все они важны для нас. Иллюстрацию к обложке выполнил Карен Монтгомери (Кагеп МопТ^отегу) на основе черно-белой гравюры Одюбона.
*Ы1У® уууууу.Ы1У.ги Дронов В. А. Кеас( 17. Разработка веб-приложений на 5ауа8спр1 Отдел оптовых поставок: е-таЛ: ор!@Ызу.ги Простота и скорость разработки Владимир Дроиои Неас1 17 Разработка веб-приложений на иауа5спр1 • Компоненты — классы и функции • Язык 18Х • Веб-формы • Валидация • Кеас! КоШсг • Кедих • Разделение кода • Отладка • Публикация • Рендеринг на стороне сервера Книга посвящена программированию веб-приложений на языке 1ауа8спр1 с приме- нением популярного веб-фреймворка Кеас1. Дается вводный курс, наглядно, по ша- гам описывающий разработку несложного веб-приложения — списка запланиро- ванных дел. Описываются базовые инструменты: создание и настройка Кеас1- проекта, написание компонентов, язык 18Х, передача данных между компонентами и создание веб-форм. Рассматриваются полезные дополнительные библиотеки: Кеас! Кои1ег (навигация), Кес1их, Ееас1 Кедих и Кебих Тоо1кй (централизованное хранилище данных), Еоптпк (быстрая разработка веб-форм), Уир (валидация), Кеас(Ксуеа1 (анимационные эффекты) и др. Рассказывается о разделении кода, обработке ошибок, средствах отладки, публикации готового веб-приложения и рендеринге на стороне сервера. Электронный архив на сайте издательства содержит код описанного в книге веб-приложения и другие полезные файлы. Дронов Владимир Александрович, профессиональный программист, писатель и журналист, работает с компьютерами с 1987 года. Автор более 20 популярных компьютерных книг, в том числе «НТМЬ, 1ауа8спр1, РНР и Му8рЬ. Джентльменский набор ХУеЬ-мастера», «Руйюп 3 и Ру<Э1 5. Разработка приложений» и др. Его статьи публикуются в журналах «Мир ПК» и «Интерфейс» (Израиль) и на интернет-порталах 12 С11у и ТИеУ^Та.ги.
ИНТЕРНЕТ-МАГАЗИН вни.ви КНИГИ, РОБОТЫ, ЭЛЕКТРОНИКА Интернет-магазин издательства «БХВ» • Ответы на и компакт-дисков • Более 25 лет на российском рынке - Книги и наборы по электронике и робототехнике по
РеасТ Сборник рецептов Фреймворк Реас+ поможет вам создать работоспо- собное приложение всего за несколько минут. Но на- учиться использовать составляющие Реас+ — работа не из легких. Как выполнить валидацию формы? Или реализовать сложное многошаговое действие пользо- вателя, избежав создания запутанного кода? Как про- тестировать приложение? Приспособить его для мно- гократного использования? Подключить его к бэкенду? Сделать его легким для понимания? Данная книга дает быстрые ответы на эти и другие вопросы. Многие книги учат, как начать работу с фреймворком РеасТ, понять его архитектуру или использовать библио- теки компонентов, но почти нигде не показаны при- меры, помогающие решить конкретные задачи. Этот сборник отсортированных по темам и областям при- менения рецептов содержит примеры кода, которые позволят разработчикам найти решения наиболее распространенных задач при использовании фрейм- ворка РеасТ. Вы научитесь: • Создавать одностраничные приложения в РеасГ, использующие сложный пользовательский интерфейс • Создавать прогрессивные веб-приложения, которые пользователи смогут установить на свои устройства и работать с ними в автономном режиме • Интегрировать разрабатываемые приложения со службами бэкенда, такими как РЕ5Т и бгарЬОЬ • Выполнять автоматическое тестирование для обнаружения проблем с доступностью в разрабатываемом приложении • Обеспечивать безопасность приложений при помощи цифровых отпечатков и ключей безопасности с применением технологии У/еЬДЩЬп • Исправлять ошибки и избегать распространенных проблем с функциональностью и производительностью «Книга проводит читателя через полный жизненный цикл разработки приложений РеасТ. Каждый рецепт—это краткий, легко усваиваемый комплекс знаний. Книга обязательна для прочтения всем разработчикам!» —Сэм Уорнер, инженер-программист Дэвид Гриффитс — автор и преподаватель, занимается разработкой кода в Реас+ на профессиональном уровне в течение 5 лет. Создавал приложения для стартапов, магазинов розничной торговли, производителей транспортных средств, национальных спортивных команд и крупных поставщиков программного обеспечения. Дон Гриффитс — автор и преподаватель с более чем 20-летним опытом разработки программного обеспечения для персональных компьютеров и Интернета. Вместе Дэвид и Дон написали несколько книг, включая Неас1 Яга!’ Апс1го1с1 Эеуе1ортеп+ и Неаб НгзГ Ко+Нп. Также они проводят онлайн-консультации для компании О'РеШу. 15ВИ 978-5-9775-6839-5 191036, Санкт-Петербург, Гончарная ул., 20 Тел.: (812) 717-10-50, 339-54-17, 339-54-28 Е-таП: та|1@Ы1У.ги 1п1егпе1: УЛЛМ.Ы1У.ги (ЬНу-