Организуйте свой процесс, пишите чистый код и тестируйте как начальник

В этом руководстве я расскажу, как писать чистый, организованный и легко читаемый код при создании приложения CRUD на JavaScript с использованием fetch.

Мы создадим простое приложение, которое позволит нам просматривать животных в вымышленном зоопарке и переключать их профили одним щелчком мыши. Я стараюсь объяснить не только то, как работает выборка, но и то, как преобразовать ваш код в модули многократного использования.

Для этого урока я буду использовать файлы, которые вы можете найти в этом репо. Убедитесь, что сервер запущен (отметьте README).

Шаг 1. Прочтите HTML-файл

Представьте, что нам дали HTML-файл:

Мы видим, что у нас есть три основных раздела: zoo-animals, в котором будут перечислены наши животные, animal-info, который будет являться карточкой профиля животного, по которому щелкнули мышью, и create-animal форма.

Найдите время, чтобы проверить:

  1. Идентификаторы уникальны - это поможет вам найти узлы с querySelector функциями.
  2. Атрибуты имени в форме такие же, как ключи в файле JSON - это поможет вам сделать выборку POST / PATCH плавно.
  3. Если есть тег скрипта, указывающий на нужный файл - без него ваш JS не будет работать.

Как видите, наши атрибуты имени формы не пересекаются с тем, что мы видим в файле db.json: вместо свирепости это дикий.

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

Идеально. Перейдем к файлу JS. Вот как сейчас выглядит наше приложение:

Шаг 2: DOMContentLoaded

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

Давайте сделаем это и давайте включим console.log, чтобы увидеть, подключен ли JS-файл - если мы обновим страницу в браузере, мы должны увидеть консольное сообщение.

document.addEventListener('DOMContentLoaded', (event) => {
    console.log('DOM fully loaded and parsed');
});

Проверим консоль:

Хорошо, теперь, когда мы знаем, что наш JS-файл работает, давайте проследим за процессом.

Процесс

  1. Сохраните все необходимые querySelectors в переменных на самом верху. Как только вы установите переменную, запустите ее в консоли. Если приложение требует некоторого переключения, создайте для него let. Если какой-либо из существующих элементов должен иметь прослушиватель событий, вы можете добавить его уже!
  2. Напишите первую выборку (это GET и следуйте этой структуре: URL, затем, затем + slapItOnTheDom).
  3. Console.log, чтобы увидеть, как выглядят возвращенные данные.
  4. Напишите slapItOnTheDom метод. Создавайте новые элементы. Дайте этим элементам наборы данных и _14 _ / _ 15_. Добавьте eventListeners туда, где они принадлежат, и не забудьте написать вспомогательную функцию appendChild.
  5. Выполните краш-тест и отладьте.
  6. Добавьте другие выборки (убедитесь, что заголовки и тело отправляют правильные данные).
  7. Убедитесь, что preventDefault находится там, где вы хотите!

Шаг 3: querySelector

Давайте сохраним все элементы, о которых мы будем говорить позже, в переменных:

  • ul#zoo-animals - вот где у нас будет список животных.
  • div#animal-info - это профиль животного.
  • form#create-animal.
  • input#submit-new - это наша кнопка отправки.

Более того, поскольку профиль животного можно переключать, давайте создадим переменную seeAnimal с выражением let, поскольку мы будем изменять ее значение.

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

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

Ваш код должен выглядеть следующим образом:

Давайте console.log все переменные. Знаете ли вы, что console.log может принимать сколько угодно аргументов? Мне нравится быть описательным, см. Ниже:

Ой, у тебя ошибка!

Если вы посмотрите на ошибку, вы увидите, что она указывает на строку 16 в файле script.js в моем редакторе. Ошибка говорит, что createNewAnimal не определен. Верно.

В моем eventListener я вызываю функцию, которой еще не существует. Чтобы ошибка исчезла и код не сломался, вы можете на мгновение закомментировать эту строку или заменить имя функции на null или console.log(“submitted!”).

Я выбираю функцию обратного вызова анонимной стрелки с журналом консоли:

Вот что я получаю:

Теперь, если переменная работает, вы должны не только увидеть HTML-узел в своей консоли, но также, если вы наведете на него курсор в консоли справа, выбранный элемент должен выделиться в DOM слева.

Проверьте каждый элемент, чтобы увидеть, действительно ли то, что, по вашему мнению, вы должны получить, является выбранным. Если вы видите неопределенное значение, это означает, что нужно проверить свои предположения.

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

Шаг 4: Первая выборка (GET)

Хорошо, давайте пригласим животных! Мы напишем нашу первую выборку, которая будет запускаться, как только переменные будут объявлены / определены.

Я считаю, что получение GET очень полезно - оно простое, быстрое и дает почти мгновенный эффект. Напишем это:

