Разбираем тестовое задание на должность фронтенд-разработчика на Vue.js

Первое правило тестовых заданий — никогда не делайте тестовые задания!

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

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

Для ознакомления с заданием, которое я получил, прошу под спойлер:

Техническое задание:

Средствами Vue.js реализуйте небольшое SPA приложение для заметок.

Каждая заметка имеет название и список задач (todo list), далее — Todo. Каждый пункт Todo состоит из чекбокса и относящейся к нему текстовой подписи.

Приложение состоит всего из 2х страниц.

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

  • перейти к созданию новой заметки

  • перейти к изменению

  • удалить (необходимо подтверждение)

Страница изменения заметки позволяет определенную заметку отредактировать, отметить пункты Todo, а после сохранить изменения. Действия с заметкой:

  • сохранить изменения

  • отменить редактирование (необходимо подтверждение)

  • удалить (необходимо подтверждение)

  • отменить внесенное изменение

  • повторить отмененное изменение Действия с пунктами Todo:

  • добавить

  • удалить

  • отредактировать текст

  • отметить как выполненный

Требования к функционалу:

  • Все действия на сайте должны происходить без перезагрузки страницы.

  • Подтверждение действий (удалить заметку) выполняется с помощью диалогового окна.

  • Интерфейс должен отвечать требованиям usability.

  • После перезагрузки страницы состояние списка заметок должно сохраняться.

  • Можно пренебречь несоответствием редактирования текста с помощью кнопок отменить/повторить и аналогичным действиям с помощью комбинацияй клавиш (Ctrl+Z, Command+Z, etc.).

Технические требования:

  • Диалоговые окна должны быть реализованы без использования «alert», «prompt» и «confirm».

  • В качестве языка разработки допускается использовать JavaScript или TypeScript.

  • В качестве сборщика, если это необходимо, используйте Webpack.

  • Верстка должна быть выполнена без использования UI библиотек (например Vuetify).

  • Адаптивность не обязательна, но приветствуется.

  • Логика приложения должна быть разбита на разумное количество самодостаточных Vue-компонентов.

Особое внимание стоит обратить на следующие моменты:

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

  • Читабельность и наличие элементарной архитектуры.

  • Чистота и оформление кода — не менее важный фактор. Код должен быть написан в едином стиле (желательно в рекомендуемом для конкретного языка). Также к чистоте относятся отсутствие копипаста и дублирования логики.

Тестовое задание должно быть предоставлено в следующем виде:

  • Ссылка на публичный репозиторий (GitHub, BitBucket, GitLab) с исходным кодом.

  • Ссылка на сайт для тестирования функционала. Или Dockerfile и docker-compose.yaml, позволяющие развернуть локально командой docker-compose up работоспособную копию сайта.ехническое заданиеехническое заданиеехническое задание

Вот что у меня в итоге получилось.

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

Разберем по пунктам задание и возможные способы его решения:

Средствами Vue.js реализуйте небольшое SPA приложение для заметок. Тут все просто: используем Vue CLI для создания проекта.

Каждая заметка имеет название и список задач todo list, (далее — Todo). Каждый пункт Todo состоит из чекбокса и относящейся к нему текстовой подписи. — А вот и первая сложность, у нас будет два уровня абстракции: множество заметок и множество дел, которые составляют заметку.

Приложение состоит всего из 2х страниц. Тут возник вопрос — использовать ли Vue Router? Это же всего две страницы. Дальше из задания мы узнаем, что первая страница используется для отображения списка заметок, а вторая — для редактирования отдельной заметки. Конечно, можно переключать компоненты условным оператором, но раутер даст возможность перехода на сгенерированные страницы для каждой заметки. Но это уже больше двух страниц, или сгенерированные страницы считаются за одну, так как они по одному шаблону сделаны? После недолгих колебаний я решил все-таки его использовать.

Дальше следуют подробности по каждой странице, обратим внимание на два пункта:

