[React] accordion menu 만들기

바질·2022년 9월 16일
0

react-accordion

목록 보기
2/2

리액트로 아코디언 메뉴를 만들어보자.
바닐라 js로는 만들어본 적이 있지만 리액트로는 만들어본 적이 없어서 꽤 난황을 겪을 거 같다. 특히나 리액트는 DOM 선택에 자유롭지 않고 애니메이션에 취약한 거 같기 때문이다. 그럼, 시작~

계기 : 예전에 포토폴리오용으로 만들었던 웹사이트를 React로 다시 만들면서 시도해보았다.

사용 : React, styled-components, typescript

4개의 방법을 시도해보았다.

  • useRef 사용 (실패)
  • props 사용 (실패)
  • 개별적인 className 사용 (실패)
  • useRef 사용 및 map 미사용 (실패)
  • event.target 사용 (성공)

1. useRef 사용

const elRef = useRef(null);

const onShow=()=>{
	console.log(elRef)

}

return(
...

  {data?.map((project) => (
              <Work ref={elRef} onClick={onShow()} key={project.id}>
                <ContentsTitle key={project.title}>
                  <div>
                    <Date>{project.date}</Date>
                    <Title>{project.title}</Title>
                    <Produce>{project.work}</Produce>
                    <Etc>{project.etc}</Etc>
                  </div>
                </ContentsTitle>
                <ContentsImg></ContentsImg>
                <Over />
              </Work>
            ))}
 )

styled-components를 사용해 선언한 Work(div)에 useRef를 사용하여 선택했다. map을 사용해 div를 뿌리고, console.log를 찍어봤다. 결론적으로는 마지막 div만 선택하는 것을 확인했다. 따라서, 어디를 선택해도 마지막 div의 height 크기만 늘어났다.
(나는 이게 map을 사용해서 그런 것인 줄 알았지만 나중에야 아니란 걸 알았다. 지금 생각해보면 전혀 연관이 없는데...)

2. props 사용

styled-components에 props를 주어 값을 유동적으로 변경하는 것이다. 결론적으로는 실패했다. 사유는 어디를 선택해도 값은 동일하게 주어지기 때문에 모든 div의 height 값이 달라졌다. styled-components에 props를 사용하는 방법은 아래와 같다.
(참고로 typescript 사용 중)

const Work = styled.div<{click:boolean}>`
	...
    height:${(props)=> props.click ? "300px":"50px"}
    ...
`

2-1. 개별 className 사용

미리 css에 className을 정의해놓는다. 클릭하면 classList 처럼 height이 증가하고 줄어들게 설계해보았으나 이 역시도 실패했다. 개별적으로 className을 부여하는 것까지는 좋았으나 클릭하기도 전에 height 속성이 적용되어 있고, 또 열기와 닫기의 상태변수를 알 수 없었다.

(구글링 해보니 해당 방법과 유사한 방법이 존재했다. 이 경우에는 index와 clicked를 사용하여 컨트롤한 것인데, 해당 방법을 알기 전에 이미 해결해버린 상태...)

이쯤에서 나와 같이 리액트로 아코디언 메뉴를 만들어본 사람이 있지 않을까 하여 구글링 해보았다.
블로그를 참고하여 다시 1번의 방법으로 시도.

3. useRef 사용 및 map 미사용

여기서 map 사용을 포기했다. 데이터가 많을 때나 수정하기는 용이하지만 지금 상황에서 데이터는 많지 않다. 그리고 map 사용이 문제가 되는지 테스트하기 위함이라는 이유도 있다.

따라서 개별적으로 styled-component를 사용해 시도해보았으나 1과 같은 상황에 놓였다. styled-component도 사용 방법은 class와 비슷하다고 생각했는데 혹시 이것 때문은 아니었을까? class는 함수를 재사용하기 위해 사용하는 것이니 같은 객체라 보는 것은 아닌가. 해서 다르게 시도해보았으나 실패.
useRef는 여전히 마지막 li만을 잡고 있었다.
(map 사용의 문제가 아니었다.)

근본적인 문제가 무엇일까 생각해보았다. 해당 블로그에서 활용하는 코드의 구조를 보면 내가 작성한 구조와 달랐다. 그래서 해당 방법을 포기하고 다시 구글링을 시도했다. 앞서 언급한 index와 clicked을 이용했는데 블로그를 보다보니 다른 방법으로 시도할 수 있을 거 같아 내 방식대로 작성했다.

4. event.target 사용

이번에는 아예 onClick을 사용하여 event.target을 잡아보았다. 이렇게 잡으니 확실히 선택하는 div를 console에 찍을 수 있었다.

const [click, setClick] = useState(false);

 const onShow = (e: any) => {
    console.log(e.target.parentElement.offsetHeight, click);
    if (click) {
      setClick(false);
      e.target.parentElement.style.height = "50px";
    } else if (!click) {
      setClick(true);
      e.target.parentElement.style.height = "500px";
    }
  };
return(
  {data?.map((project) => (
              <Work onClick={(e) => onShow(e)} key={project.id}>
                <ContentsTitle key={project.title}>
                  <div>
                    <Date>{project.date}</Date>
                    <Title>{project.title}</Title>
                    <Produce>{project.work}</Produce>
                    <Etc>{project.etc}</Etc>
                  </div>
                </ContentsTitle>
                <ContentsImg key={`${project.title}-image`}></ContentsImg>
                <Over />
              </Work>
            ))}

)

해당 방법으로 시도하니 개별적으로 height의 크기가 늘어났다 줄어들었다. 꽤 성공적인 수확이다. typescript를 사용하고 있어서 event의 타입을 any라고 작성하여 오류를 없앴지만 나중에 수정해줄 것이다.
(코드에 event를 e로 줄여서 썼는데 이건 안 좋은 코드 작성법이다...)

고치고 싶은 점

지금 상태의 아코디언 메뉴는 선택했을 때 개별적으로 늘어나지만, 하나의 메뉴를 연 상태로 다른 메뉴를 클릭하면 원래 선택했던 메뉴는 닫고 클릭했던 메뉴를 열고 싶다. 이건 나중에 차차 고쳐나가보자.

0개의 댓글