Todo-List라면 당연히 할 일(이 강의에서는 할일=메뉴이다. 앞으로는 메뉴라고 작성하겠다) 데이터를 추가해야 한다. 그럼 가장 먼저 input
태그를 통해 사용자가 할 일을 입력하게 되는데, React에서는 간단하게 이렇게 구현했을 것이다.
const [text, setText] = useState('')
const onChange = (e) => {
setText(e.target.value)
}
const onSubmit = (e) => {
e.preventDefault();
if(e.key === 'Enter') console.log('submit 성공!')
}
<span class="mr-2 mt-4 menu-count">총 0개</span>
<form id="espresso-menu-form" onSubmit={onSubmit}>
<input id="espresso-menu-name" type="text" onChange={onChange} />
</form>
간단하다. 그럼 이걸 JS로 변화시켜보자.
onSubmit
이벤트 핸들러 함수const App = () => {
// html에서 enter를 눌렀을 때
// form 태그 안에서 누르게 되면 화면이 새로고침 된다(무엇을 전송하는 동작을 하기 때문)
document
.querySelector('#espresso-menu-form')
.addEventListener('submit', e => {
e.preventDefault();
});
// 메뉴 이름 입력받는 부분
// React에서는 상태에 넣어서 간단하게 구현했었는데..?
document
.querySelector('#espresso-menu-name')
.addEventListener('keypress', e => {
if (e.key === 'Enter') console.log(e.target.value);
});
};
App();
훨씬 코드가 길다. 먼저, 첫 번째 이벤트 리스너 함수에서는 submit
e.preventDefault()
로 화면 새로고침을 막아준 것이다. 이를 위해서 form
태그에 document.querySelector
을 통해 접근해야 한다.
또한, 엔터를 눌렀을 때 keypress
라는 이벤트 타입을 지정해줘야 한다. 또한 이는 input
태그의 id
인 espresso-menu-name
을 가져온다.
document.querySelector()
메서드가 너무 길기 때문에 $
라는 util 함수를 추가해서 작성하도록 하겠다.const $ = selector => document.querySelector(selector);
위에 작성했던 코드에서 메뉴를 입력받는 부분을 바꿔보자.
$('#espresso-menu-name').addEventListener('keypress', e => {
if (e.key === 'Enter') {
const espressoMenuName = $('#espresso-menu-name').value;
const menuItemTemplate = espressoMenuName => {
return `
<li class="menu-list-item d-flex items-center py-2">
<span class="w-100 pl-2 menu-name">${espressoMenuName}</span>
<button
type="button"
class="bg-gray-50 text-gray-500 text-sm mr-1 menu-edit-button"
>
수정
</button>
<button
type="button"
class="bg-gray-50 text-gray-500 text-sm mr-1 menu-remove-button"
>
삭제
</button>
</li>`;
};
$('#espresso-menu-list').innerHTML = menuItemTemplate(espressoMenuName);
}
});
문제가 생겼다. ul
태그 사이에 li
태그를 집어넣어야 해서 innerHTML
메서드를 통해 구현했는데, 데이터가 아래에 쌓여서 보여지는 형태가 아니라 계속 다른 데이터로 덮어 씌워진다.
이 문제를 해결하기 위해서 innerHTML
메서드 대신 insertAdjacentHTML
를 사용하였다. 이 메서드는 element
안에 존재하는 element
를 건드리지 않는다.
inserAdjacentHTML에 대한 추가 정보: https://developer.mozilla.org/ko/docs/Web/API/Element/insertAdjacentHTML
그럼 맨 아래 코드를 innerHTML
대신 insertAdjacentHTML
을 적용한 코드는 다음과 같다.
$('#espresso-menu-list').insertAdjacentHTML('beforeend', menuItemTemplate(espressoMenuName));
이제 아래 부분에 메뉴의 전체 개수를 보여줘야 한다.
<span class="mr-2 mt-4 menu-count">총 0개</span>
상태값.length
를 보여주면 전체 데이터의 개수를 구할 수 있었다. 그러면 Vanilla JS에서는 어떻게 할까? 일단 Vanilla JS는 React처럼 상태값을 제공하지는 않는다. const menuCount = $('#espresso-menu-list').querySelectorAll('li').length;
menuCount
변수를 보자. ul
태그인 #espresso-menu-list
의 자식 요소인 li
태그를 querySelectorAll('li')
을 통해 모두 가지고 왔다.(querySelectorAll
메서드는 배열 형태로 값을 return
한다.) 이후 해당 배열의 length
가 바로 전체 메뉴의 개수가 될 것이다.innerText
메서드를 사용해 값을 변경해준다. $('.menu-count').innerText = `총 ${menuCount}개`;
React에서는 보통 아래와 같이 Text
상태값을 ''
이렇게 빈 string을 넣어주는 방식으로 구현했을 거라고 생각한다.
const onSubmit = (e) => {
e.preventDefault();
if(e.key === 'Enter') console.log('submit 성공!')
setText('')
}
Vanilla JS로는 이렇게 구현할 수 있다.
$('#espresso-menu-name').value = '';
직접 DOM의 value
프로퍼티에 접근하는 것이다.
if (!$('#espresso-menu-name').value) {
alert('값을 입력해주세요.');
return;
}
alert
와 null
을 return
함으로써 값이 없는 경우 alert
창만 뜨게 하였다.그런데 이렇게 구현하면 맨 처음에는 항상 input에 값이 없기 때문에 alert창이 뜨게 된다. 그렇다면 어떻게 해결하면 될까?
if (e.key !== 'Enter') return; // 추가된 부분
if (!$('#espresso-menu-name').value) {
alert('값을 입력해주세요.');
return;
}
input
을 검사하는 코드 위에 e.key
가 'Enter'
가 아닐 경우 null
을 return
하면 된다. 이전에 Todo-List를 만들때의 기억으로는 innerHTML
메서드와 반복문을 사용해 만들었던 걸로 기억하는데, 이번엔 insertAdjacentHTML
를 사용하였다. 처음 알게 된 메서드였는데, 더 직관적이고 사용이 편리한 것 같다.
추가적으로, React에서는 상태값을 변경하는 방식을 사용했지만, Vanilla JS에서는 주로 DOM에 직접 접근한다는 것이다. React에서는 DOM에 직접 접근하는 것이 암묵적으로 '좋지 않다'라고 알고 있었는데, Vanilla JS에서 DOM 직접 접근은 상관없는지? 의문이 생겼다.