[TIL] To Do List 생성 심화 실습

신재욱·2023년 3월 6일
0
post-thumbnail

📒 오늘 공부한 내용

🔍수업목차

[15-1] API란?
[15-2] 웹페이지의 통신
[15-3] Promise 객체
[15-4] 구조 분해 할당
[15-5] 값의 복사
[15-6] 구조분해할당 적용
[15-7] JSON 데이터 활용
[15-8] 렌더링 딜레이 개선

✅ API


API란, 어떠한 프로그램에서 제공하는 기능을 사용자가 활용할 수 있도록 만들어 둔 인터페이스다.


우리가 접근하는 사이트 혹은 프로그램을 어떠한 식당이라고 가정해 보겠다.

그 식당에서 팔고 있는 메뉴들을 보여주는 메뉴판, 이것을 우리는 API 명세서라고 부른다.

우리가 사용할 수 있는 기능들이 무엇이 있는지, 혹은 어떻게 사용하는 것인지가 적혀져 있다.

메뉴를 주문하는 행위, 해당 사이트, 프로그램에게 어떠한 기능을 사용하겠다 라고 전달하는 행위를 요청(Request)이라고 한다. 이때 요청을 전달하는 사람을 클라이언트(Client)라고 부른다.

이때 그 주문, 요청을 받아서 실제로 음식을 조리해주는 요리사에게 주문을 전달해주는 직원과 같은 역할.
요청을 받아 데이터를 가공해주는 서버에게 어떤 요청이 들어왔는지를 전달해주는 것이 바로 API다.

그리고 우리가 돌려 받게 되는 음식, 데이터를 응답(Response)이라고 한다.

✅ HTTP(Hyper Text Transfer Protocol)


API와 같이 다른 서버와 통신을 할 때는 특정한 규칙에 맞춰 그 통신이 이루어져야 한다.
HTTP는 간단하게 이야기해서 서로 다른 서버 간에 문자 형식으로 데이터를 주고 받을 때 지켜야 하는 규약이다.

HTTP에서 요청을 보낼 때는 대상 서버로 HTTP 메세지를 보내고 요청 헤더(Request header)요청 바디(Request body)가 그 안에 담겨진다.

⏩ Request message

이미지 가장 윗줄의 GET /data/2.5/weather? HTTP/1.1에 해당하는 부분은 start-line이라 부른다.
우리가 사용한 HTTP Method, 요청 URI, HTTP의 버전을 담고 있다.

Request Header

그리고 그 아래의 Host부터가 요청 헤더에 속한다.
Request header에는 다양한 정보가 담기게 되는데, 요청을 받는 서버의 이름, 서버의 버전, 전달하는 컨텐츠의 타입, 요청 날짜, 요청을 보낸 컴퓨터의 정보 등 수많은 내용이 담겨진다.
그리고 Request header의 내용이 모두 종료되면 하나의 빈 줄로 Request body와 구분해 준다.

Request Body

Request body에는 우리가 서버로 혹은 다른 사용자가 우리의 서버로 전달하고자 하는 컨텐츠를 담게 된다.
이 때, 어떠한 메서드를 통한 요청인지에 따라 Request body를 담을 수 있는지, 없는지가 결정된다.

⏩ Response Message

HTTP/1.1 200 OK에 해당하는 부분은 status-line이라고 부릅니다. HTTP 버전과 상태 코드(Status code), 응답 메세지를 담고 있다.
그리고 두번째 줄부터가 Response header에 속한다.

Response Header

Response header도 Request header와 마찬가지로 응답 날짜, 응답을 전달한 버의 이름, 서버의 버전, 컨텐츠의 타입 등을 담고 있다.
Response header의 내용이 모두 종료되면 하나의 빈 줄로 Response body와 구분해 준다.

Response Body

Response body에는 실제로 응답 리소스 데이터가 담겨져 있다.

⏩ HTTP Request Method

HTTP 요청은 각 요청마다 Method를 사용하며 그 역할이 모두 다르다.

HTTP Status Code

✅ 멀티 스레드와 싱글 스레드


