1. AJAX, REST API : 230327
1) View단에서 심어놓은 데이터, JS에서 사용(최종)
- JS에서 자식 노드에서 부모 노드는 1개라서 부모 노드 찾기는 쉽다. 반대로 자식 노드는 여러 개이므로 한 번에 찾기가 힘들다.(여러 조건이 필요)
- View단에서 심어놓은 데이터를 심기 위해서 html dataset을 이용한다. 하지만, 우리는 thymeleaf를 사용하기 때문에 타임리프에서 사용하는 dataset을 이용하게 된다.
- 위의 코드에서
let categoryId =1;
로 데이터를 상수로 지정했지만, 우리가 원하는 형태는 사용자 입력에 의해서 데이터를 심어 놓고 JS에서는 그 심어놓은 데이터를 사용해야 한다.- html에서
data-cid=${c.id}
로-
로 구분지어 dataset에 의해 데이터를 심었었다. - 이것을 JS에서는 el라는 변수에 담아서 사용하는데 el.dataset.cid 로 꺼내서 쓰며 html에서 심은
-
로 구분된 dataset의 뒷부분 값으로 식별하여 사용한다.
- html에서
- URL에 백틱을 사용하는데 이 백틱이 매우 중요하다!! (JSON 데이터를 꽂기 위해서!!) 그리고 이것은 비동기 처리 요청에서 사용된다.
a. HTML의 dataset 개념
- 아래 예시 코드처럼 html에서
-
라는 구분자를 이용하여 dataset을 심고 JS에서.
라는 구분자를 이용하여 dataset을 꺼낸다.
<div id="user" data-id="1234567890" data-user="carinaanand" data-date-of-birth>
Carina Anand
</div>
const el = document.querySelector("#user");
// el.id === 'user'
// el.dataset.id === '1234567890'
// el.dataset.user === 'carinaanand'
// el.dataset.dateOfBirth === ''
b. 타임리프의 dataset 사용법
- 타임리프에서는 th:attr 속성을 이용해서
th:attr="data-cid=${c.id}"
이렇게 dataset을 심는다. - dataset 심는 방법 :
<li th:attr="data-cid=${c.id}" class="" th:class="${#strings.equals(param.c, c.id)}?'menu-selected'" th:each="c: ${categoryList}" >
c. dataset 최종 실습 코드** :
<nav class="menu-category">
<div>
<h1 class="text-normal-bold">메뉴분류</h1>
</div>
<ul>
<li class="" th:class="${param.c} ==null ? 'menu-selected'">
<a href="list">전체</a>
</li>
<li th:attr="data-cid=${c.id}" class="" th:class="${#strings.equals(param.c, c.id)}?'menu-selected'"
th:each="c: ${categoryList}" >
<a href="?c=1" th:href="@{list(c=${c.id})}" th:text="${c.name}">커피음료</a>
</li>
</ul>
</nav>
window.addEventListener("load", function() {
let ul = document.querySelector(".menu-category>ul");
// 클릭 시 이벤트 요청 :
// 1) AJAX에서 addEventListener 하지마 : 여러 함수를 쓸 때 사용한다.(함수 누적 시, 사용한다.)**
// 2) AJAX에서 람다도 쓰지 : 람다는 지역화를 쓸 수가 없어서!!**
// 3) 위의 2개는 css의 연장선에서 사용가능
ul.onclick = function(e){
// a태그는 기본 행위가 있어서 그것을 없애준다.
e.preventDefault();
// 이벤트 객체의 요소로서 target이 tagName이며 대문자이다.**
let tagName = e.target.tagName;
if(tagName != 'LI' && tagName != 'A'){ // li가 아니면 return;(종료)
return; // tagName은 반환값이 대문자인 경우가 많다.
}
// JSON 데이터 사용하기 위해 우리는 데이터 수집을 해야 한다. 타겟이 없으면 부모노드로 올라가서 전체에서 찾는다!!
let elLi = (tagName === 'LI')? e.target: e.target.parentNode;
console.log(elLi.dataset.cid);
// e.target.parentNode를 이해하기 위해서는 PreviousSibling(노드 순회) 개념 필요!!
// Element(태그들이 객체화 모든 것들), Attr, Document 등등의 집중화된 것이 'Node' 객체이다.
// 우리는 url에서 데이터를 심어줘야 한다. 이것은 html에서 'dataset'으로 심어준다!
// 구글 'mdn dataset' 검색하고 'thymeleaf dataset' 검색 -> th:attr로 심는다.
let categoryId = elLi.dataset.cid;
// 자바스크립트 코드를 외부에서 이용하기 위해서 XMLHttpRequest 객체 이용!!
const request = new XMLHttpRequest();
// open은 브라우저에서 url을 입력하는 것과 같다.
// 메인 스레드는 잠궈버리면 안된다! 그래서 콜백함수를 사용한다.
// 콜백함수는 이벤트를 위임하는 것이다. 미리 설정해놓고 나중에 호출한다.
// 비동기 작업이 완료되면 호출한다.**
// 비동기를 처리해서 따로 빠져버린다!!
request.onload = function() {
// 이렇게 쓰면 JSON 객체에서 출력된다.
let menus = JSON.parse(request.responseText);
console.log(menus[0]); // 같은 메뉴 1개만 출력한다.
// menus.forEach(e => console.log(e)); // 이렇게 하면, 클릭 시, 그 카테고리 Id를 가진 모든 리스트 출력
}
// 이렇게 하면 JSON으로 심어진 데이터를 확인할 수 있다. // 백틱 중요하다!! (JSON 데이터를 꽂기 위해서!!)
request.open("GET", `http://localhost:8080/menus?c=${categoryId}`, true); // 동기 처리를 하면 문제가 많다. 그래서 비동기 처리를 하자!
request.send();
}
});
2) AJAX 정리 :
- 동기형 처리에서는 IO 작업이 대부분 이렇게 이루어지고 내부에서는 이미 매우 느려진다고 판단한다.
3) JS Node, Element :
a. Node
firstChild
,lastChild
:- 주석이나 공백도 포함하여 가리킨다.
parentNode
,parentElement
:- parentElement는 Element 객체에 있을 것 같지만 부모 객체인 Node에 있어서 이들은 DOM이 아니다.
b. Element
firstElementChild
,lastElementChild
:- firstChild와 다르게 Element용이다. 이것은 HTML Element에 있지 않고 Element 객체에서 가리킨다.
c. children
- Element 객체를 의미한다.
- Element 기능이 훨씬더 효과적이다.
- childrenNode는 Node 객체에서 쓰는 children이다.
d. previousSibling의 사용 이유
- 모든 Node를 대상으로 할건지, Element를 대상으로 할건지 판단가능!
- 그래서, previousSibling인지 previousElementSibling인지 상황에 따라 사용하자.
4) JS Node, Element 객체 공부 방법 :
- a.
mdn document
검색
- b.
mdn
의 검색 내용에서 아래의 ‘명세서’ 클릭
- c.
WHATWG
에서 스펙 확인
5) 최종 실습 코드 :
window.addEventListener("load", function() {
let ul = document.querySelector(".menu-category>ul");
const menuList = document.querySelector(".menu-list");
// 클릭 시 이벤트 요청 :
// 1) AJAX에서 addEventListener 하지마 : 여러 함수를 쓸 때 사용한다.(함수 누적 시, 사용한다.)**
// 2) AJAX에서 람다도 쓰지 : 람다는 지역화를 쓸 수가 없어서!!**
// 3) 위의 2개는 css의 연장선에서 사용가능
ul.onclick = function(e){
// a태그는 기본 행위가 있어서 그것을 없애준다.
e.preventDefault();
// 이벤트 객체의 요소로서 target이 tagName이며 대문자이다.**
let tagName = e.target.tagName;
// if(!(tagName == 'LI' || tagName == 'A'))
if(tagName != 'LI' && tagName != 'A'){ // li가 아니면 return;(종료)
return; // tagName은 반환값이 대문자인 경우가 많다.
}
// JSON 데이터 사용하기 위해 우리는 데이터 수집을 해야 한다. 타겟이 없으면 부모노드로 올라가서 전체에서 찾는다!!
let elLi = (tagName === 'LI')? e.target: e.target.parentNode;
console.log(elLi.dataset.cid);
// e.target.parentNode를 이해하기 위해서는 PreviousSibling(노드 순회) 개념 필요!!
// Element(태그들이 객체화 모든 것들), Attr, Document 등등의 집중화된 것이 'Node' 객체이다.
// 우리는 url에서 데이터를 심어줘야 한다. 이것은 html에서 'dataset'으로 심어준다!
// 구글 'mdn dataset' 검색하고 'thymeleaf dataset' 검색 -> th:attr로 심는다.
let categoryId = elLi.dataset.cid;
// 자바스크립트 코드를 외부에서 이용하기 위해서 XMLHttpRequest 객체 이용!!
const request = new XMLHttpRequest();
// open은 브라우저에서 url을 입력하는 것과 같다.
// 메인 스레드는 잠궈버리면 안된다! 그래서 콜백함수를 사용한다.
// 콜백함수는 이벤트를 위임하는 것이다. 미리 설정해놓고 나중에 호출한다.
// 비동기 작업이 완료되면 호출한다.**
// 비동기를 처리해서 따로 빠져버린다!!
request.onload = function() {
// 이렇게 쓰면 JSON 객체에서 출력된다.
let menus = JSON.parse(request.responseText);
// console.log(menus[0]); // 같은 메뉴 1개만 출력한다.
// menus.forEach(e => console.log(e)); // 이렇게 하면, 클릭 시, 그 카테고리 Id를 가진 모든 리스트 출력
// menuList.removeChild(menuList.firstElementChild);
// menuList.children[0].remove();
// a) while 문 이용해서 메뉴 전부 없애기!
// while(menuList.firstElementChild){
//
// menuList.firstElementChild.remove();
// }
// b) for-in 문 이용해서 메뉴 전부 없애기!
for(let i in menuList.firstElementChild){
console.log(i);
if(menuList.firstElementChild){
menuList.firstElementChild.remove();
}
}
}
// 백틱은 템플릿이 가능한 문자이다. 데이터를 여기에 꽂을 것이라서.. 그래서 백틱이 필요하다!!
// request.open("GET", `http://localhost:8080/menus?c=${el.dataset.cid}`, true); // 이렇게 하면 JSON으로 심어진 데이터를 확인할 수 있다.
request.open("GET", `http://localhost:8080/menus?c=${categoryId}`, true); // 동기 처리를 하면 문제가 많다. 그래서 비동기 처리를 하자!
request.send();
// console.log(request.responseText); // 아직은 콜백함수가 필요가 없다.(동기형이라서) 하지만, 비동기 처리를 하려면 콜백함수가 필요하다!
// ================== Fetch API ======================
/*
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch ("http://localhost:8080/menus/617", requestOptions)
.then (response => response.text () )
.then (result => console.log (result))
•catch (error => console.log('error', error));
//======================================================
//========= XHR (XmlHttpRequest) =======================
var xhr = new XMLHttpRequest () ;
xhr.withCredentials = true;
xhr.addEventListener ("readystatechange", function () {
if (this.readyState === 4) {
console.log (this.responseText);
}):
xhr.open("GET", "http://localhost:8080/menus/617");
xhr.send();
*/
}
});
2. AJAX-XHR 정리 : 230328
1) 멘토 피드백
- 개발에서 정답은 없다.
- 증권사에서는 유저 정보를 지워달라고 할 때, 레코드를 지울 때, 마지막 레코드부터 지워야해서.. VIEW 테이블을 사용하지 않는다. 그래서, 엔드유저에 따라 달라진다.
- 증권사는 경험이 없는 신입은 안 뽑는다.
- 데이터 타입이 굉장히 중요하다!
- float형은 요즘에는 잘 안쓰고 증권사에서 double을 사용한다. 경우의 수가 많기 때문이다.
- String은 클래스..
- 멘토 피드백 :
- 기획서 만들기(피그마 이용해서 스토리 보드 만들기!),
- 모든 스펙 문서화하기
- 아키텍처 명세 : 변수명과 메서드명등 정리
- ERD :
- API 명세 : URL 정리
- WBS
- 실무에서 물어볼 것 :
- 서버를 어떻게 운영하는지?
- 실무에서 실제 서버에서 웹서버는 1개만 이용하는지?
- 그때 쓰는 플랫폼은 뭐가 있는지? 게이트웨이는 어떻게 사용하는지? 무엇인지?
- 팀원들은 몇명이서 사용하는지? 업무는 어떻게 분업하는지? 실무에서 실력자들이 몇명이 분포되는지?
- 레이어는 각자가 맡아서 진행하는지? 다 만드는지?
- 한 프로젝트가 DB 1개만 쓰는지?
- 각 프로젝트마다 DB가 다 다른지? 1개씩만 쓰는지?
파이어 베이스
: 클라우드 데이터 베이스라서 DB가 없는 경우에 클라우드 DB를 사용한다. 앱 개발시, 사용한다.
0) AJAX 정리 :
a. 동기형 요청
- 동기형으로 요청하는 방법은 단순히 open 메소드에 마지막 인자를 false로 설정하면 된다.
request.open("GET", url, false);
- 그러면 데이터를 요청하는 send() 메소드 이후에 그 데이터가 도착하기 전까지 모든 흐름은 멈추고 결과가 올 때까지 기다리게 된다. 이런 동기형은 도착한 데이터를 사용하는 위치를 분명히 알 수 있어서 다음처럼 그 다음 줄에 도착한 결과를 사용하는 코드를 순차적으로 입력하면 된다.
request.open(..., false);
request.send(); // 데이터가 도착할 때까지 기다리는 함수 Blocking 함수
request.responseText 를 사용하는 코드
-
문제는 이런 방식은 순차적으로 실행되는 방식을 따르기 때문에 코드를 이해하는 난이도는 낮아지지만 네트워크 상태가 좋지 않거나 서버의 응답이 느린 경우는 …. 화면이 멈추는 일이 발생한다.
-
이런 방식의 문제를 해결하려면 요청 방식을 비동기로 처리해야 한다.
b. 비동기형 요청
- 비동기형으로 요청하는 방법은 앞에서 언급했던 것처럼 open 메소드에서 마지막 인자를 true로 설정하거나 아예 그 인자를 설정하지 않으면 기본값이 비동기처리가 된다.
request.open("GET", url, true); 또는 request.open("GET", url);
- 이렇게 비동기로 요청을 설정하게 되면 흐름은 순서와 달라지게 된다.
request.send() // 비동기로 요청했으므로 데이터가 도착할 때까지 흐름은 새로 만들어지고 메인 흐름은 막지 않고 바로 패스 시킨다.
request.responseText 를 사용하는 코드
- 따라서 responseText는 데이터를 받지 못한 상태에서 사용될 수 있기 때문에 이 부분에서 오류가 발생할 수 있다.
c. Callback 함수를 이용한 데이터 처리
-
앞서 언급했던 것처럼 비동기로 요청하게 되면 요청 이후의 데이터는 별도의 다른 흐름에서 처리되므로 그 별도의 흐름에서 내가 처리할 로직을 실행할 수 있도록 함수를 위임하는 방법을 사용한다.
-
위임된 함수는 바로 실행되는 것이 아니라 나중에 load가 완료되면 실행되는 또는 호출되는 함수라고 해서 붙여진 이름이 CallBack이다.
-
이 함수는 메인 흐름에서 호출되지 않으며 request가 가지는 별도의 흐름에서 로드가 완료되는 순간 실행되는 함수이기 때문에 이벤트 함수라고도 불리기도 한다.
-
따라서 이벤트는 대부분 비동기로 실행되며 그 때 위임되는 함수를 위임함수, 이벤트 함수, 콜백함수 등으로 다양하게 불린다.
1) AJAX : innerHTML 이용해서 메뉴 전부 없애기
- innerHTML 이용
// c) menuList.innerHTML 이용해서 메뉴 전부 없애기!
// // a. 값을 그냥 텍스트형태로 넘긴다!
// menuList.innerText = "<span style='color:blue'>test hello</span>";
//
// // b. HTML 태그 자체를 인코딩하여 넘겨버린다.
// menuList.innerHTML = "<span style='color:blue'>test hello</span>";
//
// // c. 다 지우기 위해서 가장 간단히 이것도 가능한다.
menuList.innerHTML = "";
2) AJAX : 목록 다시 채우기
- JS 템플릿 문자열 이용하기 : 백틱, ${}
- insertAdjacentHTML 이용(vs insertAdjacentElement)
// ** 문제 2 :
// ** 목록을 만들어서 다시 채우기!
// 방법 1 : DOM 객체를 직접 생성해서 채우기
// ** 엘리먼트는 본래의 다큐먼트의 기능을 이용하여 create 한다. **
// a. 엘리먼트는 다큐먼트의 create 기능을 이용하여 섹션 만들기!
// ** 노드 2개를 만들어서 직접 만드는 방법 : document 객체를 만들고 append해서 데이터만 바꿔준다.(AJAX)
// - 위에서 기존 메뉴를 지우고 새로 html을 다시 만드는 과정이다!!**
// let menuSection = document.createElement("section");
//
// // css의 class 이름인데 예약어라서 className을 사용.
// // 이렇게 class는 조금 다르게 처리해준다.
// menuSection.className = "menu";
//
// let form = document.createElement("form");
// form.className = "";
//
// //menuSection.appendChild(form); // Node interface의 기능
// menuSection.append(form); // Element interface의 기능
//
// menuList.append(menuSection);
// 방법 2 : 템플릿 문자열(``) 이용하기! -> 이게 정답!!
// ${menus[0].name}는 자바스크립트 ES6 템플릿 문자열 ``안의
// 데이터를 꽂을 수 있는 방법이다.
// let template = `<section class="menu">
// <form class="">
// <h1><span>${menus[0].name}</h1>
// <div class="menu-img-box">
// <a href="detail.html"><img class="menu-img" src="/image/product/12.png"></a>
// </div>
// <div class="menu-price">4500 원</div>
// <div class="menu-option-list">
// <span class="menu-option">
// <input class="menu-option-input" type="checkbox">
// <label>ICED</label>
// </span>
// <span class="menu-option ml-2">
// <input class="menu-option-input" type="checkbox">
// <label>Large</label>
// </span>
// </div>
// <div class="menu-button-list">
// <input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
// <input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
// </div>
// </form>
// </section>`;
//
// menuList.innerHTML = template;
// 방법 2-1 : 문제점 있는 템플릿 문자열(``) 이용 방법 : for of 이용!
// 이 방법은 문제점이 많다....
for(let m of menus) {
let template = `<section class="menu">
<form class="">
<h1><span>${m.name}</span>/<span style="font-size:11px;">${m.categoryName}<span></h1>
<div class="menu-img-box">
<a href="detail?id=${m.id}"><img class="menu-img" src="/image/product/12.png"></a>
</div>
<div class="menu-price">${m.price}</div>
<div class="menu-option-list">
<span class="menu-option">
<input class="menu-option-input" type="checkbox">
<label>ICED</label>
</span>
<span class="menu-option ml-2">
<input class="menu-option-input" type="checkbox">
<label>Large</label>
</span>
</div>
<div class="menu-button-list">
<input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
<input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
</div>
</form>
</section>`;
// menuList.innerHTML += template;
// '+='의 문자열을 누적하는 것이 오버헤드가 발생
// 누적할 때마다 새로 HTML 문서를 계속 새로 만든다.
// 부하가 제곱수 만큼 늘어나서 성능이 안 좋다!!
// 그래서 사용하는 방법이 insertAdjacentElement를 사용한다.
// ** insertAdjacentElement가 아니라 insertAdjacentHTML을 이용한다!
// ** 차이점 :
menuList.insertAdjacentHTML("beforeend",template);
// 그 위치에서 그 다음 위치에 새로 들어간다. 그래서 afterend 기능을 사용한다.
3) AJAX : elLi가 선택되면 버튼 색깔 바꿔주기
- className, classList의 toggle이나 add 이용
// 클릭 시 이벤트 요청 :
// 1) AJAX에서 addEventListener 하지마 : 여러 함수를 쓸 때 사용한다.(함수 누적 시, 사용한다.)**
// 2) AJAX에서 람다도 쓰지 : 람다는 지역화를 쓸 수가 없어서!!**
// 3) 위의 2개는 css의 연장선에서 사용가능
ul.onclick = function(e){
// a태그는 기본 행위가 있어서 그것을 없애준다.
e.preventDefault();
// 이벤트 객체의 요소로서 target이 tagName이며 대문자이다.**
let tagName = e.target.tagName;
// if(!(tagName == 'LI' || tagName == 'A'))
if(tagName != 'LI' && tagName != 'A'){ // li가 아니면 return;(종료)
return; // tagName은 반환값이 대문자인 경우가 많다.
}
// JSON 데이터 사용하기 위해 우리는 데이터 수집을 해야 한다. 타겟이 없으면 부모노드로 올라가서 전체에서 찾는다!!
let elLi = (tagName === 'LI')? e.target: e.target.parentNode;
// ** 문제 3 : elLi가 선택되면 버튼 색깔 바꿔주기!!
// ** className을 마구잡이로 사용하면 안 된다! 같은 className을 쓰는 모든 태그에 스타일이 다 겹치기 때문이다.
// elLi.className = "menu-selected";
// 그래서, classList의 toggle을 이용하거나 add를 이용한다.
// elLi.classList.toggle("menu-selected");
// elLi.classList.add("menu-selected");
// 1) 기존의 것을 찾기 위해서 반복문을 쓰지 말고 if문으로 해결!
let curLi = ul.querySelector("li.menu-selected");
// 이건 내가 한 방식
// if(curLi === elLi){
// elLi.classList.add("menu-selected");
// }
// else if(curLi !== elLi){
// elLi.classList.add("menu-selected");
// curLi.classList.remove("menu-selected");
// }
// ** 2) 다를 때만 비교하면 된다! 같을 때는 비교 안해도 된다!!
if(curLi !== elLi){
elLi.classList.add("menu-selected");
curLi.classList.remove("menu-selected");
}
// ** 3) 같으면 이벤트를 끝내버리고 다를 때만 데이터를 요청하면 된다!(이게 정답!!)
if(curLi === elLi)
return;
elLi.classList.add("menu-selected");
curLi.classList.remove("menu-selected");
}
4) AJAX : 검색 기능 만들기
-
검색 기능 만들기
-
이렇게 만들면 url이 깔끔해진다.
-
쿼리스트링은 페이지의 상태값을 말한다.
window.addEventListener("load", function() {
let ul = document.querySelector(".menu-category>ul");
const menuList = document.querySelector(".menu-list");
// ** 문제 4 : 검색 기능 만들기
// ** 항상 버튼을 1개 만들면 테스트 해보기!
const form = document.querySelector('.search-header form');
const findButton = document.querySelector('.icon-find');
// console.log(findButton);
// **검색 기능 : 돋보기 누르면 검색 기능이 구현!**
// 카테고리 별 검색도 가능하다..?
findButton.onclick = function(e){
// 본래 HTML 태그의 기능(default 행위)이 있어서 기본 행위를 막아내는 함수이다.**
// 이것은 이미지의 기본 기능으로 드래그 기능이 있어서 기능 중 드래그 드랍 기능을 사용할 때도 기본 기능을 막아 줘야 한다.
e.preventDefault();
// input 태그의 값은 여기서 뽑아낸다.**
const queryInput = form.querySelector("input[name=q]");
let query = queryInput.value;
console.log(queryInput.value);
// 이제는 전체 검색인지, 카테고리 검색인지 구별해서 검색 구현을 해야 한다.**
// ** 이 부분은 계속 반복적이라서 추후에 함수로 따로 만들어서 사용하자!
// const request = new XMLHttpRequest();
//
// request.onload = function() {
// let menus = JSON.parse(request.responseText);
// }
//
// request.open("GET", `http://localhost:8080/menus?c=${categoryId}`, true); // 동기 처리를 하면 문제가 많다. 그래서 비동기 처리를 하자!
// request.send();
const request = new XMLHttpRequest();
request.onload = function() {
let menus = JSON.parse(request.responseText);
console.log(menus);
bind(menus);
}
request.open("GET", `http://localhost:8080/menus?q=${query}`, true); // 동기 처리를 하면 문제가 많다. 그래서 비동기 처리를 하자!
request.send();
}
// binding되면서 request까지 기능이되어서 load라고 하자!
function bind(menus){
menuList.innerHTML = "";
// 방법 2-1 : 문제점 있는 템플릿 문자열(``) 이용 방법 : for of 이용!
// 이 방법은 문제점이 많다....
for(let m of menus) {
let template = `<section class="menu">
<form class="">
<h1><span>${m.name}</span>/<span style="font-size:11px;">${m.categoryName}<span></h1>
<div class="menu-img-box">
<a href="detail?id=${m.id}"><img class="menu-img" src="/image/product/12.png"></a>
</div>
<div class="menu-price">${m.price}</div>
<div class="menu-option-list">
<span class="menu-option">
<input class="menu-option-input" type="checkbox">
<label>ICED</label>
</span>
<span class="menu-option ml-2">
<input class="menu-option-input" type="checkbox">
<label>Large</label>
</span>
</div>
<div class="menu-button-list">
<input class="btn btn-fill btn-size-1 btn-size-1-lg" type="submit" value="담기">
<input class="btn btn-line btn-size-1 btn-size-1-lg ml-1" type="submit" value="주문하기">
</div>
</form>
</section>`;
// menuList.innerHTML += template;
// '+='의 문자열을 누적하는 것이 오버헤드가 발생
// 누적할 때마다 새로 HTML 문서를 계속 새로 만든다.
// 부하가 제곱수 만큼 늘어나서 성능이 안 좋다!!
// 그래서 사용하는 방법이 insertAdjacentElement를 사용한다.
// ** insertAdjacentElement가 아니라 insertAdjacentHTML을 이용한다!
// ** 차이점 :
menuList.insertAdjacentHTML("beforeend",template);
// 그 위치에서 그 다음 위치에 새로 들어간다. 그래서 afterend 기능을 사용한다.
}
}
5) DOM, JQuery 흐름
- DOM은 브라우저 사이에 움직이는 객체들이다. 사용자 입력(입출력)을 DOM을 통해 입력받거나 수반한다. Javascript가 실제로 움직이게 해주는 것이며 기반이 되는 것이다.
- JQuery는 DOM을 대신해주는 친구이다. DOM을 안쓰려고 JQuery를 사용한다. 하지만 이것은 IE를 쓰려고 했었는데 IE가 망해버림..
- DDD 공법으로 깔끔하게 만드는 것이 아니라 DOM을 대신해서 사용했던 것이다. 하지만 IE가 망해버림.(MS의 브라우저가 사라짐.)
- 그 다음에 등장한 것이 MVC 프레임워크인데 DOM을 대신해주는 MVC 프레임워크를 써라!! 그것은 Angular나 React나 Vue.js를 이용한다.
- DOM을 이용하는 방법 :
- 노드를 얻기 위해서 querySelector를 쓸 수 있는지
- 노드 수정, 삭제, 등록을 할 수 있는지
- 이벤트를 다뤄 봤는지? 드래그 앤 드랍 이벤트, 필 이벤트, 페이지로드 이벤트, 키보드 이벤트
- 이제는 DOM의 기능을 쓰면 안 된다. 프레임워크를 쓰는 것이 맞다.
6) Vue.js 시작!**
-
DOM을 직접 사용하기 보다는 MVC 프레임워크를 이용하자!
-
DOM 개발에서 라이브러리가 대신 해준다.
-
DOM 입/출력에 관한 코드가 가장 길기 때문이다.
-
MVW 방식이다. DOM 프로그래밍과 차이
3. Vue.js 기본 개념 : 230329
-
Vue.js MVC가 진짜다. 백엔드 MVC는 단방향
-
현장에서 모르겠으면 Vue.js를 사용해라!!
1) Vue.js 사용하는 방법 :
- 먼저 ,html에 아래의 script 태그를 적어주면 된다. 그리고 .js 파일에서 스크립트 코드를 적어주자
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
- 보통은 global vue.js를 사용하고 여러 페이지를 만들면, layout에서 위의 태그를 쓰거나 한 페이지만 만들면, 그 페이지에서만 사용한다!
2) Vue.js 기초 실습
- 계산기에서 적용
a. 실습 코드 :
- calc.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="./calc2.js" defer="defer"></script>
<body>
<section id="calc">
<h1>덧셈 계산기</h1>
<form>
<fieldset>
<!-- 는 innerTxt에서 사용한다. -->
<legend>계산기 입력폼</legend>
<div>
<label>x:</label>
<!-- v-bind:value 이렇게 하는 것은 원웨이 방식인데 문제가 많다. -->
<!-- :value="x" 이렇게도 사용 가능하다! -->
<!-- 이전까지는 원웨이 방식인데 이제 사용자 입력도 가능한 투웨이 방식인 v-model을 이용하자! -->
<!-- @input은 oninput과 같아서 값이 변경되면 다 바뀐다. -->
<input dir="rtl" name="x" v-model.trim.number="x" @input="this.z-this.x;">
<label>y:</label>
<input dir="rtl" name="y" v-model.number="y">
<span>=</span>
<!-- v-text의 오버라이드 에러가 발생해서 기본 텍스트 지우기! -->
<span v-text="z"></span>
</div>
<hr>
<div> <!-- v-on:click.prevent=""과 @.click.prevent=""가 가능하다 -->
<!-- @click.prevent.once는 1번만 사용된다! 이렇게하면 사용하고 나서 prevent는 다 사라진다! -->
<input type="submit" value="초기화" @click.prevent="resetHandler">
<input type="submit" value="계산하기" @click.prevent="calcHandler">
</div>
</fieldset>
</form>
</section>
</html>
</body>
- DOM만 사용한 원래 코드 :
window.addEventListener("load", function () {
const inputs = document.querySelectorAll("input");
const xInput = inputs[0];
const yInput = inputs[1];
const resetButton = inputs[2];
const submitButton = inputs[3];
const resultSpan = document.querySelector("form span:last-child");
resetButton.onclick = (e) => {
e.preventDefault();
xInput.value = 0;
yInput.value = 0;
console.log(e);
};
// 1way binding
submitButton.onclick = (e) => {
e.preventDefault();
let x = parseInt(xInput.value);
let y = parseInt(yInput.value);
let result = x + y;
resultSpan.innerText = result;
console.log(e);
};
// 2way binding
xInput.oninput = (e) => {
let x = parseInt(xInput.value);
let y = parseInt(yInput.value);
let result = x + y;
resultSpan.innerText = result;
};
yInput.oninput = (e) => {
let x = parseInt(xInput.value);
let y = parseInt(yInput.value);
let result = x + y;
resultSpan.innerText = result;
};
});
- Vue.js를 사용한 JS 코드 :
Vue
.createApp({
data(){ // 데이터를 반환하는 것이 모델을 반환한다.
// return { // 이렇게 쓰는 것이 JSON 형태이다.
// x:20,
// y:30
// };
let x = 0;
let y = 0;
let z = 0;
// html에서 v-text="z"를 쓰기 위해서는 model이 필요하다!
return {x,y,z}; // {}로 이렇게 쓰면, 모델(Vue의 모델)이다. 이것은 1way방식이다.
// 2way 방식으로 고치자!! 입력 컨트롤에서 사용된다.
// 이제 이벤트 함수를 사용하기!!
},
// 객체 안에 함수를 정의한다.
methods: {
calcHandler(e){
// model을 return만 하면 바로 꽂힌다.
// let z = parsetInt(this.x) + parseInt(this.y);
// v-text="z"를 쓰기 위해서는 model이 필요하다!
// 모델이 input, select, textarea에 사용된다!!
// JS의 parseInt말고도 html에서 vue.js의 number도 가능하다!
// this.z = parseInt(this.x)+parseInt(this.y);
this.z = this.x+this.y;
// console.log(z);
// console.log("hello");
},
resetHandler(e){
this.z = 0;
// console.log("reset");
}
}
})
.mount("#calc"); // mount는 vue 영역을 의미한다. 갹체나 함수를 담는 그릇을 의미한다.
// css의 id를 참조할 수 있다.
b. 결론 :
- Vue.js는 DOM을 대신해서 사용하기에 코드량이 적어지고 성능도 좋기 때문에 대신해서 사용할만 하다!
- Vue 이용하기
3) Vue.js 응용 실습
-
Vue를 이용하기 위한 script 태그는 html 태그와 헤드 태그가 있는 곳에서 사용한다.**
-
rland에서 적용하는 실습이다.
-
Fetch API를 사용하기 위해서 JS ES6의 Promise 개념이 필요하다.
-
추후에는 콜백함수가 없어진 비동기 처리 방식을 이용할 예정이다.(Fetch API, 프로미스 이용)
a. 테스트 코드 :
Vue
.createApp({
data(){
return{
test:"hello"
};
}
})
.mount("#main-section");
<!DOCTYPE html>
<html
xmlns=th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="inc/layout"
>
<main layout:fragment="adminmain" class="main-padding-none">
<!-- 문서 안에 포함되어야 해서 스크립트 위치 주의! 그리고 defer 설정 중요!!-->
<script src="/js/admin/menu/list.js" defer="defer"></script>
<section id="main-section">
<header class="search-header">
<h1 class="text-title1-h1">알랜드 메뉴<span v-text="test"></span></h1>
<form>
<input type="text">
<input type="submit" class="icon icon-find">
</form>
</header>
4. Vue MVC, ES6 Promise 개념 : 230330
1) Vue.js 3.x 버전
-
Vue.js 3.x 버전에서는 Vue의 options-API로 따로 뺐다.
-
Vue.js 2.x 버전과 비교해서 보면 options-API를 mount로 따로 뺐다.
2) Vue MVC 구성
data()
는 모델,methods:
는 컨트롤러,mount()
는 view 부분을 역할로 갖고 있다.
data
는 model이라서 함수()로 구성한다.methods
는 controller라서 여러 개의 컨트롤러가 들어갈 수 있어서 객체로 사용하여 여러 개의 함수를 포함한다.mount
는 View 부분이라서 CSS Selector값으로 받아서 처리한다.
- 이벤트 함수 실습 :
@click.prevent="categoryClickHandler"
에서@
는지시자
이며v-on:
을 의미한다.
<nav class="menu-category" >
<header class="d-flex">
<h1 class="text-normal-bold">메뉴분류</h1>
<div>
<a class="btn btn-mini bg-blue" href="">분류추가</a>
</div>
</header>
<ul>
<li class="menu-selected">
<a href="/menu/list">전체</a>
</li>
<li>
<a href="">커피음료</a>
</li>
<li data-id="2" @click.prevent="categoryClickHandler">
<a href="">수제청</a>
</li>
<li>
<a href="">샌드위치</a>
</li>
<li>
<a href="">쿠키</a>
</li>
</ul>
</nav>
Vue
.createApp({
data(){
return{
test:"hello"
};
},
methods:{
categoryClickHandler(e){
//console.log("clicked");
this.load(e.target.dataset.id);
},
load(cid){
// XHR(XmlHttpRequest)
// - Callback(나중에 호출한다 = delegation 함수 = 나중에 호출하므로 위임함수)을
// 이용한 비동기처리
// Fetch API
// - Promise를 이용한 비동기 처리
console.log(cid); // '2'가 출력!!
}
}
})
.mount("#main-section");
3) ES6 Promise 개념
- Callback 함수의 문제점 :
- 콜백함수의 중복 현상이 꼬리에 꼬리를 물어버린다.
- 결론 : 그래서, blocking이 가능한 것이 필요하다. 그래서 ES6 Promise 개념을 이용하여 처리하자!
- 초기 콜백함수 코드 :
- setTimeout 함수가 JS에서 비동기 처리를 해준다! 위임 함수이기 때문이다.
Vue
.createApp({
data(){
return{
test:"hello"
};
},
methods:{
categoryClickHandler(e){
//console.log("clicked");
this.load(e.target.dataset.id,function(){
console.log("데이터가 도착했니?");
});
console.log("click");
},
async load(cid, callback){
// XHR(XmlHttpRequest)
// - Callback(나중에 호출한다 = delegation 함수 = 나중에 호출하므로 위임함수)을
// 이용한 비동기처리
// Fetch API
// - Promise를 이용한 비동기 처리
// setTimeout을 사용하면 callback 함수를 사용하는
// request 객체의 send(); 이다.
// 따라서, 나중에 load가 실행된다.
setTimeout(()=>{
console.log("load");
callback();
},3000);
console.log("load");
}
}
})
.mount("#main-section");
- Promise
- 나름 동기를 쓰는 비동기해주는 콜백함수 코드
- await를 사용하기 위해서는 Promise를 반환해주는 함수를 사용해야 await를 사용할 수 있다.
Vue
.createApp({
data(){
return{
test:"hello"
};
},
methods:{
categoryClickHandler(e){
//console.log("clicked");
// this.load(e.target.dataset.id,function(){
// console.log("데이터가 도착했니?");
// });
console.log("click");
await this.load(e.target.dataset.id);
console.log("데이터가 도착한 후에 할일");
},
async load(cid){
// XHR(XmlHttpRequest)
// - Callback(나중에 호출한다 = delegation 함수 = 나중에 호출하므로 '위임함수'라고도 부름.)을
// 이용한 비동기처리
// Fetch API
// - Promise를 이용한 비동기 처리
// setTimeout을 사용하면 callback 함수를 사용하는
// request 객체의 send(); 이다.
// 따라서, 나중에 load가 실행된다.
// setTimeout(()=>{
// console.log("load");
// callback();
// },3000);
return new Promise(resolve=>{
setTimeout(()=>{
console.log("load");
callback();
},3000);
});
console.log("load");
}
}
})
.mount("#main-section");
- VScode에서 Promise 실습 코드
- 비동기를 쓰면 문제가 많다. 실행 순서대로 동작하지 않아서.. 그래서 Promise와 await를 사용한다.
// await를 쓰면, async 함수로 감싸주거나 async 함수로 만들어야 한다.
(async ()=>{
console.log("click");
let data = await load(2); // 비동기를 가지는 함수!!**
console.log("도착한 데이터 : " + data.name);
})();
function load(cid){
// await를 쓰면 Promise로 반환받도록 한다.
// Promise로 감싸주면, resolve 함수가 실행된다.
// Promise로는 무조건 반환을 의미하는 함수로 반환해줘야 한다.
return new Promise(resolve=>{
setTimeout(()=>{
console.log("데이터가 도착하였습니다.");
// resolve 함수를 실행하지만 곧, 이것이 return을 의미한다.
// 이게 없으면 await가 계속 기다려서 무한 루프로 돌아간다.
resolve({name:"hello"});
}, 2000);
})
}
4) Fetch API 개념 정리** :
a. fetch 중요 개념!** :
- 5)의 코드처럼, fetch 함수는 API 요청과 함께 Promise 객체를 반환한다. 여기서 fetch 함수 앞에 await가 안 붙으면, response는 단순히 Promise 객체를 반환해준다. 그래서 해당 fetch 함수에 await를 붙인다면, await가 Promise를 만나서 그 요청 API에 대한 결과를 기다리기 시작한다. 결국, Promise가 기다리는 것이 된다. 즉, Promise 개념에서 resolve를 기다리는 것처럼 기다리다가 요청에 대한 데이터가 오면, 그 다음 흐름인 코드를 실행해준다.(나름 동기형인 비동기 처리 방법이다.)**
- fetch 함수는 XHR의 콜백함수의 open()과 send() 함수의 작동 방식을 동시에 한 번에 처리한다.**
- fetch() 함수와 그 다음에 등장하는 json() 함수는 Promise 객체를 반환한다.**
b. await 개념** :
- await는 비동기로만 쓰지 않게 하고 코드의 흐름대로 쓰게 한다.(동기형..)
- await를 만나면 흐름을 Promise가 잡아서 흐름이 진행되지 않도록 해준다.
- 이렇게 진행되면, 그 다음 결과가 더 진행되지 못하도록 막고 await를 만난 fetch의 API 요청에 대한 결과가 도착하면 fetch 이후의 그 다음 코드를 실행해준다.**
c. Promise 동작 과정** :
- Promise를 넘겨받으면 나중에 데이터가 도착한다.
- Promise를 반환받으면 동기화가 가능하다. fetch는 비동기로 처리하는 함수이다. 데이터를 가져오는 함수가 따로 빠져 있다.
- fetch는 Promise를 반환하는데, Promise가 await를 만나면, 비동기로 빠지지 않고 흐름을 멈추게 한다. 그래서 동기형으로 바꿔준다.
- 비동기 함수는 데이터가 도착할 때, 실행하는 함수가 있는데 그것이 XHR이 있고 Promise를 반환하는 함수인 fetch가 있다.
- Promise인 이것은 도착할 때까지 기다려주는 함수이다.
d. JS 비동기 처리 정리 :
- 자바스크립트는 비동기처리하는 방법이 없었다. fetch 함수가 그 기능을 해주기 때문에 비동기 처리를 해주는 것이다.
- XHR에서는 send 함수가 비동기 처리 함수였다. 원래 자바스크립트에서는 이러한 기능이 없었다.
- 언제 처리가 끝나는 지 모르기 때문이다.
e. 결론** :
- fetch를 반환하는 Promise는 JS의 비동기 처리 방법인데 await가 그 코드 흐름을 중간에 잡아서 가져가므로 await를 이용하면 동기형으로 처리하는 결과를 초래한다. **
- 자바스크립트 코드를 외부(스프링 부트의 model, thymeleaf, Vue.js 등등)에서 이용(접근)하기 위해서 JS에서 비동기 처리를 하는 것이다!**
f. 추가** :
-
Promise 함수는 비동기 처리가 되어 있어 전체 코드의 흐름이 완료할 때까지 멈춘다. 나중에 마지막에 실행된다. 먼저, 서버에서 데이터를 불러오고 화면을 그 다음에 구성하기 때문에 비동기 처리를 해주어야 한다.
-
then 로직 대신 await-fetch 로직은 await를 걸고 resolve라는 변수를 만들어 담는다는 점이 다르다.
-
then을 이용하는 처리방식과 비교해보면, load()라는 함수가 불리는 위치에 따라 동기화가 필요한 부분은 외부 함수와도 동기화를 할 수 있다는 장점이 있다.
-
load() 함수는 resolve가 생길때까지 다음 로직이 실행되지 않는다.
-
또한, 이 경우는 try-catch로 에러를 잡아줘야 한다.
5) Fetch API 적용 코드
a. 초기 코드 :
Vue
.createApp({
data(){
return{
test:"hello"
};
},
methods:{
async categoryClickHandler(e){
//console.log("clicked");
// this.load(e.target.dataset.id,function(){
// console.log("데이터가 도착했니?");
// });
// console.log("click");
// await this.load(e.target.dataset.id); // 기다릴테니까 난 기다렸다가 할거야... -> 나름 동기형으로 처리
//
// console.log("데이터가 도착한 후에 할일");
// 비동기가 빠지면서 Promise로 받는다.
// 그래서, 받는 것도 await 처리를 해줘야 한다.
let response = await fetch("/menus"); // fetch가 Promise를 대신 해준다! 되게 간단해졌다!!
let list = await response.json(); // 동기형인 비동기형이다. 화면은 안 바뀌고 데이터만 바뀌므로!!
console.log(list);
},
load(cid){
// XHR(XmlHttpRequest)
// - Callback(나중에 호출한다 = delegation 함수 = 나중에 호출하므로 '위임함수'라고도 부름.)을
// 이용한 비동기처리
// Fetch API
// - Promise를 이용한 비동기 처리
// setTimeout을 사용하면 callback 함수를 사용하는
// request 객체의 send(); 이다.
// 따라서, 나중에 load가 실행된다.
// setTimeout(()=>{
// console.log("load");
// callback();
// },3000);
return new Promise(resolve=>{
setTimeout(()=>{
console.log("load");
callback();
},3000);
});
console.log("load");
}
}
})
.mount("#main-section");
b. fetch-await 코드 :
- 이렇게 많이 사용한다!**
- 이게 제일 간편하고 코드가 짧다.
Vue
.createApp({
data(){
return{
test:"hello"
};
},
methods:{
categoryClickHandler(e){
this.load(2);
},
async load(cid){
// XHR(XmlHttpRequest)
// - Callback(나중에 호출한다 = delegation 함수 = 나중에 호출하므로 '위임함수'라고도 부름.)을
// 이용한 비동기처리
// Fetch API
// - Promise를 이용한 비동기 처리
// 비동기가 빠지면서 Promise로 받는다.
// 그래서, 받는 것도 await 처리를 해줘야 한다.
let response = await fetch("/menus"); // fetch가 Promise를 대신 해준다! 되게 간단해졌다!!
let list = await response.json(); // 동기형인 비동기형이다. 화면은 안 바뀌고 데이터만 바뀌므로!!
console.log(list);
// 에러 : Promise pending을 반환하면 함수 앞에 await를 써주자!(기다리는 곳이 필요하기 때문이다.)**
}
}
})
.mount("#main-section");
c. fetch-then : then 내부에 중첩
Vue
.createApp({
data(){
return{
test:"hello"
};
},
methods:{
categoryClickHandler(e){
this.load(2);
},
async load(cid){
// ========================================================================================
// XHR(XmlHttpRequest)
// - Callback(나중에 호출한다 = delegation 함수 = 나중에 호출하므로 '위임함수'라고도 부름.)을
// 이용한 비동기처리
// Fetch API
// - Promise를 이용한 비동기 처리
// ========================================================================================
// 비동기가 빠지면서 Promise로 받는다.
// 그래서, 받는 것도 await 처리를 해줘야 한다.
// fetch, json도 반환을 Promise로 반환한다.**
// fetch는 XHR의 콜백함수의 open과 send를 동시에 작동 하는 것을 한 번에 처리한다.
// let response = await fetch("/menus"); // fetch가 Promise를 대신 해준다! 되게 간단해졌다!!
// let list = await response.json(); // 동기형인 비동기형이다. 화면은 안 바뀌고 데이터만 바뀌므로!!
// console.log(list);
// 에러 : Promise pending을 반환하면 함수 앞에 await를 써주자!(기다리는 곳이 필요하기 때문이다.)**
// ========================================================================================
// ========================================================================================
// ** 중요!!
// 콜백을 사용하는 fetch : 나중에 실행하는 코드 내가 갖고 있을게!
// 콜백함수를 같이 쓰는 이것은 진짜 불편하다!
// let promise = fetch("/menus", function(response){console.log("도착했냐?");});
let promise = fetch("/menus");
// 성공했으면 실행해주는 함수이다.
// 여기서 response는 위의 함수처럼 매개변수이다.
// 즉, response는 Promise 갹체가 반환되는 인자이다. Promise 개념에서 결과를 반환해주는 역할인 resolve를 의미한다.
promise
.then(response => {
// console.log("도착 했냐?");
// console.log(response.json());
let promise = response.json();
promise.then(list=>{
console.log(list);
});
}); // 애로우 펑션!
//.then(function(){console.log("도착 했냐?");});
// ========================================================================================
// *** 이게 제일 편해서 아래의 코드처럼 사용한다!! ***
// let response = await fetch("/menus"); // fetch가 Promise를 대신 해준다! 되게 간단해졌다!!
// let list = await response.json(); // 동기형인 비동기형이다. 화면은 안 바뀌고 데이터만 바뀌므로!!
// console.log(list);
// ========================================================================================
// ========================================================================================
}
}
})
.mount("#main-section");
d. fetch-then 병렬 중첩
Vue
.createApp({
data(){
return{
test:"hello"
};
},
methods:{
categoryClickHandler(e){
this.load(2);
},
async load(cid){
// ========================================================================================
// ****** 바로 위의 코드의 중첩보다는 중첩을 병렬로 가져갈 수 있다. *****
let promise = fetch("/menus");
// 성공했으면 실행해주는 함수이다.
// 여기서 response는 위의 함수처럼 매개변수이다.
// 즉, response는 Promise 갹체가 반환되는 인자이다. Promise 개념에서 결과를 반환해주는 역할인 resolve를 의미한다.
promise
.then(response=>{
// console.log("도착 했냐?");
// console.log(response.json());
return response.json(); // 반환을 하면 아래 코드처럼 계속 이어가서 또 반환 할 수 있다.
})
.then(list=>{
console.log(list);
// this.list = list; // 이렇게 담아서 사용한다!
});
// ========================================================================================
// *** 이게 제일 편하고 간단해서 아래의 코드처럼 사용한다!! ***
// let response = await fetch("/menus"); // fetch가 Promise를 대신 해준다! 되게 간단해졌다!!
// let list = await response.json(); // 동기형인 비동기형이다. 화면은 안 바뀌고 데이터만 바뀌므로!!
// console.log(list);
// ========================================================================================
// ========================================================================================
}
}
})
.mount("#main-section");
e. fetch-then 병렬 중첩 응용
- 미들웨어 시스템!!이라고도 부른다.
- 보통 then을 쓰면 이렇게 사용하는데 그 이유는 List 타입이라면 중첩의 중첩을 이용하여 변수 값을 뽑아 낼 수 있기 때문이다.
- 병렬 then 사용 중 return으로 반환하면 아래 코드처럼 계속 이어가서 또 데이터를 다시 반환할 수 있다. then이 Promise로 쌓여있는 데이터에서 데이터를 뽑아낼 수 있게 해준다.
Vue
.createApp({
data(){
return{
test:"hello"
};
},
methods:{
categoryClickHandler(e){
this.load(2);
},
async load(cid){
// ========================================================================================
// XHR(XmlHttpRequest)
// - Callback(나중에 호출한다 = delegation 함수 = 나중에 호출하므로 '위임함수'라고도 부름.)을
// 이용한 비동기처리
// Fetch API
// - Promise를 이용한 비동기 처리
// ========================================================================================
// ****** '.'을 써서 바로 위의 코드의 중첩의 중첩으로 미들웨어 시스템으로 가져갈 수 있다. *****
let promise = fetch("/menus");
promise
.then(response=>{
// console.log("도착 했냐?");
// console.log(response.json());
return response.json(); // 반환을 하면 아래 코드처럼 계속 이어가서 또 반환 할 수 있다.
})
.then(list=>{
return list[1];
// this.list = list; // 이렇게 담아서 사용한다!
})
.then(menu=>{
console.log(menu.name);
});
// ========================================================================================
// *** 이게 제일 편하고 간단해서 아래의 코드처럼 사용한다!! ***
// let response = await fetch("/menus"); // fetch가 Promise를 대신 해준다! 되게 간단해졌다!!
// let list = await response.json(); // 동기형인 비동기형이다. 화면은 안 바뀌고 데이터만 바뀌므로!!
// console.log(list);
// ========================================================================================
// ========================================================================================
}
}
})
.mount("#main-section");
f. catch-then 사용 방법 :
- API 요청 URL이 이상하면 에러가 발생한다.
- 중간에 URL에 요청한 데이터가 반환 값이 이상해서 에러가 발생했을 때도 catch로 점프해서 에러 메세지를 반환한다.!!
Vue
.createApp({
data(){
return{
test:"hello"
};
},
methods:{
categoryClickHandler(e){
this.load(2);
},
async load(cid){
// ========================================================================================
// XHR(XmlHttpRequest)
// - Callback(나중에 호출한다 = delegation 함수 = 나중에 호출하므로 '위임함수'라고도 부름.)을
// 이용한 비동기처리
// Fetch API
// - Promise를 이용한 비동기 처리
// ========================================================================================
// ****** '.'을 써서 바로 위의 코드의 중첩의 중첩으로 미들웨어 시스템으로 가져갈 수 있다. *****
let promise = fetch("/menus");
promise
.then(response=>{
// console.log("도착 했냐?");
// console.log(response.json());
return response.json(); // 반환을 하면 아래 코드처럼 계속 이어가서 또 반환 할 수 있다.
})
.then(list=>{
return list[1];
// this.list = list; // 이렇게 담아서 사용한다!
})
.then(menu=>{
console.log(menu.name);
// this.list = list; // 이렇게 담아서 사용한다!
})
.catch(error=>{
console.log("error"); // 중간에 url의 요청한 데이터가 반환 에러가 발생하면 점프해서 에러 반환!!
}); // then은 이전의 내용을 중첩해서 사용한다.
// ========================================================================================
// *** 이게 제일 편하고 간단해서 아래의 코드처럼 사용한다!! ***
// let response = await fetch("/menus"); // fetch가 Promise를 대신 해준다! 되게 간단해졌다!!
// let list = await response.json(); // 동기형인 비동기형이다. 화면은 안 바뀌고 데이터만 바뀌므로!!
// console.log(list);
// ========================================================================================
}
}
})
.mount("#main-section");
5. fetch API - AJAX 최종 정리 : 230331
1) Vue.js에서 AJAX 사용
- Vue MVC에서 가장 중요하게 생각하는 것이 바꾸고자하는 것을 어떤 변수와 연동하게 할 것인가?만 생각하면 된다.(Vue의 model 입장!)
- 어떤 지시자를 사용할 것인가? Vue Built-ins의 Directives와 같은 지시자 검색하기!
- 정리 : Vue.js에서 자주 바뀌는 model만 신경쓰면 된다.**
a. test 1 : vue.js랑 thymeleaf를 같이 쓰는 경우
- vue.js랑 thymeleaf를 같이 쓰면, 동시에 동작해서 vue.js의 model 수만큼 배수로 동작한다.
- 타임리프는 서버에서 처리하기 때문에 서버에서 렌더링하여 문서를 만들어 오면, 타임리프 템플릿 처리가 완료되고 Vue 템플릿만 남기 때문에 Vue의 for 반복문에 의해 Vue의 model 수만큼 반복해서 배수만큼 더 동작한다.
- Vue의 model은 문자열로서 사용해도 되지만, JSON 타입으로도 사용할 수 있다.
- 결국, 한 번만 템플릿이 동작하게 하게 하는 방법은 thymeleaf는 모두 지우고 Vue.js만 이용하면 된다!
- 왜냐하면, 위의 설명처럼, thymeleaf는 서버에서, Vue.js는 브라우저(클라이언트)에서 화면을 구성하기 때문이다.
a) 실습코드 :
<section v-for="menu in list" th:each="menu : ${list}" class="menu border-bottom border-color-1">
<form class="">
<h1 th:text="${menu.name}" v-text="menu.name"></h1>
<div class="menu-img-box">
<!-- 절대 경로가 아니라 상대 경로라서 주의!! -->
<a th:href="@{detail(id=${menu.id})}" href="detail?id=617"><img class="menu-img" src="/image/product/12.png"></a>
</div>
<div class="menu-price" th:text="${menu.price} + 원">4500 원</div>
<div class="menu-option-list">
<span class="menu-option">
<input class="menu-option-input" type="checkbox">
<label>ICED</label>
</span>
<span class="menu-option ml-2">
<input class="menu-option-input" type="checkbox">
<label>Large</label>
</span>
</div>
<div class="menu-button-list">
<input class="btn btn-line btn-round btn-size-1 rounded-0-md" type="submit" value="수정">
<input class="btn btn-fill btn-round rounded-0-md btn-size-1 ml-1" type="submit" value="삭제">
</div>
</form>
</section>
Vue
.createApp({
data(){
return{
test:"hello",
list:[
{id:1, name:"아메리카노", price:5000},
{id:2, name:"카페라떼", price:5500},
{id:3, name:"카페모카", price:6000}
]
};
},
b. 정리 : Vue.js랑 Thymeleaf를 같이 쓰는 경우 우선 순위?
- 중요** :
- Thymeleaf와 Vue.js를 동시에 쓰면 다음과 같은 상황이 발생한다. 먼저, 서버에서 Thymeleaf에 의해 데이터를 바인딩해서 화면을 만들어왔지만 다시 Vue.js에 의해서 클라이언트가 다시 재작업하여 화면을 구성한다. 이때, Vue.js에 model이 없으면 화면 구성상 목록이 전부 사라진다.
- Vue.js랑 Thymeleaf를 동시에 같이 템플릿으로 사용하면, Vue.js에 의해서 목록을 만들어내는 수량은 Vue의 model 개수에 의해서 정해진다.
- 결론 : Thymeleaf 코드를 걷어내자!! 그래서, 모든 프로젝트에서 Thymleaf를 걷어내고 Vue.js를 사용할 예정이다.
2) Vue.js의 Life Cycle
- Vue Options API 중 Life Cycle은
이벤트 함수 처리
라고 불린다. - Vue에 의해 작성된 화면 구성을 바꾸려면 연관되어 있는 Vue model을 찾아야 한다!
- Vue에서 ‘인스턴스’나 ‘컴포넌트’를 생성할 때, 생기는 과정들을 ‘라이프 사이클’이라 한다.
- 인스턴스의 ‘상태’에 따라 호출할 수 있는 속성들을 ‘라이프 사이클 속성’이라고 한다. 그리고 각 라이프 사이클 속성에서 실행되는 ‘커스텀 로직’을 ‘라이프 사이클 훅(Hook)’이라고 한다.
- Lifecycle Hooks** :
- 컴포넌트가 생명 주기를 거쳐 생성되고 사라지는 데 과정마다 hook을 걸어서 코드를 실행 할 수 있다.
a. Vue Life Cycle의 중요한 점
- 하지만, 데이터를 언제 가져와야하는지?
- 페이지가 처음 실행되는 블럭?
b. Vue Life Cycle 정리
- 서버에 데이터를 요청할 때, Vue Life Cycle은 유용하게 사용 :
- created(), mounted()에서 주로 사용한다.
a) Creation : 컴포넌트 초기화 단계
- 컴포넌트들을 초기화하는 단계이며 해당 단계에서 실행되는 훅들이 라이프사이클 중에서 가장 처음 실행된다.
- 컴포넌트가 DOM에 추가되기 전이다. 서버 렌더링에서도 지원되는 훅이다.
new Vue()
:- Vue Instance(Component) 생성
init()
:- Event와 Lifecycle 초기화
a-1) beforeCreate** :
- 인스턴스가 방금 초기화 된 후 데이터 관찰 및 이벤트 / 감시자 설정 전에 동기적으로 호출
- 모든 훅 중에 가장 먼저 실행되는 훅
- 아직 data와 events가 세팅되지 않은 시점이므로 접근 시, 에러가 발생된다.
- 가장 중요!!
a-2) created :
- 인스턴스 작성 후 동기적으로 호출
- 데이터 처리, 계산된 속성, 메서드, 감시/이벤트 콜백 등과 같은 옵션 처리를 완료
-︎ 아직 mount가 되지 않았으므로
mount
속성은 아직 사용할 수 없다.
- created 훅에서는 이제 data와 events가 활성화되어 접근할 수 있다.
- 여전히 템플릿과 가상돔은 마운트 및 렌더링되지 않은 상태이다.
b) Mounting : DOM 삽입 단계
- Mounting 단계는 초기 렌더링 직전에 컴포넌트에 직접 접근할 수 있다.
- ︎서버렌더링에서는 지원하지 않는다.
- 초기 렌더링 직전에 DOM을 변경하고자 한다면 이 단계에서 활용할 수 있다.
- 그러나, 컴포넌트 초기에 세팅되어야할 데이터는 created 단계를 사용하는것이 낫다.
b-1) beforMount :
- mount는 원래, 유닉스에서 사용하던 용어인데 USB를 꽂은 다음에 USB와 OS를 연결해주기 위해서 mount라는 것을 시켜야 했었다. 여기서 나온 ‘연결’을 뜻하는 용어이다.
- Vue의 모든 객체를 활용할 수 있을 때, 사용한다.
- beforMount는 클라이언트가 작업하기 전에 서버에서 만들어오는 내용들이다.
- 아직, 화면에 연결이 안 된 상태이다.
- mount 시작되기 바로 전에 호출된다.
- render 함수의 첫 호출
- 대부분의 경우, 사용을 권장하지 않는다.**
- beforeMount 훅은 템플릿과 렌더 함수들이 컴파일된 후에 첫 렌더링이 일어나기 직전에 실행된다.
- 그리고 서버사이드 렌더링시에는 호출되지 않는다.
b-2) mounted :
- DOM에 node가 붙어야한다. DOM에 node가 붙으면 이것은 mount가 된것이다.
- 인스턴스가 마운트 된 직후 호출.
- 컴포넌트,템플릿, 렌더링된 돔에 접근 할 수 있다.
- 루트 인스턴스가 문서 내의 엘리먼트에 마운트 되어 있으면, mounted가 호출될 떄 Vue의 controller의 함수도 문서 안에 있게 된다.
- 모든 자식 컴포넌트가 마운트 된 상태를 보장하진 않는다.
- 부모와 자식 관계의 컴포넌트에서 우리가 생각한 순서로 mounted가 발생하지 않는다는 점이다. 즉, 부모의 mounted훅이 자식의 mounted훅보다 먼저 실행되지 않는다.
- 결론** : 부모 구성 요소는 자신의 템플릿을 DOM에 마운트하기 전에 자식이 마운트될 때까지 기다린다는 점만 기억하면 됩니다.
- Created 훅은 부모 -> 자식의 순서로 실행되지만 mounted는 그렇지 않다는 것을 알 수 있다. 다른 식으로 말하면, 부모는 mounted훅을 실행하기 전에 자식의 mounted훅이 끝나기를 기다린다!!
c) Updating : Diff 및 재 렌더링 단계
- 컴포넌트에서 사용되는 반응형 속성들이 변경되거나 어떠한 이유로 재렌더링 발생 시, 실행된다.
- 디버깅이나 프로파일링 등을 위해 컴포넌트 재 렌더링 시점을 알고 싶을 때, 사용하면 된다.
- 서버렌더링에서는 호출되지 않고 유용하게 활용할 수 있는 기능이다.
c-1) beforeUpdate :
- 컴포넌트의 데이터가 변하여 Update 사이클이 시작될 때, 실행된다.
- 정확히는 DOM이 업데이트되기 전, 데이터가 변경될 때 호출된다.
- 업데이트 전에 기존 DOM에 접근할 수 있다.
- 재 렌더링 전이 새 상태의 데이터를 얻을 수 있고 더 많은 변경이 가능하다.
c-2) updated :
- 메모리에 생성한 다음에 화면에 붙이는데 화면에 붙일 때, Vue controller에 의해 Vue model의 항목이 바뀌면 이것은
updated
상태이다. - updated가 호출될 때, 컴포넌트의 DOM이 업데이트되므로 여기서 DOM의 종속적인 연산을 할 수 있습니다. ︎- 그러나, 여기서 상태를 변경하면 무한루프에 빠질 수 있다.
- 모든 자식 컴포넌트의 재 렌더링 상태를 보장하지 않는다.
d) unmount : 컴포넌트 삭제
d-1) beforeUnmounted :
- Vue 인스턴스가 파괴되기 직전에 호출되는 단계이다.
- 이 단계에서는 아직 인스턴스에 접근할 수 있으며, Vue 인스턴스의 데이터를 초기화 시키기 좋은 단계이다.
- 컴포넌트는 원래 모습과 모든 기능들을 그대로 가지고 있다.
- 이벤트 리스너를 제거하거나 reactive subscription을 제거하고자 한다면 이 훅이 제격이다.
- 서버 렌더링시 호출되지 않는다.**
d-2) unmounted :
- Node에서 빠져서 화면에서 안보이게 되었다.
- 보통 다른 페이지로 이동하면, unmount가 된다.
- Vue 인스턴스가 파괴되고 나서 호출되는 단계이다.
- Vue 인스턴스의 모든 디렉티브가 바인딩 해제되고 Vue 인스턴스에 정의한 모든 속성이 제거되고 하위에 선언한 인스턴스들 또한 모두 파괴시킨다.
c. 실습 코드 :
a) 기존 Vue 코드 (1) :
- Vue 라이프 사이클을 이용하지 않고 model을 이용한 방법
- list.js
methods:{
categoryClickHandler(e){
//console.log("clicked");
// this.load(e.target.dataset.id,function(){
// console.log("데이터가 도착했니?");
// });
// console.log("click");
// await this.load(e.target.dataset.id); // 기다릴테니까 난 기다렸다가 할거야... -> 나름 동기형으로 처리
//
// console.log("데이터가 도착한 후에 할일");
// this.load(2);
// 화면을 바꾸려면 연관되어 있는 모델을 찾아라!
this.list.push({id:4, name:"디카페인 아메리카노", price:5500});
// 하지만 데이터를 언제 가져와야하는지?
// 페이지가 처음 실행되는 블럭?
},
b) Vue의 라이프 사이클을 이용해서 Vue로만 만든 전체 코드 (2) :
- 아래 코드처럼 이렇게하면, Vue.js로만 페이지가 만들어진다!
- Vue model을 이용한 것이 아니라 Vue의 라이프 사이클을 이용했다.
- mount가 아니라 mounted로 동작한다.
- 이렇게 만들면, 싱글페이지가 아닌가?
Vue
.createApp({
data(){
return{
test:"hello",
list:[
{id:1, name:"아메리카노", price:5000},
{id:2, name:"카페라떼", price:5500},
{id:3, name:"카페모카", price:6000}
// push pop : 뒤에서 부터 빼고 넣기 // unshift shift : 앞에서 부터 빼고 넣기
]
};
},
methods:{
categoryClickHandler(e){
// this.load(2);
// 화면을 바꾸려면 연관되어 있는 모델을 찾아라!
this.list.push({id:4, name:"디카페인 아메리카노", price:5500});
// 하지만 데이터를 언제 가져와야하는지?
// 페이지가 처음 실행되는 블럭?
},
load(){
// ========================================================================================
// XHR(XmlHttpRequest)
// - Callback(나중에 호출한다 = delegation 함수 = 나중에 호출하므로 '위임함수'라고도 부름.)을
// 이용한 비동기처리
// Fetch API
// - Promise를 이용한 비동기 처리
// ========================================================================================
// ****** '.'을 써서 바로 위의 코드의 중첩의 중첩으로 미들웨어 시스템으로 가져갈 수 있다. *****
let promise = fetch("/menus");
promise
.then(response=>{
// console.log("도착 했냐?");
// console.log(response.json());
return response.json(); // 반환을 하면 아래 코드처럼 계속 이어가서 또 반환 할 수 있다.
})
.then(list=>{
this.list = list; // 기존의 데이터를 대치하는 것이라서 추가하는 것이 아니다.
// 데이터를 추가하려면 이렇게 하면 안된다.
// this.list는 모델에 담는 것이다. list는 넘겨 받은 데이터이다.
})
.catch(error=>{
console.log("error"); // 중간에 url의 요청한 데이터가 반환 에러가 발생하면 점프해서 에러 반환!!
}); // then은 이전의 내용을 중첩해서 사용한다.
// ========================================================================================
// *** 이게 제일 편하고 간단해서 아래의 코드처럼 사용한다!! ***
// let response = await fetch("/menus"); // fetch가 Promise를 대신 해준다! 되게 간단해졌다!!
// let list = await response.json(); // 동기형인 비동기형이다. 화면은 안 바뀌고 데이터만 바뀌므로!!
// console.log(list);
// ========================================================================================
// ========================================================================================
}
},
beforeCreate(){console.log("beforeCreate")},
created(){console.log("created")},
beforeMount(){console.log("beforeMount")},
mounted(){
console.log("mounted")
this.load();
},
beforeUpdate(){console.log("beforeUpdate")},
updated(){console.log("updated")},
beforeUnmount(){console.log("beforeUnmount")},
unmounted(){console.log("unmounted")}
})
.mount("#main-section");
c) 위의 코드의 ES6 JS this 개념 추가 정리** :
-
결론** :
promise.then
내부에서 실행되는this.list = list;
는this
가data()
객체이다. -
하지만, 원래 여기서
this
는 스코프 개념 때문에methods
객체이다. 그렇지만methods
는 this로서 list와 관련된 객체 정의가 된 것이 없어서 this는 없다고 무방하여 에러가 발생할 것이라고 예상된다. -
하지만,
this.list
에서data()
객체가Vue
의 라이프 사이클 중mounted()
에 의해1oad()
가 실행될 때,call()
이 되기 때문에this.list
에서list
의 객체로 인식된다.** -
정리** :
this.list
에서this
는data()
이다. 그래서data()
객체의list
속성에다가fetch
의 요청 데이터인list
를 담는다.(Vue
모델에 요청 데이터를 담는 것과 같다.)** -
이러한 개념은 ES6 JS 개념에서
call() 함수
개념을 사용하는 것과 같다. 그래서, call 함수 내부의 값으로 객체로 감싸는call() 함수
를 사용하는 것과 같다. -
ES6 JS의 call() 개념은 함수가 호출한 주체가 this가 된다.**
d. 참고 사이트 :
3) ES6 JS call() 연습
- 없는 this를 붙여주기 위해서 call을 사용했었다.
- 아래 코드는 그에 해당하는 예시 코드이다.
// strict mode
// "use strict";
// ** this가 없으면 error(변수 중복 에러, 변수 선언 에러)를 발생하게 해주고
// html의 script 태그에서 type이 module 타입이면 strict mode로 작동한다. **
var x = 30;
console.log(window.x); // undefined
var f1 = function(){
console.log(this); // f1
}
f1();
// var obj = {x:x, f1:f1};
var obj = {x,f1};
obj.f1();
// obj.f1();에서 obj가 f1()의 this를 의미한다.
// this 없더라도 call을 써서 이렇게 해야지 this를 전달해서 확인할 수 있다.
f1.call(obj);
// add가 있으면 {}는 JS 객체 뽀개기로 사용한다.
// 객체로 인자를 받는다.
function add({x,y}){
return x+y;
}
add(3,4);
// var {x,y} = values;
// var x = values.x;
// var y = values.y;
// ======================================
// 데이터를 받는 입장! // 객체로 인자를 받는다.
function createApp({data, methods}){
// var data = values.data;
// var methods = values.methods;
// methods가 정해진 것이 없는데 그것의 자식을 불러온다 어떻게? call로 불러들인다.
let d = data();
// methods.f1();
// -> 아래 코드의 this.test에서 아직 methods.f1()이 this가 정해진게 없어서 undefined이다.
// **여기서, this는 methods.f1()인데 methods.f1()가 정해진 것이 없어서 methods.f1()의 this 또한 정의 된 것이 없다.**
// **그래서 바로 아래 코드 처럼 call로 this를 만들어서 붙여줘야 한다.**
methods.f1.call(d); // this.test에서 그래서, call로 붙여줘야 한다.
methods.f1.call(Object.assign(d,methods));
};
// 전달하는 입장!(객체) : JS 코드를 실행하는 부분
createApp({
data(){
return{
test: "Hello"
};
},
methods:{
f1(){
console.log(this.test);
// 여기서 this는 스코프 개념에서 methods.f1()인데 methods.f1()가 정해진 것이 없어서 methods.f1()의 this 또한 정의 된 것이 없다.
// 그래서 call()로 함수를 호출하는 곳이 정의되어 있는 곳으로 붙여주자! 그것이 data()쪽이다.
this.f2(); // 원래는 undefined인데 call을 객체로 call해주면 객체를 합쳐서 this를 다시 만들어 줄 수 있다.
},
// this로 사용되는 객체를 합치는 것도 가능하다!
f2(){
console.log("hehe");
}
}
});
4) Vue: 메뉴 이벤트 처리
- css ‘d-none’ 속성의 토글 연습
v-bind:class
에서:class
만 적어서 사용하자!
a) 실습 코드 :
- list.js
Vue
.createApp({
data(){
return {
test:"hello",
list:[
// push pop : 뒤에서 부터 빼고 넣기 // unshift shift : 앞에서 부터 빼고 넣기
],
isShowRegForm:true // 토글을 위한 부분
};
},
methods:{
// e 가 없어도 되네?
menuAddHandler(){
// toggle on / off
this.isShowRegForm = !this.isShowRegForm;
},
- list.html
<section class="cart-section">
<h1 class="d-none">장바구니</h1>
<span class="text-title3">커피음료</span>
<a class="btn btn-mini bg-blue" href="" @click.prevent="menuAddHandler">메뉴추가</a>
</section>
<section class="menu-section">
<h1 class="d-none">메뉴목록</h1>
<div class="menu-list">
<section class="menu menu-reg-section border-bottom border-color-1" :class="{'d-none':isShowRegForm}">
<form class="overflow-hidden">
<h1><input type="text" class="input w-75 w-100-md" name="titile" placeholder="메뉴 이름을 입력하세요."></h1>
<div class="menu-img-box">
<img src="/image/product/blank-img.png" class="img-input">
<input type="file" class="d-none">
</div>
<div class="menu-price"><input class="w-75 w-50-md input ml-0 ml-1-md" type="text" placeholder="가격을 입력하세요"> 원</div>
<div class="menu-option-list">
<span class="menu-option">
<input class="menu-option-input" type="checkbox">
<label>ICED</label>
</span>
<span class="menu-option ml-2">
<input class="menu-option-input" type="checkbox">
<label>Large</label>
</span>
</div>
<div class="menu-button-list">
<input class="btn btn-line btn-round btn-size-1 btn-bd-blue rounded-0-md" type="submit" value="취소">
<input class="btn btn-fill btn-round rounded-0-md btn-size-1 btn-bd-blue btn-color-blue ml-1" type="submit" value="저장">
</div>
</form>
</section>
5) Vue: 다른 이벤트 처리
@click.prevent=""
이용 : 이벤트 핸들러v-model=""
이용 : 사용자 입력을 받을 수 있다.
a) 실습 코드
- list.html
<nav class="menu-category" >
<header class="d-flex">
<h1 class="text-normal-bold">메뉴분류</h1>
<div>
<a class="btn btn-mini bg-blue" href="">분류추가</a>
</div>
</header>
<ul>
<li class="menu-selected">
<a href="/menu/list">전체</a>
</li>
<li>
<a href="">커피음료</a>
</li>
<li data-id="2" @click.prevent="categoryClickHandler">
<a href="">수제청</a>
</li>
<li>
<a href="">샌드위치</a>
</li>
<li>
<a href="">쿠키</a>
</li>
</ul>
</nav>
<section class="cart-section">
<h1 class="d-none">장바구니</h1>
<span class="text-title3">커피음료</span>
<a class="btn btn-mini bg-blue" href="" @click.prevent="menuAddHandler">메뉴추가</a>
</section>
<section class="menu-section">
<h1 class="d-none">메뉴목록</h1>
<div class="menu-list">
<section class="menu menu-reg-section border-bottom border-color-1" :class="{'d-none':isShowRegForm}">
<form class="overflow-hidden">
<h1><input type="text" class="input w-75 w-100-md" name="titile" placeholder="메뉴 이름을 입력하세요." v-model="menu.name"></h1>
<div class="menu-img-box">
<img src="/image/product/blank-img.png" class="img-input">
<input type="file" class="d-none">
</div>
<div class="menu-price"><input class="w-75 w-50-md input ml-0 ml-1-md" type="text" placeholder="가격을 입력하세요" v-model="menu.price"> 원</div>
<div class="menu-option-list">
<span class="menu-option">
<input class="menu-option-input" type="checkbox">
<label>ICED</label>
</span>
<span class="menu-option ml-2">
<input class="menu-option-input" type="checkbox">
<label>Large</label>
</span>
</div>
<div class="menu-button-list">
<input class="btn btn-line btn-round btn-size-1 btn-bd-blue rounded-0-md" type="submit" value="취소">
<input class="btn btn-fill btn-round rounded-0-md btn-size-1 btn-bd-blue btn-color-blue ml-1" type="submit" value="저장" @click.prevent="menuSaveHandler">
</div>
</form>
</section>
- list.js
Vue
.createApp({
data(){
return {
test:"hello",
list:[
// push pop : 뒤에서 부터 빼고 넣기 // unshift shift : 앞에서 부터 빼고 넣기
],
isShowRegForm:true,
menu:{name:"aaa", price:0}
};
},
methods:{
// e 가 없어도 되네?
menuSaveHandler(){
console.log(this.menu.name);
},
menuAddHandler(){
// toggle on / off
this.isShowRegForm = !this.isShowRegForm;
},
categoryClickHandler(){
// this.load(2);
// 화면을 바꾸려면 연관되어 있는 모델을 찾아라!
this.list.push({id:4, name:"디카페인 아메리카노", price:5500});
// 하지만 데이터를 언제 가져와야하는지?
// 페이지가 처음 실행되는 블럭?
},
load(){
// ========================================================================================
// XHR(XmlHttpRequest)
// - Callback(나중에 호출한다 = delegation 함수 = 나중에 호출하므로 '위임함수'라고도 부름.)을
// 이용한 비동기처리
// Fetch API
// - Promise를 이용한 비동기 처리
// ========================================================================================
// ****** '.'을 써서 바로 위의 코드의 중첩의 중첩으로 미들웨어 시스템으로 가져갈 수 있다. *****
let promise = fetch("/menus");
promise
.then(response=>{
// console.log("도착 했냐?");
// console.log(response.json());
return response.json(); // 반환을 하면 아래 코드처럼 계속 이어가서 또 반환 할 수 있다.
})
.then(list=>{
this.list = list; // 기존의 데이터를 대치하는 것이라서 추가하는 것이 아니다.
// 데이터를 추가하려면 이렇게 하면 안된다.
// this.list는 모델에 담는 것이다. list는 넘겨 받은 데이터이다.
})
.catch(error=>{
console.log("error"); // 중간에 url의 요청한 데이터가 반환 에러가 발생하면 점프해서 에러 반환!!
}); // then은 이전의 내용을 중첩해서 사용한다.
// ========================================================================================
// *** 이게 제일 편하고 간단해서 아래의 코드처럼 사용한다!! ***
// let response = await fetch("/menus"); // fetch가 Promise를 대신 해준다! 되게 간단해졌다!!
// let list = await response.json(); // 동기형인 비동기형이다. 화면은 안 바뀌고 데이터만 바뀌므로!!
// console.log(list);
// ========================================================================================
// ========================================================================================
}
},
beforeCreate(){console.log("beforeCreate")},
created(){console.log("created")},
beforeMount(){console.log("beforeMount")},
mounted(){
console.log("mounted")
this.load();
},
beforeUpdate(){console.log("beforeUpdate")},
updated(){console.log("updated")},
beforeUnmount(){console.log("beforeUnmount")},
unmounted(){console.log("unmounted")}
})
.mount("#main-section");
6) 메뉴 등록 insert (PostMan 이용)
- 5)의 수정된 추가 예제
- raw는 JSON처럼 통으로 데이터를 보낼 수 있다.
- POSTMAN에서 request 탭에서 AJAX를 위한 fetch API 코드를 가져올 수 있다.
a) 의문점 :
- POSTMAN이 만든 fetch API는 왜 반환이
response
이며then
,then
,catch
구조를 사용할까?
- 요청한 데이터를
@RequestBody
로 매핑해서 받기 위해 보내줄 데이터의요청 Body
타입을 정해주는요청 Header
도 정해줘야 한다.(타입!!정해주기)
- 기본적인 Get, Post에 대한 HTTP 전송 프로토콜에 경우에는
@RequestBody
나@ResponseBody
매핑이 필요가 없었다. 하지만 JSON이나 XML의 데이터를 HTTP 프로토콜로 주고 받기 위해서는 JSON이나 XML의 데이터는 Raw 타입으로 데이터를 보내서 정해진 데이터 타입을 모르기 때문에@RequestBody
나@ResponseBody
매핑을 해줘서 데이터 타입을 설정해줘야 한다.(보통, 요청 Header에서 요청 Body로 받을 데이터 타입을 설정해준다.)
- 따라서, 아래 예제 코드를 보면 요청 데이터를 정해진 타입으로 받기 위해서
myHeaders.append("Content-Type", "application/json");
로서요청 Header
에서요청 Body
의 타입을 정해준다.**- 이 예제에서는 데이터를 JSON 타입으로 보냈었다.
{
"name" : "아메리카노",
"price" : 5000
}
- 핵심** :
fetch() 메소드
는Promise
를 반환한다. 그러나, 이것은 단순히HTTP response
이며 실제JSON
의 데이터가 아니다. Promise는Response Object(객체)
로써 해결된다. 반환된Promise
객체를 실제로 처리해보자.**
Response
객체는 몇 가지 메소드를 가지고 있다.text()
메소드는response
내용을 텍스트로써 다루는Promise
로써 반환한다. Response 객체는 아래와 같은 메소드를 가지고 있다.**arrayBuffer()
blob()
json()
text()
formData()
fetch("http://localhost:8080/menus", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
b) 실습 코드 :
- list.js : Vue 이용
Vue
.createApp({
data(){
return {
test:"hello",
list:[
// push pop : 뒤에서 부터 빼고 넣기 // unshift shift : 앞에서 부터 빼고 넣기
],
isShowRegForm:true,
menu:{name:"aaa", price:0}
};
},
methods:{
// e 가 없어도 되네?
menuSaveHandler(){
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
// 여기만 바꿔주기!!**
var raw = JSON.stringify(this.menu);
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
fetch("http://localhost:8080/menus", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
},
menuAddHandler(){
// toggle on / off
this.isShowRegForm = !this.isShowRegForm;
},
categoryClickHandler(){
// this.load(2);
// 화면을 바꾸려면 연관되어 있는 모델을 찾아라!
this.list.push({id:4, name:"디카페인 아메리카노", price:5500});
// 하지만 데이터를 언제 가져와야하는지?
// 페이지가 처음 실행되는 블럭?
},
load(){
// ========================================================================================
// XHR(XmlHttpRequest)
// - Callback(나중에 호출한다 = delegation 함수 = 나중에 호출하므로 '위임함수'라고도 부름.)을
// 이용한 비동기처리
// Fetch API
// - Promise를 이용한 비동기 처리
// ========================================================================================
// ****** '.'을 써서 바로 위의 코드의 중첩의 중첩으로 미들웨어 시스템으로 가져갈 수 있다. *****
let promise = fetch("/menus");
promise
.then(response=>{
// console.log("도착 했냐?");
// console.log(response.json());
return response.json(); // 반환을 하면 아래 코드처럼 계속 이어가서 또 반환 할 수 있다.
})
.then(list=>{
this.list = list; // 기존의 데이터를 대치하는 것이라서 추가하는 것이 아니다.
// 데이터를 추가하려면 이렇게 하면 안된다.
// this.list는 모델에 담는 것이다. list는 넘겨 받은 데이터이다.
})
.catch(error=>{
console.log("error"); // 중간에 url의 요청한 데이터가 반환 에러가 발생하면 점프해서 에러 반환!!
}); // then은 이전의 내용을 중첩해서 사용한다.
// ========================================================================================
// *** 이게 제일 편하고 간단해서 아래의 코드처럼 사용한다!! ***
// let response = await fetch("/menus"); // fetch가 Promise를 대신 해준다! 되게 간단해졌다!!
// let list = await response.json(); // 동기형인 비동기형이다. 화면은 안 바뀌고 데이터만 바뀌므로!!
// console.log(list);
// ========================================================================================
// ========================================================================================
}
},
beforeCreate(){console.log("beforeCreate")},
created(){console.log("created")},
beforeMount(){console.log("beforeMount")},
mounted(){
console.log("mounted")
this.load();
},
beforeUpdate(){console.log("beforeUpdate")},
updated(){console.log("updated")},
beforeUnmount(){console.log("beforeUnmount")},
unmounted(){console.log("unmounted")}
})
.mount("#main-section");
- MenuController.java : API 코드
package kr.co.rland.web.controller.api;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ObjectMapper;
import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.entity.MenuView;
import kr.co.rland.web.service.MenuService;
// API 이용하기 = AJAX
@RestController("apiMenuController")
@RequestMapping("menus") // 예전에는 절대경로로 썼지만 지금은 "/"를 안써도 된다.
public class MenuController {
// REST API에서는 반환값이 문서를 전달하는 것이 아니라 사용자가 받는 데이터이다.
// List<MenuView>
// public List<MenuView> getList(){
//
// }
@Autowired
private MenuService service;
// 이전까지는 HTML이 제공해주는 Method만 이용했었다.
// 이제 HTTP가 제공해주는 API를 쓸 수 있다.
@GetMapping // @RequestMapping과 경로가 같다면 여기는 안써도 된다.
public List<MenuView> getList(
@RequestParam(name = "p", defaultValue="1") int page,
@RequestParam(name = "c", required=false) Integer categoryId, // categoryId는 무조건 사용하는 것이 아니라서
@RequestParam(name = "q", required=false) String query) { // @RequestParam 이용하자!!
// null을 담기 위해서 required false 설정을 한다.
List<MenuView> list = service.getViewList(page, categoryId, query);
// 메인 스레드는 잠궈버리면 안된다! 그래서 콜백함수를 사용한다.
// 콜백함수는 이벤트를 위임하는 것이다. 미리 설정해놓고 나중에 호출한다.
// 콜백함수는 비동기 작업이 완료되면 호출한다.**
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return list;
}
// 경로를 중간이 {}로 감싸면 경로를 변수화할 수 있다.
// 이 id를 찾다가 없으면 쿼리스트링을 찾다가
// @PathVariable : Path에서 찾아서 이 변수에 담아달라고 매핑해준다.
// return 값에 이렇게 전달된다.
@GetMapping("{id}")
public Menu get(
@PathVariable("id") int id) {
// Menu menu = service.getById(id);
Menu menu = service.getById(id);
return menu; // 객체를 반환할수 없어서 데이터를 반환해줘야 한다.
}
@PutMapping // put도 통째로 바뀌어서 Mapping 경로를 {id}도 없애도 된다.??
public String edit(
@PathVariable("id") int id) {
return "menu edit: " + id;
}
@DeleteMapping("{id}")
public String delete(
@PathVariable("id") int id) {
return "menu del : " + id;
}
// insert는 아직 넣어줄 데이터가 없어서 문제가 많다.
// 요청한 데이터를 @RequestBody로 매핑하면, 헤더를 정해줘야 한다.(타입!!정해주기 )
@PostMapping
public String insert(@RequestBody Menu menu) {
menu.setCategoryId(1);
int addCount = service.add(menu);
System.out.println(addCount);
System.out.println(menu.getName());
return "menu insert";
}
}
- 추가로 할 것: menu insert하는 서비스 완성시키기