Организирайте своя процес, напишете чист код и тествайте като шеф

В този урок ще разгледам как да напиша чист, организиран и лесно четим код, когато създавам 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 метод. Създайте нови елементи. Дайте на тези елементи набори от данни и innerHTML/innerText. Добавете 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))

ДОБРЕ. Нека сега го разгледаме ред по ред. Започваме с извикване на функцията 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 (обект) в низ.

Сега методът ще изглежда по следния начин:

Сега, помните ли Promise и async нещата? Да, добавете тези две .thens. И познайте какво… Ние отново ще използваме същия метод за публикуване на новото животно в DOM, както направихме с GET fetch — slapItOnTheDOM!

Нека създадем ново животно — отворете мрежовия раздел с инструменти за разработчици, за да видите дали вашето животно е създадено:

Уаууу! Вече имаме козела Ерик в нашия зоопарк.

Стъпка 6: АКТУАЛИЗАЦИЯ

Забелязали ли сте, че имаме бутон за актуализиране, но функцията се нарича editAnimal? Е, това е така, защото тази функция всъщност ще накара формуляра за редактиране да се появи и след това ще извика функцията за актуализиране, за да направи актуализацията.

Тази функция приема обекта на обекта животно само за да може да покаже данните на животното във формуляра.

Нека да видим какво се случва, когато щракнем върху бутона за актуализиране сега — първо ще добавя console.log, за да видя дали това, което щракнах върху DOM, ми дава животинския обект, за който си мислех:

Изглежда, че проработи! Сега нека напишем функцията за актуализиране. Тази функция ще използва помощния метод за събиране на данни и след това извиква два метода за актуализиране на животното в задния и в предния край.

Извикваме updateOnFrontEnd без аргумент, защото в този случай по подразбиране ще приеме върнатата стойност на предишната функция.

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

Добре, нека направим задната актуализация. Както виждате, методът updateOnBackend приема два аргумента: обект, който идва от събраните данни и ID на животното, което ще трябва да добавим към 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 магия! Или накарайте формуляра за актуализация да изчезне, след като актуализацията приключи.

Или направете отново формулярите да имат празни полета за въвеждане след изпращането. Споделете вашите резултати!