Это про редактирование заметки. Решение тут же пришло мне в голову: создать массив, который запоминал бы историю изменений заметки, каждый элемент массива содержал бы состояние заметки до следующего изменения. С помощью кнопок можно было бы «скакать» по истории, и реактивность Vue нам очень в этом помогает.

Do и RedoDo и Redo

  • Все действия на сайте должны происходить без перезагрузки страницы. Это означает стандартное SPA, мы и так используем Vue CLI.

  • Подтверждение действий (удалить заметку) выполняется с помощью диалогового окна.

  • Диалоговые окна должны быть реализованы без использования «alert», «prompt» и «confirm».

    Диалоговое окно должно создавать Promise, который зависит от решения юзера. Желательно, чтобы решение было переиспользуемым для всех наших случаев. Помучавшись над решением, я пришел к выводу, что лучше использовать готовое решение, к тому же запретов на пакеты не было. Я использовал vue-modal-dialogs — очень удобная библиотека, рекомендую. Надеюсь её перепишут для Vue 3.

  • Интерфейс должен отвечать требованиям usability. Другими словами, он должен быть удобным. Лучше бы написали конкретные требования, так не очень понятно.

  • После перезагрузки страницы состояние списка заметок должно сохраняться. — Так как серверной части у нашего приложения не планируется, заметки следует сохранять на стороне клиента, для этого есть два решения Cookie и localStorage. Выбираем кому что ближе. Я выбрал localStorage. К тому же я решил не использовать Vuex, а вместо него использовать локальное хранилище, как единый источник истины. Для небольшого приложения без бэка это выглядит разумным решением, экономящем время, в других случаях я не рекомендовал бы так делать.

  • В качестве языка разработки допускается использовать JavaScript или TypeScript. -, а разве есть еще варианты? Честно говоря, ТЗ оставляет ощущение, что тот кто его составлял плохо знаком с Vue. TypeScript на Vue 2 спорно применять, слабая поддержка. Посмотрим, что будет на Vue 3.

    Важное замечание для тех, кто захочет воссоздать его с нуля на новой версии! Мой проект написан на Vue 2, обещали, что старый синтаксис будет работать, но все же.

  • В качестве сборщика, если это необходимо, используйте Webpack. Vue CLI это и есть Webpack, настроенный для удобства создания SPA на Vue.js

  • Верстка должна быть выполнена без использования UI библиотек (например Vuetify) — это минус. Для того чтобы «оживить» приложение, я использовал Material Icons от Гугла, вместо кнопок. Не знаю, оценил ли заказчик, он так и не ответил.

  • Адаптивность не обязательна, но приветствуется. — flexbox в помощь.

  • Логика приложения должна быть разбита на разумное количество самодостаточных Vue-компонентов. — у меня вышло семь: 2 на страницы, еще заметка, тудушка, заголовок заметки, кнопка-иконка и диалоговое окно.

    Остальные пункты менее важны.

Еще из вкусностей:

Я использовал пакет v-click-outside. Название говорит само за себя. Он добавляет директиву, которая срабатывает при клике вне элемента. Можно было написать самому, но я решил не изобретать велосипед. Использовал для отмены редактирования тудушки, если пользователь кликнул где-то еще. Это в задании не было, включим это в юзабилити.

Еще мне пришла в голову такая мысль:, а что делать, если юзер захочет покинуть страницу редактирования заметки? Куда повесить вызов диалогового окна: на историю браузера, на кнопки меню? Есть элегантное решение. Vue-Router добавляет хуки жизненного цикла в компонент. Хук beforeRouteLeave поможет нам во всех ситуациях когда пользователь пытается покинуть страницу. Пусть наш хук вызывает модальное окно. Только не забыть сделать его асинхронным, ведь окно возвращает промис. Например, вот так:

  async beforeRouteLeave (to, from, next) {
    if (await confirm('Do you realy want to leave this page?',
       'All unsaved changes will be lost.')) {
        this.clearNote()
        next()
      } else{
        next(from)
      }
  }

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

Если возникли вопросы и замечания, прошу в комментарии или личку.

© Habrahabr.ru