dshovchko / practice-5

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Практическая работа 5

Все привычные методы для запуска проекта и тестирования остаются с нами без изменений.

Для дополнительной помощи в разработке на React поставьте плагин React Developer Tools для браузера.

Для дополнительной помощи в разработке на Redux поставьте плагин Redux DevTools

Задача 1

Напишите компонент Accordion, который принимает список панелей с заголовками и контентов, и создает.. ну.. аккордеон: https://codepen.io/taniarascia/pen/BpwOKZ

Компонент принимает одну проперти tabs - массив объектов с двумя полями: header и content. Структура HTML которую он должен рендерить уже записана в методе render() компонента. Вначале все табы должны быть закрыты (класс d.none на всех card-body) При клике на заголовок любой табы он должен стать выделенным (класс active на card-header), а контент табы - открыться (класс d-none должен отсутствовать на card-body).

Может быть открыто несколько таб одновременно.

Вам будет гораздо проще решить задание если вы заведете дополнительный компонент TabItem, представляющий одну табу (заголовок + контент)

Задача 2

Напишите компонент Calculator, позволяющий ввести два числа и произвести с ними арифметические операции. Внешний вид калькулятора уже готов в методе render() компонента. При нажатии на кнопки add/subtract/multiply/divide в поле result должен появиться ответ.

Поля ввода позволяют вводить только цифры, если пользователь пытается ввести букву - это должно игнорироваться. Попытки ввода в поле результата тоже должны игнорироваться. Кнопка "Clear" должна, как нетрудно догадаться, сбрасывать все поля ввода в значение 0.

Опять же, вам будет проще справиться с заданием если вы выделите два очень простых компонента - Button и Input, делегировав им задачи ввода данных и обработки нажатий на кнопку, а компоненту Calculator останется только разобраться с общим каркасом верстки, хранением состояния и арифметикой второго класса.

Задача 3

Напишите компонент Tabs, который работает.. ну.. как табы. Однако кроме того чтобы просто рендерить список заголовков и переключать контент, он должен позволять настраивать вид заголовков и контента.

Tabs принимает три проперти:

  • tabs - структура такого же формата как и в задаче 1;
  • headerTpl - функция, которая принимает в качестве аргумента объект с полями item (одна таба из tabs) и index (порядковый номер табы) и возвращает элемент React, описывающий внешний вид заголовка табы. Например: tab => ${tab.index}. ${tab.item.header}`` - выведет не только заголовок табы, но и ее индекс
  • contentTpl - функция, которая принимает в качестве аргумента такой же объект как и headerTpl, и возвращает элемент React, описывающий внешний вид контента табы. Например: tab => <h4>{tab.item.content}</h4> - выведет контент в тегах h4.

Пропишите defaultProps компонента так, чтобы можно было опустить проперти headerTpl и contentTpl. В этом случае должен рисоваться просто tab.item.header для headerTpl и tab.item.content для contentTpl.

На странице компонент Tabs вызывается три раза с разыми значениями headerTpl и contentTpl. Исходный HTML как всегда прописан в методе render().

Сначала пропишите логику переключения табов (класс active для заголовков и класс d-none для невидимых таб), затем добавьте в ваш JSX вызов функций headerTpl и contentTpl.

Задача 4 (*)

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

Разбиение на компоненты и черновая верстка уже готовы, осталось всего ничего: вызов API для получения данных, подстановка этих данных в верстку и управление состоянием с помощью Redux.

Подстановка данных

Начните с подстановки реальных данных в верстку.

Для этого напрямую возьмите данные из вспомогательного файла api/data-generator. Он экспортирует две переменных: daysShort - массив с кратким прогнозов для семи дней, и days - объект вида dt -> forecast, представляющий полный прогноз по конкретному дню. Заметьте, что мы испольузем dt (timestamp) в качестве уникального идентификатора дня.