우리의 운영체제에게 프로세스라고 하는 작업 영역을 할당 받아 어떠한 동작을 수행한다.
코드의 실행, 프로그램의 실행, 파일 다운로드 등이 모두 프로세스별로 할당이 되고, 각 프로세스 내부에는 할당 받은 업무를 처리할 스레드가 존재한다.

멀티 스레드

싱글 스레드

✅ 동기, 비동기


동기

  • 하나의 작업이 종료될 때까지 다음 동작은 대기하는 실행 방식을 의미한다.

비동기

  • 하나의 작업이 진행됨과 동시에 또 다른 작업도 함께 진행되는 실행 방식을 의미한다.

각각 자료구조 stackqueue의 형태를 띄고 있다.

예를 들어, 위와 같은 코드를 실행하면, setTimeout 함수는 지연 시간을 지정해주지 않았음에도 불구하고 비동기 함수이기 때문에 callback queue 영역으로 옮겨진다.

call stack 영역에 쌓인 함수는 LIFO 방식으로 수행되기 때문에 func3, func2, func1 순서로 함수가 완료된다.
그리고 해당 함수들이 모두 완료되면 그 후에 callback queue에 담긴 setTimeout 함수가 call stack으로 옮겨져 실행되며 마지막으로 "setTimeout call" 메세지가 출력된다.

⏩ 비동기 처리

  • 자바스크립트는 싱글 스레드이기 때문에 한번에 하나의 동작만 수행할 수 있다.
  • 이러한 한계점을 해결, 비동기 작업을 수행하기 위해 가장 먼저 나온 해결책은 callback 함수를 활용하는 것이었다.
  • 하지만 callback 함수가 여러번 중첩되는 경우 가독성에서 심각한 손해를 보게 되는 콜백 지옥(callback hell)을 만나게 되는 문제가 있다.

⏩ Promise

Promise는 현재는 얻을 수 없지만 추후 작업이 완료되면 받아올 수 있는 데이터에 대한 접근 수단의 역할을 해준다. new Promise()와 같은 코드를 입력하여 직접 생성해 줄 수 있다.

  • 비동기 처리가 완료되면(Fulfilled), resolver가 호출되고 비동기 처리에 실패하면(Rejected), reject가 호출된다.
  • 위 함수를 보면 resolver의 실행을 2초간 지연시키고 있기 때문에 Promise 객체를 돌려 받은 뒤 2초가 지나기 이전에 참조하려 하면 Pending 상태임을 돌려받게 된다.
  • 아직 resolver가 호출되지 않았기 때문이다.

✅ then(), catch()


통신을 통해 응답을 받아오는 과정은 코드가 실행되는 과정보다 느린 경우가 대다수다.
그렇기 때문에 통신 또한 비동기 처리가 필요하다.

비동기 통신을 처리해주는 방법은 여러가지가 있는데, then(), catch() 메서드를 사용해서 처리하는 방법이 있다.

⏩ then()

fetch()를 사용해주면 JavaScript에서 바로 통신을 수행할 수 있습니다.

const communicationResult = fetch(HTTP Request)
console.log(communicationResult)

하지만 위와 같이 작성하면 정상적인 응답을 받지 못한다는 것을 확인했다.
그래서 응답이 돌아올 때 까지 기다려 주어야 하는데, 이때 then() 메서드를 사용할 수 있다.

fetch(HTTP Request).then()

fetch()를 사용한 후 응답을 받아올 때 까지 기다리도록 fetch()에 붙여서 then()을 사용하면 된다.
then() 메서드는 fetch()뿐만 아니라 Promise 객체를 돌려주는 함수라면 언제든 사용할 수 있다.

fetch(HTTP Request)
	.then((res) => {
		console.log(res)
	})

then() 메서드 내부에 익명 함수를 하나 만들고 그 안에서 매개변수로 데이터를 받을 수 있는데,
이때, 받아오는 데이터가 바로 통신의 결과로 우리에게 돌려준 데이터다.

⏩ catch()

만약 통신에서 문제가 생기거나 then() 메서드 내에서 로직을 수행하다가 에러를 만난다면 우리는 catch() 메서드로 그 분기를 나눠줄 수 있다.

fetch(HTTP Request)
	.then((res) => {
		console.log(res)
	})
	.catch((err) => {
		console.error(err)
	})