fetch('http://localhost:3000/animals')  
.then(response => response.json())  
.then(animal => animal.forEach(slapItOnTheDiv))

OK. Давайте теперь рассмотрим это построчно. Мы начинаем с вызова функции fetch с аргументом URL-адреса - в этом случае мы получаем данные из файла JSON, который запущен на нашем сервере.

Поскольку fetch возвращает неразрешенное обещание вместо данных, нам нужны следующие шаги для выполнения обещания. И поскольку fetch является асинхронной функцией, мы хотим указать JavaScript, чтобы он дождался завершения загрузки, чтобы перейти к следующему шагу - вот почему мы используем .then.

Если мы этого не сделаем, fetch еще не завершится, когда JS перейдет на следующую строку, и код сломается. Метод .then принимает возвращаемое значение предыдущего метода и действует в соответствии с ним.

Давайте посмотрим, что будет возвращаемое значение, введя console.log:

fetch('http://localhost:3000/animals')  
.then(console.log)  

Мы получили ответ. Это означает, что мы отправили запрос на сервер, и сервер отправил нам данные обратно. Однако посмотрите на атрибут body в ответе ниже:

Это ReadableStream, который представляет собой читаемый поток байтовых данных. Давайте превратим его в то, с чем мы можем работать, в ответ JSON, и снова проверим консоль.

fetch('http://localhost:3000/animals')  
.then(response => response.json())
.then(console.log)

Отлично, мы вернули массив объектов. Теперь самое время проверить, все ли у объектов есть ключи.

Этот вопрос может быть полезен, если вы не можете отобразить некоторые данные на своей странице, например может случиться так, что image_url на самом деле не является строкой или URL-адресом, и поэтому изображения не будет.

Итак, давайте посмотрим ... Кажется, у последнего объекта разные ключи?

При осмотре вы можете увидеть, что его ключи идут в другом порядке, но это нормально, потому что объекты в JavaScript представляют собой неупорядоченную коллекцию, и поэтому порядок, в котором идут ключи, не имеет значения с точки зрения программирования.

Однако я заметил, что его URL-адрес изображения неверен, поэтому я знаю, чего ожидать позже.

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

fetch('http://localhost:3000/animals')
  .then(response => response.json())
  .then(animals => animals.forEach(animal => animal(slapItOnTheDOM)))

Мы можем сделать его короче:

fetch('http://localhost:3000/animals')
.then(response => response.json())
.then(animals => animals.forEach(slapItOnTheDOM))

Функция slapItOnTheDOM будет воздействовать на каждый из элементов повторяемого массива.

Теперь давайте создадим функцию slapItOnTheDOM, которая принимает объект-животное в качестве аргумента и псевдокодирует наши следующие шаги:

Давайте позаботимся о li и добавим console.logs, чтобы проверить, работает ли он:

Вот консоль:

Идеально. Консоль реагировала на каждый элемент, который мы получали в выборке. Теперь давайте займемся задачами с кнопками и посмотрим, правильно ли они отображаются:

Обратите внимание на динамический идентификатор. Это поможет нам позже удалить эту кнопку, когда мы будем удалять животное и его li и кнопку. Вот консоль:

И последнее, но не менее важное: давайте добавим прослушиватель событий. Мы воспользуемся тем фактом, что находимся в функции с доступом к объекту-животному, и поэтому передадим животное в функцию удаления (которую мы еще не написали).

buttond.addEventListener('click', () => {deleteAnimal(animal) })

В целях тестирования давайте временно заменим эту функцию на console.log:

buttond.addEventListener('click', console.log(animal)})

Мы знаем, что это работает, но… Поскольку метод не является обратным вызовом, он запускается, как только страница была отрисована без нажатия кнопки. Не забудьте использовать функции как обратные вызовы:

buttond.addEventListener('click', () => console.log(animal))

Для записи, хорошо также проверить, работает ли не только первый, но и последний элемент (особенно, если вы используете закрытие в выборках или getElementById или querySelector в целом). Итак, давайте сделаем это:

Милая. Теперь сделаем то же самое с кнопкой обновления. Затем, раскомментируем createAnimal и напишем сейчас updateAnimal и deleteAnimal, поскольку у нас уже есть eventListener для этого.

Мы можем просто добавить console.logs в функции, чтобы код не сломался.

На этом этапе ваш код должен выглядеть следующим образом:

Большой.

Шаг 5: СОЗДАТЬ

Теперь напишем функцию createAnimal. Прежде чем мы перейдем к кодированию, давайте сделаем наши шаги псевдокодом:

# include preventDefault;
# create a helper function that will gather the data from the form;
# save the form inputs to a variable;
# fetch;

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

Итак, мы хотим иметь функцию, которая будет собирать все данные из формы. Я настоятельно рекомендую этот подход, потому что мы будем использовать ту же функцию позже в случае обновления зверька.