Форматировать даты удобно с помощью метода Date::toLocaleDateString. Также вам придется округлять температуру, подставлять иконки соответствующие погоде и их описания. Изменить нужно компоненты WeatherDay и WeatherDetails; компонент Weather пока что не требует изменений.

Redux: общий вид хранилища

У нас будет следующая структура Redux Store:

{
  dayForecast: {
    '1523912400000': {
      data: {
        dt: 1523912400000,
        temp: {
          min: 6.78,
          max: 21.71
        },
        . . . // структура полного прогноза погоды по дню
      },
      loading: false, // прогноз погоды по этому дню уже загрузился 
      error: false    // не было ошибок при загрузке
    },
    '1523998800000': {
      data: null,     // если была неудачная попытка загрузить, то null
      loading: false,
      error: true     // была ошибка при загрузке
    },
    '1524085200000': {
      data: null, 
      loading: true,  // запрос послан, но еще не пришел
      error: false
    }
  },
  weekForecast: [
    . . . // структура краткого прогноза погоды по 7 дням
  ],
  selectedDt: 1524085200000, // timestamp выбранного дня. Если ничего не выбрано, то null
  weekLoading: false,        // Загружается ли краткий прогноз погоды
  weekError: false           // Была ли ошибка при загрузке краткого прогноза погоды
}

Как видно, мы собираемся обрабатывать статус загрузки и ошибки при загрузке для краткого прогноза погоды на неделю, а также для полного прогноза погоды для каждого отдельного дня. Экшены и редьюсеры для загрузки полного прогноза по дням уже готовы, внимательно ознакомьтесь с ними (actions/day-forecast, reducers/day-forecast)

Компонент Weather
  • подключите поля weekLoading, weekError и weekForecast из хранилища Redux к компоненту Weather (функция mapStateToProps)
  • дополните метод render обработкой состояния загрузки данных и состояния ошибки Во время загрузки данных компонент должен содержать следующую верстку:
<div className="weather">
  <span className="fa fa-spinner fa-spin"></span>
</div>

При ошибке мы должны отобразить текст и кнопку для перезагрузки данных (метод onClick добавится позже):

<div className="weather">
  <div className="error">Error occurred during data fetch. Try to <button>reload</button></div>
</div>
  • проверьте работу компонента в каждом из состояний. Это можно сделать, изменяя значения по умолчанию в каждом из редьюсеров reducers/week-forecast
  • напишите экшены для загрузки краткого прогноза погоды в actions/week-forecast, по аналогии с экшенами из day-forecast. Здесь нужно использовать функцию getWeekForecast из api/index, которая оборачивает в промис соответствующие данные из генератора данных
  • напишите редьюсеры краткого прогноза погоды в reducers/week-forecast, ни в коем случае не используя аналогию с редьюсерами в day-forecast: ваши редьюсеры получатся гораздо более простыми и не должны использовать Object.assign, spread или другую магию
  • подключите экшен fetchWeekForecast к компоненту Weather (функция mapDispatchToProps)
  • добавьте вызов этого экшена в lifecycle-метод componentDidMount (это неплохое место чтобы начать загрузку данных), и в обработчик onClick кнопки "Reload"
  • напишите propTypes для полей и экшенов привязанных к компоненту
  • удалите импорт daysShort из генератора данных и замените эту переменную в методе render на значение из Redux

Теперь компонент Weather полностью готов, он должен отображать спиннер при загрузке, сообщение об ошибке в случае неудачной загрузки данных, и данные из генератора данных если загрузка оказалась успешной. При нажатии кнопки "Reload" должна выполниться повторная попытка загрузки данных.

Компонент WeatherDay