catch() 메서드 내부에도 익명 함수를 넣어줄 수 있는데, 해당 함수의 매개변수로 error의 내용을 받아올 수도 있습니다.

✅ 구조 분해 할당 (Destructuring assignment)


구조 분해 할당이란, 구조화 되어 있는 배열, 객체와 같은 데이터를 분해하여 각각의 변수에 다시 할당하는 것을 이야기한다.

값을 복사해주고 싶다면 어떻게 할 수 있을까?

⏩ 배열 구조분해할당

  1. 먼저 선언 키워드를 적은 후
  2. 변수명이 위치하는 자리에 변수명을 모아 둔 배열을 넣고
  3. 데이터가 담긴 배열을 할당하면 된다.
const arr = [ 1, 2, 3, 4, 5 ];

let [ one, two ] = arr; 

console.log(one); 
// 1
console.log(two); 
// 2

⏩ 객체 구조분해할당

  1. 선언 키워드를 먼저 적어주고
  2. 대괄호가 아닌 중괄호를 입력해서 구조분해할당을 수행합니다.
  3. 배열과 같이 중괄호 안에 담기는 명칭들이 변수명이 된다.
  4. 뒤에는 할당 연산자를 적어주신 후에 구조분해할당 해주고자 하는 객체를 적어주시면 된다.
const obj = {
	name: "otter",
	gender: "male"
}

코드를 입력하세요

let { name, gender } = obj

//let { name: a, gender: b } = obj

✅ 값의 복사


만약 배열 자체를 복사하고 싶다면 어떻게 해야할까?

const arr = [ 1, 2, 3, 4, 5 ];

const newArr = arr

이렇게 할 경우 arrnewArr이 같은 주소값을 공유하기 때문에 복사라고 이야기하기엔 어려운 부분이 있다.
그렇기 때문에 복사를 위해서 사용해줄 수 있는 연산자가 존재한다.

⏩ 스프레드 연산자 (spread operator)

  • 스프레드 연산자는 하나로 뭉쳐져 있는 값들의 집합을 전개해주는 연산자다.
  • 전개하고 싶은 배열의 이름 앞에 ...을 붙이는 것으로 사용할 수 있다.
const arr = [ 1, 2, 3, 4, 5 ];
console.log(...arr) // 1, 2, 3, 4, 5

⏩ 얕은 복사 (shallow copy)

spread 연산자를 사용하면 값의 복사를 수행할 수도 있다.

const arr = [ 1, 2, 3, 4, 5 ];
const newArr = [ ...arr ];

이렇게 하면 기존에 arr 배열이 가지고 있던 주소값과 전혀 다른 별개의 새로운 배열이 newArr이라는 변수에 담기게 된다. (참조 타입 값의 복사 성공)

⏩ 깊은 복사 (deep copy)

heap이라는 임시 저장 메모리에 담기게 된다.

이 heap이라는 영역은 참조 타입 데이터와 같이 그 데이터의 크기가 유동적으로 변할 수 있다는 특징을 가지고 있다.

스프레드 연산자를 사용해서 객체의 중괄호를 한번 벗겨냈을 때 다른 property들은 모두 주소의 연결을 끊고 새 주소를 가진 데이터로 완전히 복사가 되었지만

이 때 중첩된 favoriteFood 객체는 펼쳐지지 못하여 원본 객체의 favoriteFood와 복사된 객체의 favoriteFood는 여전히 같은 주소를 공유하고 있다.

이러한 문제를 해결하기 위해 JSON.stringify()JSON.parse()를 사용할 수 있다.

📌 JSON

Javascript Object Notation은 JS 객체 문법을 이용해 구조화된 데이터를 표현하기 위한 문자열 기반의 표준 포맷

const obj = {
	name: "otter",
	gender: "male",
  favoriteFood: {
		first: "sushi",
		second: "hamburger"
  }
}

const copy = JSON.stringify(obj)

console.log(copy)

// {"name":"otter","gender":"male","favoriteFood":{"first":"sushi","second":"hamburger"}}

위의 예시처럼 문자열로 변환이 된다면, copy에 담긴 값은 객체가 아닌 JSON이기 때문에 완전히 새로운 주소를 가지게 된다.
이후, JSON.parse()를 사용해서 JSON 데이터 포맷을 다시 객체 형태로 바꿔주면

