본문 바로가기

JavaScript/[강의] 문벅스 -ing

step1. 돔 조작과 이벤트 핸들링으로 메뉴 관리하기

0.  step 1을 통해서 내가 배운 것

1) 기술적

- 돔 조작 방법(요소 추가, 삭제)

- 이벤트 핸들링(키보드 이벤트, 클릭 이벤트, 기본 이벤트 막기, 이벤트 위임)

- 브라우저 인터페이스 활용

- 예외처리(return 적절하게 사용하기)

- 다양한 메서드들

▶ 돔 요소 선택시 querySelector(),

 이벤트 기본동작 막는 preventDefault(),

 특정 돔 트리 안에 원하는 노드 추가하는 insertAdjacentHTML(),

 해당하는 돔 요소를 다 가져오는 querySelectorAll(),

 노드리스트 길이 구하는 length,

 이벤트를 발생시킨 요소를 가리키는 event.target(),

 요소가 갖고 있는 클래스를 배열처럼(DOMTokenList) 반환하는 event.target.classList(),

 돔토큰리스트에 클래스가 있는지 없는지 불린값을 반환하는 contains(),

 가장 가까운 요소를 갖고 오는 closest()

 

2) 태도

- 개발 전 요구사항 구현을 위한 전략 짜기

- 기능을 대표하는 이름인가

- 중복되는 코드는 없는가

 

 

1.  요구사항 분석

(1)번 요구사항이 (2)번 처럼 더 구체화되었다.

개발부터 바로 들어가기 전에

1) 어떤 요구사항이 있는지 확인하고,

2) 각 요구사항들의 의존성 그리고 한 문장 안에 여러가지 요구사항이 있을 경우 하나씩 쪼개면서

3) 내가 구현해야할 요구사항들 정리한다.

 

>> 이렇게 하는 이유? : 요구사항 구현이라는 목적을 분명히 해야만 개발 도중 헤매지 않기 때문이다.

++ 이 강의에서는 step1, step2, step3 이렇게 요구사항을 미리 나눴기 때문에 무엇을 우선으로 작업해야 하는지 그 순서를 알기 쉬웠다. but 현실의 나는 '순서'를 파악하는 것이 어렵고, 잘 쪼개는 게 어렵고, 작은 단위의 task를 구현하기 위해 내가 무엇을 알아야 하는지 또는 무얼 찾아봐야 하는지 알아내는게 어렵다. 앞으로도 계속 요구사항을 잘게 쪼개는 연습을 하쟈!

 

 

2. 자바스크립트 파일 초기 세팅

구현 목표: 자바스크립트 파일을 브라우저에서 불러왔을 때, 자바스크립트 파일 내 코드가 실행되도록 하기

=> 구현한 기능 전체를 감싸는 함수를 만들고, 그 함수를 호출한다.

function App() {
	// 구현한 기능들...
}

App();

 

2-1. 돔 요소 선택하는 메서드 중복 방지위해 util 함수 만들기

// util 함수, 화살표함수에 대괄호가 없으면 바로 리턴됨
const $ = (selector) => document.querySelector(selector);

 

 

3. <input /> 태그에 이벤트 리스너 등록

구현 목표: 사용자가 인풋창에 키보드로 입력한 값을 받고 싶다.

=> 인풋 창에 키입력 이벤트를 걸고, 엔터를 눌렀을 때 인풋의 값을 변수에 저장한다.

$("#menu-name").addEventListener("keydown", (event) => {
  if (e.key === "Enter") {
    $("#menu-name").value;
  }
})

 

1) 사용자가 어떤 를 눌렀는 지 알 수 있는 메서드: key.

보통 어떤 키를 눌렀는 지 그 값을 알기 위해서는 그 전에 키보드 이벤트(keydown, keyup, keypress(추천 X))가 발생해야함.

=> 이  key 메서드는 키보드 이벤트가 일어나야만 특정한 값을 얻는데 그 효용이 있음.

https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key

 

2) input element의 값을 반환하거나 세팅하는 속성: value

*추가 change event: input 값이 변경될 때 이벤트 발생, 텍스트 입력의 경우 포커스를 잃을 때 실행

아래 코드 둘 다 똑같은 동작 하는 줄 알았으나 아님. 그래서 keypress 이벤트 발생시 핸들러 등록함

$("#menu-name").addEventListener("keypress", (e) => {
  if (e.key === "Enter") {
    console.log($("#menu-name").value);
  }
});

$("#menu-name").addEventListener("change", (e) => {
  console.log($("#menu-name").value);
});

 

 

4. form 태그 자동 전송 막기

구현 목표: submit 이벤트 기본 동작인 새로고침 막기

 

1) 해당 이벤트에 대한 기본 동작을 실행하지 않도록 하는 메서드: preventDefault();