Этот компонент отвечает за отображение краткого прогноза по одному дню. Также он должен иметь класс active, если его день соответствует выбранному для подробного отображения в WeatherDetails (selectedDt в хранилище) При клике на компонент он должен изменять день, выбранный для отображения в WeatherDetails

  • подключите поле selectedDt из хранилища к компоненту (mapStateToProps)
  • дополните метод render: если selectedDt совпадает с полем dt отображаемого дня, компонент должен иметь дополнительный css-класс active
  • напишите тело action creator'а openDayDetails (actions/day-forecast). Он должен отправлять экшен типа OPEN_DAY_DETAILS, и содержать dt выбранного дня. Если openDayDetails вызывается с тем же dt что и текущее значение selectedDt в хранилище, то поле dt нужно установить в null - таким образом, при повторном выборе одного и того же дня мы убираем этот день из подробного просмотра.
  • напишите очень простой редьюсер selectedDt (reducers/day-forecast), который по экшену OPEN_DAY_DETAILS устанавливает значение selectedDt.
  • подключите action creator openDayDetails к компоненту WeatherDay (mapDispatchToProps)
  • добавьте вызов этого экшена в обработчик клика на компоненте
  • напишите propTypes для полей и экшенов привязанных к компоненту

Теперь компонент WeatherDay полностью готов, он должен добавлять и убирать класс active при нажатии на него.

Компонент WeatherDetails

Компонент отвечает за отображение подробного прогноза по отдельному дню. При этом если в нашем хранилище еще нет данных по этому дню, то нужно их запросить; если же они уже загружены - то нужно использовать эти данные.

  • подключите поле selectedDt и поле forecast, содержащее полный прогноз по текущему дню и поля loading/error. Вам нужно получить его из объекта dayForecast по ключу selectedDt.
  • дополните метод render обработкой различных состояний компонента. Если selectedDt === null, это значит что ни один день не выбран в верхнем списке, и компонент ничего не должен отображать. Во время загруки данных (forecast.loading) нужно вывести крутящийся спиннер:
<div className="details">
  <span className="fa fa-spinner fa-spin"></span>
</div>

Если произошла ошибка (forecast.error) нужно вывести дополнительную кнопку "Reload":

<div className="details">
  <div className="error">Error occurred during data fetch. Try to <button>reload</button></div>
</div>

Иначе выводятся данные полного прогноза погоды из объекта forecast.data.

  • подключите экшен fetchDayForecast к компоненту. Использовать lifecycle-метод componentDidMount не удастся, т.к. наш компонент отображает различные данные в зависимости от selectedDt, и компонент при этом не пересоздается. Поэтому добавьте вызов этого экшена в метод componentDidUpdate, в который React передает старые props после ререндера компонента. Проверьте что selectedDt действительно был изменен при апдейте props, и вызовите подключенный экшен. Также добавьте вызов этого экшена в обработчик клика на кнопке "Reload".
  • удалите импорт data-generator
  • напишите propTypes для полей и экшенов привязанных к компоненту

Теперь компонент WeatherDetails полностью готов, должен отображать спиннер/ошибки загрузки, и загруженные данные полного прогноза погоды по выбранному дню.

Итоги подведем

Все готово. Вы восхитительны!

Задача 5 (**)

Добавьте к задаче 4 дополнительный компонент - ErrorLogger, который отображает список ошибок, возникших при загрузке данных.

Заведите дополнительный редьюсер, который будет отслеживать поле error в любых экшенах, и складывать все ошибки в отдельный массив errors в хранилище. Новой ошибке при этом должен присваиваться новый уникальный id, по которому ее затем можно будет найти.

Массив errors должен быть связан с компонентом ErrorLogger, который выводит список ошибок с помощью bootstrap alerts: https://getbootstrap.com/docs/4.0/components/alerts/ У каждого алерта при этом должна быть кнока "close", по которой она должна удаляться из списка ошибок. Не стоит использовать для этого jquery-компоненты бутстрапа; лучше взять у бутстрапа только верстку и css.

Для удаления ошибки из списка напишите новый экшен DELETE_ERROR и редьюсер для него, который будет удалять ошибку с заданным id из списка errors.

About

License:MIT License


Languages

Language:JavaScript 92.8%Language:HTML 4.7%Language:CSS 2.5%