const deepCopy = JSON.parse(copy)

console.log(deepCopy)

/*
	{
		name: "otter",
		gender: "male",
	  favoriteFood: {
			first: "sushi",
			second: "hamburger"
	  }
	}
*/

deepCopy에 담기는 것이기 때문에 원본 객체와는 전혀 다른 주소를 가진 객체가 생성되는 것입니다.
이러한 복사를 깊은 복사라 부릅니다.

📌 오늘 실습 결과


이미지

JavaScript

const todoInput = document.querySelector('#todo-input');
const todoList = document.querySelector('#todo-list');

const savedWeatherData = JSON.parse(localStorage.getItem('saved-weather'));
const savedTodoList = JSON.parse(localStorage.getItem('saved-items'));

const createTodo = function (storageDate) {
    let todoContents = todoInput.value;
    if (storageDate) {
        todoContents = storageDate.contents;
    }

    const newLi = document.createElement('li');
    const newSpan = document.createElement('span');
    const newBtn = document.createElement('button');

    newBtn.addEventListener('click', () => {
        newLi.classList.toggle('complete');
        saveItemsFn();
    })

    newLi.addEventListener('dblclick', () => {
        newLi.remove();
        saveItemsFn();
    })

    if (storageDate?.complete === true) {
        newLi.classList.add('complete')
    }

    newSpan.textContent = todoContents;
    newLi.appendChild(newBtn);
    newLi.appendChild(newSpan);
    todoList.appendChild(newLi);
    todoInput.value = '';
    saveItemsFn();
}

const keyCodeCheck = function () {
    if (window.event.keyCode === 13 && todoInput.value.trim() !== '') {
        createTodo();
    }
}

const deleteAll = function () {
    const liList = document.querySelectorAll('li');
    for (let i = 0; i < liList.length; i++) {
        liList[i].remove()
    }
    saveItemsFn();
}

const saveItemsFn = function () {
    const saveItems = [];
    for (let i = 0; i < todoList.children.length; i++) {
        const todoObj = {
            contents: todoList.children[i].querySelector('span').textContent,
            complete: todoList.children[i].classList.contains('complete')
        };

        saveItems.push(todoObj);
    }

    saveItems.length === 0 ? localStorage.removeItem('saved-items') : localStorage.setItem('saved-items', JSON.stringify(saveItems));

};

if (savedTodoList) {
    for (let i = 0; i < savedTodoList.length; i++) {
        createTodo(savedTodoList[i])
    }
}

const weatherDataActive = function ({ location, weather }) {
    const weatherMainList = [
        'Clear',
        'Clouds',
        'Drizzle',
        'Rain',
        'Snow',
        'Thunderstorm'
    ];
    weather = weatherMainList.includes(weather) ? weather : 'Fog';
    const locationNameTag = document.querySelector('#location-name-tag')
    locationNameTag.textContent = location;
    console.log(weather)
    document.body.style.backgroundImage = `url('./images/${weather}.jpg')`


};




const weatherSearch = function ({ latitude, longitude }) {
    const openWeatherRes = fetch(
        `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=33d9d8b3fcd26e46f20dad0561ec7a81`
    ).then((res) => {
        return res.json();
    }).then((json) => {
        const weatherData = {
            location: json.name,
            weather: json.weather[0].main
        };
        weatherDataActive(weatherData);
    }).catch((err) => {
        console.log(err)
    })
};


const accessToGeo = function ({ coords }) {
    const { latitude, longitude } = coords;
    // shorthand property
    const positionObj = {
        latitude,
        longitude
    }
    weatherSearch(positionObj);
}

const askForLocation = function () {
    navigator.geolocation.getCurrentPosition(accessToGeo, (err) => {
        console.log(err);
    });
}
askForLocation();
if (savedWeatherData) {
    weatherDataActive(savedWeatherData);
}

// const promiseTest = function () {
//     return new Promise((resolver, reject) => {
//         setTimeout(() => {
//             resolver(100);
//             // reject('error');
//         }, 2000);
//     });
// };

// promiseTest().then((res) => {
//     console.log(res);
// })
profile
1년차 프론트엔드 개발자

0개의 댓글

관련 채용 정보