context-menu

js·2022년 5월 12일
0

자바스크립트

내 답

반복문의 사용을 줄이기 위해 최대한 이벤트 위임을 하였고 답안지에 비해서 forEach문을 하나 덜 사용하였지만 이벤트 핸들러를 하나 더 할당 하였다.. ~코드가 길어졌는데 이게 과연 좋다고 할 수 있나? ㅠㅠ ~
이벤트 위임이란 개념을 최근에 배웠고 무조건 반복문은 안좋으니 무지성으로 반복문에 대한 혐오를 가진것 같다. 반복문을 쓸 때 쓰는게 오히려 최적화에는 더 도움이 될 수 도 있다는 것을 깨달았다 ..

details p.active {
  display: block;
}
const wrapperEl = document.querySelector('.wrapper');
let selected;
wrapperEl.addEventListener('click', e => {
	if (e.target.nodeName!=='SUMMARY' && e.target.nodeName!=='P') return;
	const pEl = e.target.parentNode.querySelector('p');
	// 클릭한 대상이 팝업이 오픈되었으면 종료
	if (pEl===selected) {
		selected.classList.remove('active');
		selected=null;
		return;
	}
	// 클릭한 대상이 팝업이 오픈되지 않았으면 
	if (selected) {
		selected.classList.remove('active');
	}
	pEl.classList.add('active');
	selected = pEl;
});
const pEls = wrapperEl.querySelectorAll('p');
document.body.addEventListener('click', e => {
	// 클릭한 대상이 details가 속한 .wrapper 요소라면 종료
	if(e.target.closest('.wrapper')) return;
	// 모든 p 요소의 active 클래스 제거 => popup 종료 
	pEls.forEach(pEl => pEl.classList.remove('active'));
});

해설

팝업창을 toggle 하는 것은 details 요소의 open 속성으로 처리하므로 open 속성을 언제 제거 해야 되는지만 로직으로 구현하면 된다

details[open] p {
  display: block;
}
const items = document.querySelectorAll('details');
document.body.addEventListener('click', function(e) {
  // p 요소가 아니고 summary 요소가 아니면 popup 창 종료해라 
  if (e.target.nodeName !== 'P' && e.target.nodeName !== 'SUMMARY') {
    items.forEach(function(item) {
      item.removeAttribute('open');
    });
    return;
  }
  items.forEach(function(item) {
    // 클릭한 대상이 details 요소의 자식(summary, p)이 아니라면 popup 창 종료해라
    if (item !== e.target.parentElement) {
      item.removeAttribute('open');
    }
  });
});

리액트 & 리액트 포탈

리액트는 해설과 일치, 리액트 포탈은 아예 못 품 ..

새로 알게 된 것

details 요소의 open 속성

details 요소에는 기본 속성으로 open 이 있어서 (기본값 false, 명시하면 true)

굳이 details 요소에 js로 class를 동적으로 추가 하지 않아도 된다.

기본적으로 details 요소를 클릭하면 open 속성이 toggle 된다.

html 문법에서 기본적으로 open 속성으로 details 요소의 하위 요소를 보여 주고 감추는 기능을 제공한다. (popup이 하위요소이다. 어떠한 js 코드도 없다.)

createPortal(요소, 해당 위치)

요소해당 위치 아래에 렌더링 한다. 외우자.

<ContextPortal target={detailRefs.current[openedIndex]}
  children={<p>{dummyData[openedIndex]?.context}</p>} />

detailRefs.current[openedIndex]

=> popup이 오픈 되어있는 details 요소,

<p>{dummyData[openedIndex]?.context}</p>

=> 오픈 되어있는 details 요소의 내용(context)이 들어있는 p 요소

p 요소를 details 요소 아래에 렌더링 한다.

element.tagNameelement.nodeName

element.tagNameelement.nodeName은 말그대로 요소의 이름과 노드의 이름을 받아오는 속성이다. 둘다 요소를 가리키면 대문자를 리턴한다.

둘의 차이점으로, element.nodeName의 경우 노드를 가리키면 해당 노드를 리턴한다. element.tagName은 요소가 아닌경우 undefined를 리턴한다.

console.log(document.querySelector('li').tagName); // LI
console.log(document.querySelector('li').nodeName); // LI
console.log(document.querySelector('li #select').tagName); // undefined
console.log(document.querySelector('li #select').tagName); // #select

자식 컴포넌트에 이벤트 핸들러 props로 건네주기

자식 컴포넌트의 summary 요소의 onClick 핸들러를 부모 컴포넌트인 App.jsx에서

props로 넘겨줘서 App.jsx에서 해당 핸들러(togglePopover) 로직을 작성 할 수 있다.

핸들러는 매개변수 index를 받기 위해 const handler = index => e => {};와 같은 구조로 되어 있다.

onClick 핸들러

const togglePopover = index => e => {
    e.preventDefault();
    e.stopPropagation();
    setOpen(e.target.parentElement.open ? null : index);
};

부모 컴포넌트: App.jsx

<Detail onToggle={togglePopover(i)}/>

자식 컴포넌트: Detail.jsx

<details open={open} ref={ref}>
 <summary onClick={onToggle}>{text}</summary>
</details>

0개의 댓글