두번째 스터디 과제는 아코디언! ! ! 레 쓰 고
내용을 작성하며 참고한 lithium design에서는 트리 메뉴와의 혼동을 방지하기 위해 아코디언에서 아이콘을 우측에 사용한다고 하기에 트리 메뉴가 뭐길래 혼동할 수 있다는 거지? 하는 궁금증이 생겨 알아보았다.
챗GPT의 답변에 의하면 트리 메뉴와 아코디언 메뉴의 차이점은 다음과 같다고 했다.
트리 메뉴 | 아코디언 메뉴 | |
---|---|---|
계층 구조 | 부모-자식 관계 (트리 구조) | 독립적인 패널 (평면 구조) |
동작 방식 | 여러 개의 항목을 동시에 펼칠 수 있음 | 보통 하나의 패널만 열림 |
아이콘 위치 | 제목 앞 (📁 Products) | 제목 오른쪽 끝 (🔽 Products) |
사용 사례 | 파일 탐색기, 카테고리 메뉴 | FAQ, 모바일 메뉴 |
그런데 lithium design에서 아코디언 메뉴 예시로 제시한 사진이 트리 메뉴와 굉장히 유사하게 생겨서 이 설명을 보고도 아이콘의 위치를 제외하고 대체 뭐가 다른 건지 잘 와닿지 않았다.
이 경우 트리 메뉴와 비슷한 형태로 구성되었으나 그럼에도 아래와 같은 차이점이 분명히 있다고 했다.
결론적으로 예시 사진은 트리 구조처럼 보일 수 있으나 하위 메뉴가 더 확장되지 않기에 아코디언 메뉴라는 얘기였다. 한끗차이같은데 굉장히 미묘하구만..!
기능 | forEach() | map() |
---|---|---|
반환값 | undefined (아무것도 반환 안 함) | 새로운 배열 반환 |
원본 배열 | 변경되지 않음 (O) | 변경되지 않음 (O) |
주 사용 목적 | 반복하며 부수 효과 수행 (출력, DOM 조작 등) | 배열 변환 및 새로운 배열 생성 |
사용 예시 | console.log() , DOM 조작, API 호출 | 배열.map(변환 함수) |
✔️ 배열을 변형해서 새로운 배열이 필요하면 → map()
✔️ 배열을 그냥 순회하면서 무언가 실행할 때는 → forEach()
onclick이나 addEventListner나 결과적으로는 같은 기능을 하지 않나? 뭐가 다르지? 라는 의문이 들어 알아봤다.
결론은 생각한 것처럼 비슷한 기능을 하지만 중요한 차이점은 addEventListner를 사용해야 이벤트를 여러 개 적용할 수 있다는 점!!
onclick 이벤트 핸들러 사용 시 새로운 onClick 이벤트를 사용한다면 기존 이벤트를 덮어쓰고 새로운 이벤트가 적용되는 반면, addEventListner를 사용할 경우 여러 이벤트를 추가해도 누적되어 모든 이벤트가 동작한다고 한다!
여러개의 addEventListner 사용 예시
https://mondaymonday2.tistory.com/233
이외의 차이점은
브라우저 호환성
버블링/캡쳐링
addEventListner의 경우 이벤트 발생 시 버블링으로/캡쳐링으로 작동될지 지정할 수 있다. 세번째 파라미터가 true면 캡쳐링, false면 버블링 사용
이러한 옵션 지정으로 더 세밀한 제어가 가능
clickEvent.addEventListner('click', 이벤트 리스너, 버블링/캡처링)
이라고 하는데..버블링이랑 캡처링은 또 뭐지...!^.ㅠ
onclick vs addEventListner 참고문서
https://beforb.tistory.com/37
그래서 또 알아봤습니다..버블링과 캡처링..니..먼데
이걸 보고 나서야 아, 그래서 여태 e.preventDefault()를 썼던 거구나..! 라고 이제야 이해하게 되었습니다..^^ 오늘도 하나 배워간다!! 더 자세히 적기에는 본래 주제에서 많이 벗어날 듯 해 참고한 자료만 첨부하기로 하겠다.
e.preventDefault() 외에도 이벤트 전파를 방지하는 다른 메소드에 대해 설명되어 있는데, 이것도 굉장히 새롭고 흥미로웠다!!
참고문서
1. https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EB%B2%84%EB%B8%94%EB%A7%81-%EC%BA%A1%EC%B3%90%EB%A7%81#%EB%B2%84%EB%B8%94%EB%A7%81__%EC%BA%A1%EC%B2%98%EB%A7%81_%EC%9B%90%EB%A6%AC
2. https://ko.javascript.info/bubbling-and-capturing
질문 | 답변 | 설명 |
---|---|---|
closest() 는 이벤트 발생 지점(자신) 포함 부모 방향으로만 검색하는 게 맞아? | ✅ O | 형제나 자식 요소는 검색하지 않고, 부모 요소 중에서만 가장 가까운 요소를 찾는다. |
검색할 요소는 태그명, 클래스명, 아이디 모두 가능해? | ✅ O | closest("a") , closest(".menu-item") , closest("#myId") 모두 사용 가능하다. |
n번째 메뉴 글씨+아이콘을 클릭하면 해당 영역이 <a> 태그인데, <li> 영역과도 겹치지 않아? | ✅ O | <a> 태그는 <li> 내부에 있지만, 브라우저가 <a> 를 위쪽 레이어로 렌더링하기 때문에 시각적으로 겹쳐 보일 수 있다. |
그런데 왜 클릭 이벤트 발생 위치는 <a> 태그가 되는 거야? | ✅ O | 클릭 이벤트는 가장 깊은(안쪽) 요소부터 시작하므로, <a> 내부의 <span> 을 클릭하면 event.target 은 <span> 이 되고, 텍스트 부분을 클릭하면 event.target 은 <a> 가 된다. |
<li> 보다 <a> 가 나중에 렌더링되었기 때문인가? | ❌ X | 브라우저의 렌더링 순서는 관련 없음. 이벤트 발생 시 항상 가장 깊은 요소부터 감지되기 때문. |
부모와 자식 영역이 겹치는 경우, 이벤트 발생 시 event.target 은 무조건 자식 요소가 되는 거야? | ✅ O | 이벤트는 항상 가장 안쪽 요소부터 시작되므로 부모보다 자식 요소가 event.target 이 된다. |
이 중 내가 잘못 이해했던 X 항목에 대한 추가 설명
이것두 처음본다.. 사용법은 아래와 같다.
타겟요소.closest('검색할 요소')
타겟을 포함해 부모 요소 방향으로 가장 가까운 검색할 요소를 찾는 메소드라고 한다.(형제,자식방향으로는 X)
if (aTag.closest(".menu-item")) {
console.log("menu-item 클래스를 가진 부모 요소가 있음!");
} else {
console.log("해당하는 부모 요소가 없음!");
}
❗️ querySelectorAll은 배열(nodelist)을 리턴하고 개인적으로 선택하려면 forEach구문을 사용해야한다. 또한 nodeList를 Array로 만들려면 Array.from()을 사용해야 한다. 하지만 배열은 addEventListner를 추가할 수 없다.
참고문서
https://velog.io/@itssweetrain/JavaScript-event-delegation
습관처럼 사용해 왔던 건데도 개념을 잘 이해하지 못하고 있다는 생각에 정리함
preventDefault()
는 언제 사용하면 좋을까?상황 | 기본 동작 | preventDefault() 사용 시 |
---|---|---|
<a> 태그 클릭 | 페이지 이동 | 이동 방지 |
<form> 제출 | 새로고침 발생 | 새로고침 방지 |
<input type="checkbox"> 클릭 | 체크 상태 변경 | 체크 안 됨 |
키보드 단축키 (Ctrl + S ) | 페이지 저장 | 저장 기능 방지 |
파일 드래그 앤 드롭 | 파일 열림 | 파일 열림 방지 |
지난 popup component 작업을 하면서 add, remove 정도는 이전에 사용한 적이 있다는 걸 금방 떠올렸었는데, 이외의 다른 메서드들은 아예 처음 사용해보는 것도 있었다. 꽤 유용하게 사용할 수 있을 것 같아 정리함
// remove(), add() 로 여러 클래스를 한번에 처리 가능하다.
targetElement.classList.add("class1", "class2", "class3");
targetElement.classList.remove("class1", "class2", "class3");
console.log(targetElement.classList.item(0))
인수가 하나일 때
targetEelemt.classList.toggle('className');
인수가 두 개일 때
targetElement.classList.toggle('className', 조건);
targetElement.classList.contains('className');
// className이 targetElement의 클래스이름에 존재하면 true/없으면 false
// <div class="class1 old"></div>
targetElement.classList.replace("old", "new");
// <div class="class1 new"></div>