form 태그는 기본적으로 웹서버에 무언갈 전송하는 태그다.

form 태그에서 전송 항목이 1개일 때 input 또는 textarea에서 엔터키를 누르면 자동 submit 이벤트 발생되어 브라우저가 화면을 새로고침한다.

=> 엔터키나 또는 제출 버튼을 눌렀을 때 새로고침 되는 것을 막아주기 위해 form 태그가 자동으로 전송되는 걸 막아줘야 한다.

$("#menu-form").addEventListener("submit", (e) => {
  e.preventDefault();
});

* 자동 전송 막는 방법(1. 엔터 입력 제어 2. Form 태그에 onSubmit="return false;" 추가 3. 속성 hidden을 가진 input 태그를 하나더 추가한다, 출처: https://fruitdev.tistory.com/151)

 

 

5. DOM tree 특정 위치에 html 추가

구현 목표: 사용자가 입력한 텍스트를 템플릿에 넣어서 DOM tree 특정 위치에 추가

=> html template을 변수에 따로 저장하고, 사용자가 입력한 텍스트를 저장한 변수를 템플릿 안에 넣어서 추가하기

 

1) 사용자 입력한 값이 들어간 html code 문자열을 담은 템플릿을 리턴하기: 함수로 만들어야 함

const $menuName = $("#menu-name").value;

// 1번 표현식
const menuItemTemplate = (menuName) => {
  return `<li>${menuName}</li>`
};

// 2번 표현식
const menuItemTemplate = (menuName) =>
  `<li>${menuName}</li>`
;

* 함수로 선언한 이유?

 

2) 특정 위치에 DOM tree 안에 원하는 node 추가(업데이트 아님)하는 메서드: insertAdjacentHTML( );

$("#menu-name").addEventListener("keydown", (e) => {
  if (e.key === "Enter") {
    const menuName = $("#menu-name").value;
    const menuItemTemplate = (menuName) => `<li>${menuName}</li>`;
    
    $("#menu-list").insertAdjacentHTML("beforeend", menuItemTemplate(menuName));
  }
})

https://developer.mozilla.org/ko/docs/Web/API/Element/insertAdjacentHTML

* innerHTML과 insertAdjacentHTML 차이는..?

https://www.udemy.com/course/vanilla-js-lv1/learn/lecture/27839466#questions/15948704

 

 

6. HTML 요소 개수 업데이트

구현 목표: html 요소, ul 태그 안에 있는 li 개수 세기

=> 메뉴 리스트(ul 태그) 안에 있는 메뉴 아이템(li) 개수를 세고 화면에 업데이트

 

1) ul 태그 하위에 있는 li 요소 가져오는 메서드: querySelectorAll

querySelectorAll 메서드는 지정된 설렉터 그룹에 일치하는 다큐먼트의 요소 리스트를 나타내는 NodeList 반환

이런 식으로 NodeList 반환

 

2) NodeList 길이 구하는 메서드: length

const menuTotal = document.querySelectorAll("#menu-list li").length;

https://developer.mozilla.org/ko/docs/Web/API/NodeList

* 출처: https://stackoverflow.com/questions/20040825/check-how-many-li-there-are-in-a-ul-with-javascript/20040849

 

Check how many <li> there are in a <ul> with javascript

Following my code: HTML: <ul id="ul_o"> <li>v1</li> <li>v2</li> </ul> JS: console.log(document.getElementById("ul_o").getElementsByClassName("LI").length); ...

stackoverflow.com

 

 

7.  조건 만족시 함수 중단(feat. return)

구현 목표: 조건 만족시 함수를 중단시켜 다음 코드를 실행시키지 않도록 한다.

=> 인풋창에 입력한 값이 빈 값일 때 alert 창을 띄우고 return을 호출해 즉시 실행을 멈추게 한다.

함수는 return을 호출하는 지점에서 즉시 실행을 멈춥니다.

$("#menu-name").addEventListener("keypress", (e) => {
    
    if (e.key === "Enter") {
    
      const menuName = $("#menu-name").value;
      if (menuName === null || menuName.trim() === "") {
        alert("메뉴명을 입력해주세요");
        return;
      }
      
      // code..
      // code..
    }
});

 

 

8. 이벤트 위임(Event Delegation)

구현 목표: 처음 index.html과 index.js 파일을 불러왔을 때 없는 돔 요소에 이벤트 걸기

=> 처음 파일 불러올 때, 없는 돔 요소(이 예제에서는 수정 버튼)에게 이벤트를 걸어야 하기 때문에 그 상위 돔 요소(메뉴 리스트)에 이벤트를 위임한다.

 

원래는 아래와 같이 menu-edit-button 을 클릭할 때 바인딩되는 핸들러를 등록해야하지만

$(".menu-edit-button").addEventListener("click", () => {
	// code...
})

