5.10 Практические задания
Чтобы как следует усвоить полученную информацию нужно хорошо попрактиковаться. Это особенно актуально для программирования. Чтобы сделать этот процесс максимально полезным, я подготовил для вас набор типовых задач, которые очень часто возникают в приложениях.
Помимо задач есть и подсказки. Вы можете обратиться к ним сразу, если совсем непонятно, что нужно сделать. Либо можно сделать всё самому, а затем посмотреть подсказку, чтобы сравнить варианты исполнения. Возможно, вы предложите даже более изящное решение! Если вдруг это произойдёт, не жадничайте и делитесь вашими идеями в комментариях)
Для работы я рекомендую среду WebStorm, создав проект на основе приложения .
Задачи отсортированы по возрастанию сложности. Решив их все, я уверен, что вы окончательно устраните все пробелы и будете готовы написать с нуля своё собственное приложение. Итак, поехали!
На странице списка приёмов добавьте в фильтр следующие поля: "Статус", "Принимающий" и "Жалобы".
На странице списка приёмов добавьте в фильтр кнопку "Сброс", наделив ее какой-нибудь svg-иконкой по аналогии с кнопкой "Поиск". По нажатии на "Сброс" поля фильтра должны быть очищены, а список перезагружен с пустыми параметрами фильтрации.
Подсказка: для того, чтобы сбросить фильтр, нам понадобится
уже реализованное действие cleanFilter()
.
На странице списка приёмов сделайте скрывающийся фильтр,
используя компонент <Collapse>
библиотеки reactstrap
.
Для распахивания/скрытия фильтра использовать svg-иконку в правом верхнем углу:
По умолчанию фильтр должен быть скрыт. Фильтр вынести в отдельный
компонент-контейнер <AppointmentFilter>
, поместив его в папку /Appointments
.
Компонент фильтра должен иметь доступ к определённой части состояния и действиям redux.
Подсказка: когда мы переносим фильтр в отдельный компонент-контейнер, последний будет иметь доступ к состоянию и действиям списка. Сам компонент будет выглядеть примерно так:
// маппинг состояния фильтра в свойства компонента-контейнера
function mapStateToProps (state) {
return {
fields: state.appointment.list.dataSource.filter
}
}
// подключение генераторов действий к компоненту-контейнеру
function mapDispatchToProps(dispatch) {
return {
actions: {
...bindActionCreators(appointmentListActions, dispatch)
}
}
}
class AppointmentFilter extends Component {
onChangeField = (name, value) => {
this.changeFilterField(name, value, false)
}
onChangeDateField = (name, value) => {
this.changeFilterField(name, value && value.getTime(), false)
}
onSearch = () => {
this.change()
}
onClean = () => {
this.clean()
}
// изменение определённых нескольких полей фильтра
change (changes, shouldReload) {
this.props
.actions
.changeFilter(changes, shouldReload)
}
// изменение определённого поля фильтра
changeField (name, value, shouldReload) {
this.props
.actions
.changeFilterField(name, value, shouldReload)
}
// сброс фильра
clean () {
this.props
.actions
.changeFilter()
}
// разметка
render() {}
}
// объявляем контейнер
export default connect(mapStateToProps, mapDispatchToProps)(AppointmentFilter)
Компонент <Appointments>
нужно очистить от лишних методов, тем самым
упростив его код.
На странице списка приёмов добавьте сортировку в следующие столбцы таблицы: "Дата", "Клиент", "Статус" и "Принимающий". Сортировка должна происходить на стороне сервера(фейкового), а на клиент приходят готовые отсортированные данные.
Подсказка. Чтобы организовать сортировку понадобится сделать несколько вещей:
-
Включить сортировку на необходимых столбцах используя .
-
По событию
onSort
список следует перезагрузить, передав на сервер поле сортировки. -
Redux состояние списка будет содержать новое свойство
sorting
:
Кодexport default Record({ error: null, isFetching: false, shouldReload: false, dataSource: Record({ data: [], filter: Record({ startDate: null, endDate: null, clientName: '', onlyMe: false })(), // сортировка sorting: Record({ field: 'firstName', order: 'asc' })() })() })
-
Добавится новое действие
sort()
:
Код// сортировать список export function sort (field, order, shouldReload) { return { type: CHANGE_ASSESSMENT_LIST_SORTING, payload: { field, order, shouldReload } } }
-
Метод
load()
компонента<Appointments>
будет передавать дополнительный параметрsort
, равный значениюsort='fieldName,order'
(напримерsort='firstName,desc'
). Сервер парсит это строковое значение и возвращает отсортированный список.
На странице списка приёмов увеличить количество тестовых данных и реализовать пагинацию.
Подсказка. Чтобы увеличить количество тестовых данных следует
отрефакторить код файла MockData.js
. Сейчас там просто статический
список appointments
. Чтобы придать этому списку произвольный размер,
нужно сделать его динамическим. Это значит, что нужно создать метод
createAppointments(N)
, который будет создавать N
элементов списка при
старте приложения. Чтобы данные списка были разнообразными, нужно
построить несколько дополнительных статических списков: список
имен firstNames
, список фамилий lastNames
, список диагнозов и
так далее. Затем при конструировании элемента динамического списка
appointments, мы для каждого элемента случайным образом выбираем
имя, фамилию, диагноз и пр. из статических списков.
Чтобы реализовать пагинацию, в Redux состояние списка приёмов
следует добавить новое свойство pagination
:
export default Record({
error: null,
isFetching: false,
shouldReload: false,
dataSource: Record({
data: [],
filter: Record({
startDate: null,
endDate: null,
clientName: '',
onlyMe: false
})(),
// пагинация
pagination: Record({
page: 1, // текущий номер страницы
size: 15, // размер страницы
totalCount: 0 // всего элементов
})()
// ...
})()
})
Под страницей понимается количество/часть элементов списка, показываемых пользователю в данный момент. Соответственно нам нужны такие параметры как номер/индекс и размер части, а также общее количество элементов (для корректной работа пагинатора таблицы).
Пример подключения пагинатора в компоненте таблицы можно .
В компоненте <Table>
рекомендую добавить
метод onRefresh
:
export default class Table extends Component {
// ...
onRefresh = (type, { page }) => {
this.props.onRefresh(page)
}
// ...
render() {
// ...
return (
<div className={cn('TableContainer', containerClass)}>
<BootstrapTable
// ...
onTableChange={this.onRefresh}
// ...
/>
</div>
)
}
}
При срабатывании onRefresh
компонента <Table>
компонент <Appointments>
будет загружать необходимую часть
(страницу) данных списка для значения параметра page
. Сервер вычисляет нужную
часть элементов списка(страницу) и возвращает её.
Реализуйте демонстрацию ошибки при неудаче получения данных с сервера. Продемонстрировать ошибку следует в виде модального диалога с кнопкой ОК, по нажатию на которой диалог пропадает.
Подсказка. в библиотеке reactstrap есть модальное окно.
Вам понадобится сделать свой универсальный базовый компонент-обёртку
<Dialog>
со всеми необходимыми для вас свойствами
(props
). Компонент <Dialog>
, помимо прочих, будет
содержать такие специфические свойства как: icon
(иконка диалога: ошибка,
успех, информация), title
(название диалога) и buttons
(кнопки диалога).
Содержимое диалога будет передаваться через свойство children
.
Использование <Dialog>
будет выглядеть приблизительно так:
<Dialog
text={text}
title={title}
icon={Danger}
isOpen={isOpen}
// buttons - это массив элементов вида:
// [{ text: ‘OK’, color: ‘success’, outline: true, className: ‘ErrorDialog-OkBtn’ }, … любые кнопки]
buttons={buttons}
onClosed={onClosed}
className={cn('ErrorDialog', className)}
/>
Для демонстрации ошибки удобно создать ещё один компонент-обёртку
вокруг <Dialog>
. Назовём его <ErrorViewer>
.
Он будет отрисовывать с компонент диалога с предварительно заданными
свойствами props
(title, icon, text и buttons), которые при необходимости можно
переопределить.
Саму ошибку мы получаем из соответствующей части состояния.
Реализуйте аутентификацию. Требования:
-
При вводе URL приложения пользователь должен быть перенаправлен на форму аутентификации.
-
При успешной аутентификации на форме пользователь должен быть перенаправлен на домашнюю страницу, а в заголовке должны отображаться его имя и фамилия.
Подсказка. Работа с формой аналогична работе с фильтром, который
мы реализовали для списка приёмов. К тому же имеется готовый макет. Важным моментом
является добавление нескольких тестовых пользователей в фейковую базу MockData.js
.
Создайте в этом файле метод login()
, который будет сверять введённые данные с
этими пользователями. Если есть полное совпадение, аутентификация признаётся
успешной, а метод возвращает данные найденного пользователя для его последующего
сохранения в состояние приложения. Эти данные будут доступны на протяжении всего
сеанса. Простейший вариант реализации метода login()
:
export function login ({ username, password }) {
const user = // найти пользователя
if (user) {
return getSuccessResponse(user, { success: true })
}
return getFailureResponse(
'bad.credentials',
'Пользователя с такими данными не найдено'
)
}
Что касается состояния, то для работы с аутентификацией удобно создать
поддиректорию /redux/auth
, а для формы аутентификации - поддиректорию /redux/login
:
Для выполнения AJAX запросов, связанных аутентификацией (логин, логаут)
создайте сервис AuthService
.
Подключите валидацию на форму аутентификации. Валидация должна срабатывать, когда пользователь пытается отправить не заполненную форму.
Подсказка. Для валидации неплохо подойдёт плагин validate.js
.
Он обладает обширными возможностями и простотой использования. Валидацию лучше
организовать через действие (в файле loginFormActions.js
). Также подготовьте
специальный класс-валидатор формы LoginFormValidatоr
с методом validate()
, который
можно вызывать в действии. Код действия и валидатора может выглядеть так:
В состояние, для каждого основного поля формы, следует добавить два валидационных:
В редюсере, в случае неудачной валидации, мы проинициализируем эти поля, а затем отобразим их значения на форме.
Реализовать просмотрщик и редактор деталей приёма в виде диалоговых окон, используя макеты. Требования:
-
В просмотрщике должна быть показана более подробная информация по приёму, чем в списке (придумайте дополнительные данные).
-
Информация должна быть представлена в виде трёх столбцов.
-
Просмотрщик должен иметь название "Просмотр приёма", а также содержать кнопку "Закрыть", по нажатию на которой он закрывается.
-
-
В редакторе должна отображаться форма для создания/редактирования приёма, на которой должна присутствовать валидация.
-
Если редактор открыт на редактирование приёма, его название должно быть "Редактировать приём", а если на создание - "Создать приём".
-
Редактор должен иметь кнопки "Закрыть" и "Сохранить".
-
По нажатию на кнопку "Закрыть", редактор должен закрываться.
-
По нажатию на кнопку "Сохранить", сначала происходит валидация. Если валидация прошла успешно - данные отправляются на сервер.
-
Пока ожидается ответ сервера, на диалоге должен показываться "модальный" лоудер, который затеняет все поля формы, не давая возможности её редактировать.
-
Если данные сохранены успешно - лоудер пропадает, редактор закрывается, а список обновляется.
-
-
Для вызова просмотрщика и редактора в таблицу следует добавить дополнительный безымянный столбец.
-
Каждая ячейка этого столбца должна содержать две кнопки для просмотра (с иконкой глаза) и редактирования (с иконкой карандаша) конкретного приёма.
-
-
Над таблицей следует добавить кнопку "Создать" для вызова редактора в режиме создания нового приёма.
Макеты:
Подсказка. Cледует создать одноимённую компонент-обёртку вокруг компонента модального окна reactstrap. Такая обёртка позволит сократить код при использовании и расширить возможности.
Просмотрщик и редактор можно представить компонентами <AppointmentViewer>
и <AppointmentEditor>
, которые будут являться обёртками модального окна.
Реализовать раздел "События" по аналогии с разделом "Приёмы". Требования:
-
Схлопывающийся фильтр
-
Список с возможностями сортировки и пагинации
-
Просмотрщик и редактор событий
Вот, пожалуй, и весь перечень практических задач. Я очень надеюсь, что их решение принесёт вам существенную пользу, разрешив многие вопросы и улучшив ваши практические навыки.
Что ж, настало время закончить эту большую главу. Не забывайте изучать новые концепции и возможности React, согласовано интегрируйте их в проект и не забывайте про качество кода. Успехов вам в этом удивительном мире фронтенда!