Наша вспомогательная функция будет выглядеть следующим образом:

Мы просто собираем имена входов нашей формы и сохраняем их как объект.

Поскольку эта функция будет вызываться внутри другой функции, у нее будет доступ к событию вызывающей стороны. Теперь мы можем сохранить этот новый объект-животное в переменной внутри функции createAnimal:

function createAnimal(event){
     event.preventDefault();
     let newAnimal = gatherFormData();
}

newAnimal теперь определяется возвращаемым значением вспомогательной функции. Милая. Перейдем к выборке.

На этот раз наша выборка будет принимать два аргумента:

  1. URL-адрес.
  2. Объект с запросом, определяющий метод, заголовки (которые также являются объектом) и тело; поскольку это объект, после каждого из этих ключей потребуется запятая.

Пошагово:

  1. Метод для этой выборки будет «POST» - запомните кавычки.
  2. Заголовки запроса указывают, что мы отправляем и что хотим получить обратно - помните, что и ключи, и значения в заголовках должны быть в кавычках.
  3. Тело, которое мы превращаем из JSON (объекта) в строку.

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

А теперь помните про обещания и асинхронность? Да, добавьте эти два .then. И, угадайте, что ... Мы снова будем использовать тот же метод для публикации нового животного в DOM, что и при использовании GET fetch - slapItOnTheDOM!

Давайте создадим новое животное - откройте вкладку сети инструментов разработчика, чтобы узнать, создано ли ваше животное:

Woohoo! В нашем зоопарке появился козел Эрик.

Шаг 6: ОБНОВЛЕНИЕ

Вы заметили, что у нас есть кнопка обновления, но функция называется editAnimal? Это потому, что эта функция фактически заставит появиться форму редактирования, а затем вызовет функцию обновления, чтобы произвести обновление.

Эта функция принимает объект животного объекта только для того, чтобы иметь возможность отображать данные животного в форме.

Давайте посмотрим, что произойдет, когда мы нажмем кнопку обновления сейчас - я сначала добавлю console.log, чтобы увидеть, дает ли то, что я щелкнул в DOM, мне объект животного, о котором я думал:

Похоже, это сработало! Теперь напишем функцию обновления. Эта функция будет использовать вспомогательный метод для сбора данных, а затем вызовет два метода для обновления животного на задней и внешней стороне.

Мы вызываем updateOnFrontEnd без аргумента, потому что в этом случае по умолчанию он примет значение, возвращаемое предыдущей функцией.

function updateAnimal(event, animal) {
    event.preventDefault();
    let updatedAnimal = gatherFormData()
    updateOnBackend(updatedAnimal, animal.id)
    .then(updateOnFrontEnd)
  }

Хорошо, давайте сделаем обновление серверной части. Как видите, метод updateOnBackend принимает два аргумента: объект, полученный из собранных данных, и идентификатор животного, который нам нужно будет добавить к URL-адресу.

Этот синтаксис не должен вас сбивать с толку, поскольку он очень похож на синтаксис create.

Давайте введем console.log, чтобы увидеть, работает ли выборка:

Да! Теперь не забудьте удалить .then вместе с console.log, потому что, хотя он и хорош для тестирования этой части, он изменяет возвращаемое значение этой функции.

Мы видим, что код работает плавно:

Давайте проверим вкладку ответа, чтобы увидеть, прошел ли запрос:

Большой. Если бы мы обновили страницу, мы бы увидели разницу. Однако мы хотим, чтобы обновление DOM происходило мгновенно. Давайте сделаем это.

Функция будет управлять DOM для редактирования нашего li. Обратите внимание на querySelector, который не только динамически находит правильный идентификатор животного в наборе данных, но также ищет диапазон, который ему принадлежит.

function updateOnFrontEnd(animal){
    const animalSpan = animals.querySelector(`li[data-id="${animal.id}"]>span`)
    animalSpan.innerText = `${animal.name} the ${animal.species}` 
  }

Давайте включим console.log в начало функции, чтобы узнать, запускается ли она когда-либо (в случае, если она не обновляет DOM), и проверим:

Давайте теперь изменим Джоуи на Джо. Ознакомьтесь с формой, консолью и DOM.

Ура! Джоуи теперь Джо.

Вот как должен выглядеть ваш код сейчас:

Шаг 7: УДАЛИТЬ

А теперь давайте представим, что мы хотим выпустить бабочку в дикий мир.

Нам понадобится функция удаления, которая сначала находит все элементы, относящиеся к животному, затем отправляет запрос на удаление и, наконец, удаляет присутствие животных в DOM.

И Джон ушел:

К настоящему моменту ваш код выглядит следующим образом:

Продолжайте и добавляйте много волшебства CSS! Или заставьте форму обновления исчезнуть после завершения обновления.

Или снова сделайте формы пустыми после отправки. Делитесь своими результатами!