그러면 이런 에러가 뜬다.

index.js를 불러올 때 index.html에는 "menu-edit-button"을 가진 돔 요소가 없기 때문에 이런 에러가 발생한다.

이벤트 등록에 관한 에러를 처리하기 위해 "menu-edit-button" 보다 상위에 있는 돔 요소인 "#menu-list"에 이벤트를 위임한다.

$("#menu-list").addEventListener("click", (e) => {
	// code...
})

* 이벤트 위임의 장점

이벤트 리스너를 등록한 요소가 많을 수록 페이지의 실행속도는 느려진다!

그래서 효율적으로 이벤트를 관리하기 위해서 이벤트의 흐름을 이용하는데

이벤트는 이벤트가 발생한 요소(elements)를 포함하고 있는 부모 요소(element)에도 영향을 미치기 때문에 자식 요소를 포함할 수 있는 요소에 이벤트 핸들러를 등록하고 이벤트 흐름을 이용해 다룰 수 있다.

즉, 이벤트 리스너가 실행할 작업을 부모 요소에게 위임할 수 있다.

 

 

9. 돔 요소에 특정 클래스의 존재 여부를 확인하는 메서드

구현 목표: 특정 클래스가 있냐에 따라 각각 다른 이벤트 리스너 등록

 

1) event.target: 이벤트를 발생시킨 요소를 알고 싶을 때

 

2) event.target.classList : 이벤트를 발생시킨 요소가 가지고 있는 클래스를 DOMTokenList인 배열처럼로 가져오는 메서드.

https://developer.mozilla.org/ko/docs/Web/API/Element/classList

 

3) event.target.classList.contains("클래스 이름"): 이벤트를 발생시킨 요소의 클래스에 특정 "클래스 이름"이 있는지 없는지 불린 값으로 리턴하는 메서드

https://developer.mozilla.org/ko/docs/Web/API/DOMTokenList/contains

 

4) event.target.closest("li"): 이벤트를 발생시킨 요소에게 가장 가까운 요소를 찾아내는 메서드

$("#menu-list").addEventListener("click", () => {
  if (e.target.classList.contains("menu-edit-button")) {
  	const $menuName = e.target.closest("li").querySelector(".menu-name");
    const updatedMenuName = propmt("메뉴명을 수정하세요", $menuName.innerText);
    $menuName.innerText= updatedMenuName;
  }
})

* 처음에 돔 요소의 innerText를 변수에 저장했음.. 그게 아니라 돔 요소를 변수에 저장하고 그 요소의 innerText 메서드를 이용하기

 

 

10.  중복되는 코드 재사용 가능한 함수로 만들기

구현 목표: 엔터키 입력시 메뉴명 추가하는 코드와 확인 버튼 클릭시 메뉴명 추가하는 코드가 중복됨. 재사용 가능한 함수로 만든다.

엔터키를 입력할 때 메뉴명을 추가하는 것과, 확인버튼을 클릭할 때 메뉴명을 추가하는 코드가 중복된다.

메뉴명을 추가하는 함수를 하나 만들어서 재사용할 곳에서 적절하게 사용한다.

 

구현 목표: 메뉴를 추가할 때와 메뉴를 삭제할 때 총 메뉴 갯수를 업데이트하는 코드가 동일하다. 재사용 가능한 함수로 만든다.

메뉴 갯수를 업데이트 하는 재사용 가능한 함수를 만들어서 필요한 곳에 사용한다.

아래처럼↓↓↓↓↓↓↓↓

* 함수명 이름 지을 때. 함수 내부의 동작을 대표하는 이름으로 짓기!

 

 

11. 리팩토링

1) 재사용 가능한 함수는 함수끼리, 이벤트 바인딩 되는 것은 바인딩 되는 것들끼리 모아 놓기

2) 함수로 분리해서 더 명확하게 할 수 있는 코드는 없는지 확인하기

3) 이벤트리스너에 등록하는 콜백함수에 넘기는 파라미터가 없고 콜백함수 하나만 있다면 간소화하기

// 1번
$("#menu-submit-button").addEventListener("click", () => {
  addMenuName();
});

// 2번
$("#menu-submit-button").addEventListener("click", addMenuName);

// 1번, 2번 같은 동작 ㅇㅇ

 

* 참고

- propmpt 인터페이스, confirm 인터페이스 : 여기서 인터페이스란 브라우저에서 제공하는 기능이라고 생각하면 된다.

- html 클래스명과 변수명(또는 함수명)을 일치시키면 좋다.

 

* 회고

단순히 코드를 따라 치는 게 아니라 강사님의 생각을 훔치자!

'JavaScript > [강의] 문벅스 -ing' 카테고리의 다른 글

step2. 상태관리로 메뉴 관리하기  (1) 2022.01.01