<윈터레스트 프로젝트를 마친 뒤 다시 볼아보는 회고록 2부 >

강민수·2022년 2월 5일
0

회고록

목록 보기
8/11
post-thumbnail

1부에 예고드렸듯이 이번에는 바로 필자가 작성했던 코드 중에 기억에 남는 코드들을 토대로 포문을 열겠다.

💻 1. 기억에 남는 코드

이번 프로젝트에서 필자는 프론트엔드만 전담하였으므로 해당 코드들로 한 번 구성해 봤다.

🧱 1) masonry layout 코드

1. 첫 인상

핀터레스트 구조를 지켜보면서 가장 처음에 난해했던, ui 구상이 바로 이 masonry. 일명 벽돌 구조 ui.

처음보는 ui였고, 아직 어떻게 구성을 해서 코드를 짜야 하는 지에 대한 것이 전혀 떠오르지 않았다.

물론 조급함이 앞서기는 했지만, 그럴 때 일수록 천천히 가는 것이 오히려 제대로 갈 수 있는 방법이라는 생각과 함께 구글링을 시작했다.

2. 무조건 공식 문서부터 Go!

구글링을 통해 이 구조가 핀터레스트 ui로 불리며 아주 유명한 구조라는 것을 알았다. 심지어는 mdn에 떡하니 설명이 잘 되어 있는 것이 아닌가...

그래서 바로 하루 동안 예제를 만들어 보면서 직접 한 번 사용법을 익혀 봤다.

이후에 누군가 만든 예제와 동영상을 익히면서 계속 만들어 봤다. 그러다 문득, 예전에 생활코딩님께서 만들어 두신 영상을 보게 되었다. 그때. 와~ 이렇게 간단하게 만들 수도 있구나라는 것을 깨달았다.

3. 첫 시작은 쉬운 적용부터.

1) column- count 적용

그래서 사실상 이번에는 그리드로 적용하기보다는 처음에는 column-count라는 css 속성 값을 이용해 봤다.


현재 맵 함수를 도는 카드 컴포넌트 전체를 감싸는 전체 골격 구조인 컨테이너에 컬럼 카운트 css 속성을 부여했다.

구글링을 통해 검색한 결과,column-count 속성은 해당 요소를 몇 개의 칼럼(column)으로 나눌지를 설정한다.

그래서 이 코드를 통해서 일단 다중 칼럼 구조를 만들었다. 후술하겠지만, 이후에 이 부분은 문제가 생겨서 바꾸긴 해야만 했다.

2) display: inline-block, position과 width 조절.

이후에는 이제 안에 맵을 돌게 되는 개별적인 카드 컴포넌트에 속성 값을 변경해 줘야만 했다.

이유는 저렇게 칼럼 카운트만 해주게 되면, 밑에 적히는 글자들이 다른 컬럼으로 넘어가는 문제가 발생하고 만다.

그래서 이 부분은 이렇게 아래와 같이 바꿔주면 해결이 되었다.

먼저, 이미지를 감싸는 전체 디브를 하나 만들고, 그 디브를 또 감싸는 피규어라는 컨테이너를 또 만들었다. 그런데 여기서 핵심은 디스플레이: 인라인 블럭이다. 이를 통해, 이미지 파일 밑에 적힌 글자들이 다음 칼럼으로 넘어가지 않도록 조절이 가능하다.

그리고 또, 보면 이미지들이 뭔가 한 쪽으로 쏠리거나 맞지 않는 형태가 된다. 이것 역시 기본적인 포지션 속성과 위드 값을 통해 변경이 가능하다. 필자는 이미지를 감싸는 컨테이너에는 relative를 주고 그 안에 이미지에는 앱솔루트를 주었고, 그리고 이미지 값이 그 컨테이너 안에 딱 박혀서 크기 조절이 되도록 width: 100%를 줬다.

그러면 다음과 같이 모든 이미지들이 다중 컬럼이자, 마조너리 형태로 잘 바뀐 것을 확인할 수 있다.

3) BUT.

그런데 필자가 언급했듯이 다중 칼럼 속성에서 column-count에는 필연적인 약점이 존재했다. 그건 바로, 어떤 화면 크기이든 간에 칼럼의 값이 동일하다는 것이다.

이게 무슨 말이냐면,

다음과 같이 칼럼이 고정되다 보니, 디스플레이의 반응형 구현 시에 문제가 생긴다. 그래서 등장한 개념이.
column-width 다! 이 속성을 통해 필자는 column-width를 다음과 같이 변경했다.

그랬더니, 바로 반응형 역시 잘 반응했다. 원리적으로 살펴보면, 컬럼 카운트는 이미지의 width 값을 신경쓰지 않고 그냥 무조건 적인 칼럼의 숫자만 통일시키는 반면, column-width는 애초에 각 이미지 별로 width 값을 300px로 설정해 줬다. 이를 통해 브라우저 뷰포인트 상에서 width 값에 따라 커지면 더 칼럼이 많아지고, 아니면 더 컴럼의 숫자가 더 적어진다.

이번 이 masonry 레이아웃을 통해 필자는 많은 것을 느꼈다. 특히
" 1.어려워 보이는 것 같아도 사실상 모든 기술은 기본적인 원리부터 접근하면 어렵지 않게 해결할 수 있다!"
" 2. 추가적으로 어떤 기능에 대한 한계를 보완하는 기술은 생겨난다. 보완하는 기술이 생겨난 이유와 원리에 대해 좀 더 고민해 보고 직접 해결해 보는 것 역시 좋은 공부다!"

ps. 이 코드도 사실상 리팩토링을 하면서 문제가 있음을 발견하고 현재는 수정을 한 상태다. 이에 대해서는 리팩토링 페이징에서 확인 바란다.

∞ 2) 무한 스크롤 구현 코드

드디어~ 이 무한 스크롤의 염원을 이번 프로젝트에서 풀 수 있었다. 사실 필자는 지난 프로젝트인 위얼네버댓 이커머스 사이트에서 무한 스크롤링 기능을 추가하고 싶었다. 하지만, 그때는 워낙 공부량도 부족했고, 남의 코드를 따라 치는 수준이라... 구현하는 것을 포기했었다. 그리고 절치부심한 끝에 이번에는 아직 많이 부족하지만 작동하는 원리에 대해서는 일정 정도 깨우친 것 같다. 그래서 이 코드에 더욱 애정이 간다.

1) 어떤 방식으로 할 지에 대한 고민.

무한스크롤 공부를 하면서 항상 고민하는 부분이 있었다. 왜냐면, 무한스크롤은 페이지네이션, 스크롤, intersection observer 등 방식이 다양했기에.
그래서 필자는 고민이 컸다. 공부해 본 바에 따르면, 각 방식마다 장단점이 있고, 무조건 이것만 써야 한다는 법은 없었다. 물론 처음에는 기왕이면 다홍치마라고, 더 진보되고 좋은 방식을 쓰는 것이 좋겠다는 생각이었다.

그러나....

사실은 그건 모든 방식을 적용해 보고 실제 해 보지 않았으면 몰랐다. 그리고 실제로 써보고 사용자가 직접 그 차이에 대해 확실하게 느껴야만 다음에 다시 사용할 일이 생기더라도 사용에 대한 명확한 판단 기준이 생길 것이라고 판단되었다. 결국 필자는 먼저, 무한스크롤을 한 번 페이지네이션 방식과 intersection observer를 합친 방식으로 해보는 것이 좋겠다는 생각이 들었다.

2) 어떻게 합칠 것인가에 대한 구상.

사실 상 하루 넘는 시간 동안, 어떻게하면 저렇게 합쳐진 방식을 만들 수가 있는 것인지 계속 구글링을 하면서 찾아만 봤던 것 같다. 그러다 문득 이런 생각이 들었다. 일단, 페이지 넘버라는 변수를 통해 api 상에서 받아올 데이터의 리밋을 정해 놓는 것이다. 그리고 나서 페이지 넘버가 늘어날 때마다 데이터가 추가될 수 있도록 함수 로직을 짠다. 이때, 이 넘버가 늘어나는 방식에서 감지 장치는 바로 intesection observer의 제어가 좋겠다는 생각이 굳혀졌다.

3) 코드 구현.

1) 유즈 스테이트 값 설정.


먼저, 로직을 작성하기 이전에, 유즈 스테이트를 통해 상태 값과 초기 값 설정부터 시작했다. 여기서 윈리스트는 무한 스크롤 할 때 설정해 둔 값으로, 리스트에 카드 컴포넌트가 맵을 돌 때 사용되는 스테이트 값이다. 페이지넘버는 아까 설명했던 api 상에서 데이터 리밋을 설정해 줄 수 있는 페이지 넘버라는 변수다. 이때 1부터 초기 값이 나와야 처음 데이터부터 나오기 때문에 1로 초기 값을 설정해 줬다. 태그는 이후에 후술하겠지만, 서칭 기능에 추가적으로 필요한 변수다. 로딩의 경우에는 intersection observer의 조건에 해당하는 변수라고 생각하면 되겠다. 이렇게 총 4개의 스테이트문을 통해 변수와 초기값 설정이 끝났다.

2) api 패치 함수& 유즈 이펙트 + 콜백 함수 설정.


이제 변수 설정이 끝났으면 본격적으로 패치 함수를 작성해 줘야 한다. 여기서 패치할 때, 기본적으로 주소 내에 페이지 넘버를 넣어줬다. 이로 인해, 백엔드 담당자에게도 이렇게 api 단위를 잘라서 데이터를 분할 되게 넣어 줬으면 좋겠다고 요청했다. 이후 데이타 부분을 처리하는 데 있어서 셋 로딩이라는 조건이 트루가 되면 다시 카드 컴포넌트의 데이터를 추가하도록 코드를 짰다.

다음 과정으로는 필자는 위의 패치 함수를 async ~ await 구조로 짰기 때문에, 유즈 이펙트가 필요했다. 페이지 넘버가 늘어날 때만 패치윈스라는패치 과정이 실행되도록 조건을 걸어줬다.

여기에 추가로 loadMore은 콜백함수라고 보면 된다. 데이터가 늘어나기 위해서 페이지 넘버가 하나 씩 증가되어야 하는 것이 무한 스크롤의 원리이기에, 페이지 넘버가 이전 페이지에서 하나씩 늘어나도록 하는 함수를 생성했다.

3) useRef 설정 + intersection observer사용을 위한 useEffect.

useref를 이용하여 해당 페이지 끝에 참조점을 잡아둔다.
다음 단계로,다시 이 fetchwins 함수에서 loading 값이 true로 바뀐다면, new 생성자로 IntersectionObserver 객체를 활용해서 observer를 생성한다.

여기서 조건을 하나 더 걸어줘야만 한다. 즉, entries를 인자로 받는 콜백함수에서 인스턴스의 배열의 첫번째 값이 관찰 대상의 교차 상태가 true라면, 다시 앞 전에 설정해 둔 loadMore 함수를 실행한다. 그 이외의 경우에는 다시 observer.unobserve[pageEnd.current]를 써서 관찰을 중단시키게 한다.

threshold는 옵저버가 실행되기 위해 타겟의 가시성이 얼마나 필요한지 백분율로 표시한다. 필자는 1로 설정하여 100%일 때 옵저버 실행하도록 만들었다. 그리고 다시 괄찰 대상을 등록하여 지속적인 감지를 해 놓았다. 맨 끝에는 loading의 상태를 뎁스로 넣어서 로딩의 상태 값이 변화할 때마다 실행되도돌 처리했다.

4) 마지막으로 ref할 div 설정.


이제 마지막으로 일전에 말한, 참조할 div를 설정해줘야만 한다. 여기서 필자는 로딩이라는 로딩바를 하나 만들어서 그 안에 컨테이너 안에 이 참조점을 넣었다. 그 이유는 렌더링의 가장 끝 부분에 해당 참조점을 넣음으로서 결국 교차 지점에 해당 지점이 닿으면 데이터가 불러와 지도록 하기 위한 의도때문이다.

4. 예외사항.

이제 코드 작성은 끝났다. 그래서 바로 테스트로 unsplash라는 무료 api사이트를 통해 한 번 돌려봤다. 너무나도 순조롭게 예상한 대로 무한스크롤이 잘 실행되는 것이 아닌가?

속으로 쾌재를 불렀다. 그런데 반은 의심이 갔다... 정말 이렇게 잘 될까?

1) 변수 발생.

역시 변수는 많았다. 일단 이렇게 코드를 작성하고, 우리 팀의 사이트 백엔드와 연결을 통해 확인했다. 그때가 바로 1차 장벽이었다. 하지만 생각보다 그 변수는 빨리 붙일 수가 있었다. 허나 제일 큰 변수는 이후부터 계속 나타난다.

2) 서치 기능의 추가.

사실상 기존 무한 스크롤만 있는 경우만 생각했던 필자의 실수이기도 한데... 이게 서치 기능이 추가가 되면서 api가 바뀌었고, 그에 따라 기존의 코드 수정이 불가피해졌다.

일단, 첫 번째 문제는 서치가 제대로 먹히질 않았다. 일단, 태그라는 우리 사이트의 고유한 분류 체계로 인해 api 주소부터 바꿨다.
바로 다음과 같이, tagName을 지정한 api가 추가적으로 필요했따.

이 태그네임에 따라 데이터가 나뉜다고 백엔드의 설명을 들었다. 만약 태그 네임이 undefined이면, 전체 윈이라는 카드 목록 자체가 다 출력되고 그 태그가 있다면 해당 태그에 해당하는 데이터만 출력되는 구조였다.

혹시... 일전에 유즈 스테이트 값들 중에서 tag기억하는가? 그 tag가 바로 이 태그였다. 그래서 이때 해당 스테이트를 추가해 준 것이다. 그리고 나서 바로 해 봤지만, 여전히 데이터는 제대로 출력되지 않았다.

3) 아직도 여전히 읽지 못하는 서치 기능.

이렇게 태그만 넣는다고 해서 달라지는 것은 없었다. 그래서 일단 콘솔을 tag를 찍어보니, undefined라고 결과 값이 나왔다. 따라서 이 tag를 직접적으로 api에 넣어줄 수 있는 방법을 구현해야 했다. 그 방법이 무엇인가에 대해 백엔드 분들과 문제를 풀어보면서 논의해 봤다.

그 결과 const location = decodeURI(useLocation().search); 이 부분을 해줘야만 했다.

그 이유는 다음과 같다.

  1. 먼저, path 값을 받아오려면, 이번에 알게되었는데 useLocation을 써야만 한다고 동료들이 알려줬다. 그래서 그걸 콘솔을 찍어보니, 현재 api에 어떤 것들이 들어가는 지 알 수 있었다.

    이를 통해 현재 search 부분을 활용해야 한다는 것을 알 수 있었다.

  2. useLocation().search 이제 여기서 search만 빼내야 하기에, 객체 접근을 쓴다. 바로 그러면 콘솔에 다음과 같이, 찍힌다.


보시는 바와 같이, 이제 드디어 tagName 부분이 보인다.

3.그런데 여기서 보면, 이상한 난수들이 찍힌다. 이건 필자가 한글로 검색을 했을 때 생기는 문제였다. 그래서 이건 decodeURI로 감싸줘야만 해당 부분을 해소할 수 있었다. 이유는 이게 기본적으로 html은 한글로 주소 작성을 하면 기본적으로 오작동을 발생할 수 있다고 한다. 따라서 encode화 시켜준다. 따라서 이것을 다시 깨지지 않고 사용하기 위해서는 디코드화 시켜줘야 한다.

그러면 이제 한글로 제대로 tagname이 나온다. 이제 태그를 셋 스테이트로 초기값에서 바꿔줘야 한다.

  1. useEffect 사용.

  2. tagName 빼오기.
    유즈이펙트로 태그를 설정해 주고 나서는 다시 몇 가지 처리를 해 주면된다.

const tags = tag.split('=');
  const tagName = tags[1];

  const fetchWins = async (pageNumber) => {
    const res = await fetch(
      `${process.env.REACT_APP_SERVER_HOST}/win?tagName=${tagName}&pagenumber=${pageNumber}`,

다시 현재 tag는 결국 tagName만 빼올 수 없기에, tag를 스플릿 처리를 해줬다. tagName이 '=' 기준으로 나뉘기 때문에, 스플릿 처리를 해주면 배열이 생긴다. 그 배열의 1번째 값이 바로 우리가 원하는 tagName의 값이 므로 저렇게 처리했다. 이후에는 다시 api 역시 tagName으로 바꿔줬다.

  1. 서치 추가 문제.
    그렇게 잘 되는 듯 해 보였지만, 문제가 완전히 해결된 것은 아니었다. 서치 자체가 먹힐 때가 있는 것이 너무 불안정했다. 문제는 바로 태그를 검색하고 엔터를 누르면 한 박자 늦게 해당 태그가 입력이 되는 문제였다.

그래서 이게 뭐가 문제인지 데이터 콘솔을 찍어 봤다. 그렇게 몇 시간을 또, 이 문제로 팀원들과 부딪혀서 씨름했다. 이때 진짜 너무나도 감사하게 다들 내일 같이 풀어줘서 고마웠다. ㅎㅎ 그러다 문득 백엔드 중 한 분이 다시 아래와 같이 풀었다고 공유해 주셨다.

마치 코드 타카를 하는 기분처럼 짜릿했다. 즉, 풀이법은 아까 태그를 설정한 유즈이펙트에 초기 카드 값들을 다시 설정해서 검색할 때마다 초기화 되도록 하는 것이었다. 근본적으로 서치할 때마다 이게 초기화가 안 되어서 발생한 문제였다. 이제 서치 기능은 별 문제가 없이 잘 되었다.

4) 데이터 제한 기능 추가.

말도 많고 탈도 많았던 무한 스크롤. 이제 거의 다 완성된 것처럼 방심하고 있었다. 그런데... 갑자기 디버깅을 하는 시점이 도래했다.

민수님! 이거 저희가 테스트 해 봤는데, 스크롤을 안 내려도 뭔가 페이지가 계속 넘어가네요...

아! 또 뭔가 코드에 문제가 생긴 것이다. 다시 살펴보니 코드 자체에 observer에 조건 식을 제대로 안 넣어줘서 생긴 문제였다. 따라서 다음과 같이 수정했다.

교차할 경우에만 옵저버 관찰을 설정하고, 그 이외에는 중지하는 unobserve를 넣어줬다. 그리고 다시 옵저버 설정을 끝에 추가해 줬다.

4. 마지막 점검 + 총평.

그리고 잘 되는 가 싶었다. 하지만..... 최종 발표 당일...

데이터를 넣다가 팀원들이 갑자기...

민수님.... 이거 이제는 무한 스크롤링이 안 되네요 ㅜㅜ
또 문제가 생겨버렸다...

정말 눈물이 날 정도로 무한 스크롤은 나를 애 먹였다.

아... 진짜... 도무지 영문을 모르겠었다. 그래서 일단, 이건 리팩토링 기간에 고치기로 마음먹고, 일단 구현을 하긴 해야하니... 임시 방편을 썼다.

그건 바로 그냥 일반 페이지 네이션 방식이다. 아... 물론 클릭 형식으로 만들지는 않았다. 구현 코드는 다음과 같다.

설명을 덧 붙이자면, 화면 제일 하단에 gif 눈꽃 모양의 아이콘에 마우스를 지나치면 로드 모어 함수를 불러와서 페이지를 수동으로 넘기게 금 한 방식이다.

그래서 총평을 하자면, 아직 무한스크롤 중에서 intersection observer에 대해 완벽하게 이해가 안 이루어 진 것 같다. 아마 더 공부를 해보고 연구를 좀 더 해야할 것 같다. 지금의 페이지 네이션 방식은 너무 사용자의 ux에 불편한 경험을 주는 것 같아 리팩토링 기간에 변경은 할 것 이다. 이번 경험을 통해 프론트 엔드 개발자는 다양한 상황과 조건을 고려해야 하고, 특히 유저의 사용패턴에 따라 어떤 기술을 적용할 지에 대한 고민도 함께 이뤄져야 한다는 깊은 이해를 하게 되었다.

ps. 필자가 이후 수정 코드는 리팩토링 페이지에 어떻게 바꿨는 지에 대해 올려 놓았으니, 리팩토링 페이지를 참조 바란다.

🪟 3) 모달창 코드

사실 이번 프로젝트를 하면서 모달창에 대해서 실전적으로 연습을 정말 많이 해 봤다고 생각한다. 특히, 처음에는 몰랐지만, sns사이트의 경우 모달을 활용하는 경우가 굉장히 많았다. 회원가입부터, 로그인, 다양한 삭제, 수정까지.

1. 모달에 대한 동작 원리부터 읽혀라!

부끄러운 얘기지만, 이전 프로젝트나 공부를 하면서 기본적인 모달창도 제대로 만들어보지 않았었다. 그러다 보니, 사실 처음부터 어떻게 만들어야 하는 것인지에 대한 구상이 안 떠올랐다. 먼저 모달이 뭔지, 모달에 대한 동작 원리를 읽히는 것부터 시작했다.

구글링을 통해 다양한 모달 구현 방법을 찾아봤다. 물론, 쉽게가려면 기본적으로 다른 개발자가 만들어 놓은 부트 스트랩과 같은 모달창을 그대로 이용할 수도 있었다. 하지만, 그렇게 되면 코드의 구조도 모른 채로 이용만 하는 것이기에... 그런 방식으로는 하지 않았다. 또, 계속 공부해 보니 그렇게 구현하는 것이 어렵지도 않았다.

1) 모달창의 원리 구조.

그렇게 다른 사람들이 만든 구조를 살펴보니, 공통적인 것들이 있었다.
1. 모달은 기본적으로 처음에는 드러나지 않은 구조체여야만 한다.
2. 이후, 사용자가 버튼을 클릭하는 등 사용자의 상호작용에 따라서 창이 나타난다.
3. 다시 사용자의 추가 이벤트를 통해, 모달창은 꺼진다.

요약하자면, 꺼져있다가 켜졌다가 또 다시 꺼지는 기존의 상태로 돌아가는 구조.

2) 스테이트로 관리하자!

1. 모달이 작용할 컴포넌트에서 스테이트와 함수의 설정

그래서 결국 그런 상태 관리에 적격인 것은 뭐다? 스테이트! 그래서 바로 필자는 리액트에서는 유즈 스테이트를 통해 모달 창을 관리할 수 있겠다는 생각에 도달했다. 그래서 스테이트를 통해 다음과 같이 코드를 작성해 봤다.

먼저 우리 홈페이지 구조 상, 홈 랜딩 페이지에서 로그인과 회원가입 버튼이 네브바 상단에 있었다. 그래서 네브바의 로그인, 사인업 버튼을 눌렀을 때 모달창이 나오는 방식으로 구현해야 했다.

<과정>
1. 모달 오픈이라는 스테이트를 선언하고, 초기값을 false로 해서 실행되지 않게 만든다.
2. 체인지 폼은 후술하겠지만, 우리 사이트의 경우, 로그인 모달화면에서 회원 가입 유도용 글씨를 누르면 바로 회원가입 모달창으로 화면 전환이 되어야만 했다. 그래서 이와 같이 체인지 폼이라는 스테이트로 상태를 추가적으로 관리해 줘야만 했다.
3. 다음은 이제 버튼을 눌렀을 때, 실행될 함수들이다. 처음의 openModal은 타겟되는 버튼의 네임에 따라 모달 창의 내용이 다르게 띄워주기 위한 조건식과 더불어 모달 창의 초기 값인 false를 true로 바꿔주게 설정했다.
4. 다음 클로즈 모달 함수는 다시 모달의 초기값을 false로 바꿔서 모달 창 자체를 끄도록 한 함수다.
5. 마지막 트랜스 폼 함수는 전술한 바와 같이, 이건 로그인 창 내에서 사인 업 양식으로 모달 창 양식을 바꿔줘야 하기 때문에 설정한 함수다.


이 부분은 결국 버튼들의 온클릭시에 위에서 설명한, 함수들이 작용될 수 있도록 넣어줬다.
다음은 모달 창의 내부 구조인데, 이건 뒤에서 후술할 부분과 겹쳐 있으므로, 다시 설명드리겠다. 간단히만 설명하자면, 모달의 구조에서 일단, open={modalOpen}, close= {closeModal}을 프롭스로 모달 컴포넌트에 전달해 줘야만 한다. 이유는 그렇게 전달한 프롭스들을 통해, 결국 모달 컴포넌트가 작동하기 때문이다.

2. 모달 컴포넌트의 구조.

모달 컴포넌트의 구조는 다음과 같다.

엄청 간단하다고 생각하실 수 있다. 맞는 말이다. 엄청 간단하다! 다만, 필자는 원래 css를 스타일 컴포넌트로 작성하려 했지만, 아직 삼항연산자를 적용한 스타일 컴포넌트 문법이 미숙하여 추후 리팩토링 시에 해당 부분을 스타일 컴포넌트화 시킬 예정이다.

<구조>
1. 일단, 아까 설명한 프롭스들을 다 받아온다. 오픈, 클로즈. 이렇게 2개를 받아온다. 아직 칠드런에 대해서는 언급하지 않겠다.
2. 두 프롭스들이 쓰이는 역할이 약간 다르다. 오픈은 말 그대로 상태 값에 따라, 전체 div 컨테이너의 클래스 명이 바뀌는 삼항 연산자의 작용체다. 또한, 참일때 섹션 태그의 내용 역시 삼항 연산자로서 모달 창 안에 내용이 채워지게 만들어준다.
3. 클로즈는 저기 보이는 버튼이 바로 모달창을 닫는 클로즈 버튼이다. 따라서 그것을 클릭할 경우, 모달창을 닫도록 클로즈를 넣어준 것이다.

3. 모달 컴포넌트 css 구조.

// 모달창이 닫혔을 때 실행되는 구조로서 디스플레이 논으로 처리하여 보이지 않게 한다. 
.modal {
  display: none;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 99; 
  background-color: rgba(0, 0, 0, 0.6); // 이건 모달 창이 등장하면서 백 그라운드 컬러를 적당히 흐릿한 상태로 해줘야 해서 (0,0,0,0)인 검은색 rgb가 아닌 0.6 정도의 진하기를 가진 검은색을 통해 투명도 조절을 처리해 줬다. 
}
.modal button {
  outline: none;
  cursor: pointer;
  border: 0;
}
.modal > section {
  width: 90%;
  max-width: 450px;
  margin: 0 auto;
  border-radius: 0.3rem;
  background-color: #fff;
  animation: modal-show 0.3s;
  overflow: hidden;
}

.modal > section > button {
  position: relative;
  top: 10px;
  right: -400px;
  width: 30px;
  font-size: 40px;
  font-weight: 700;
  text-align: center;
  color: black;
  background-color: transparent;
  z-index: 990; // 여기서 z-index를 990으로 준 이유는 이미 기본 모달이 z-index를 99를 가지고 있는 상태라서 그냥 임의적으로 그것보다 높게 설정해 준 것 뿐이다. 
}

.modal.openModal {
  display: flex;// 오픈 모달로 클래스명이 바뀌면 바로 디스플레이 논에서 플렉스로 바뀌면서 모달창이 등장하는 것이다. 
  align-items: center;
  animation: modal-bg-show 0.3s;// 그에 따라 모달의 백그라운드가 스무스하게 등장하도록 애니메이션을 지정해 뒀다. 
}

@keyframes modal-show {
  from {// 기본적으로 이번에 모달 공부를 하면서 이 애니메이션을 넣어주는 것 역시 공부했다. 물론 필수는 아니지만, 안 넣으면, 허전해 지고 약간 끊기는 느낌이 든다. 그래서 추가적으로 오패시티를 통해 약간 자연스러운 창이 띄워지는 느낌을 줬고, 마진 값을 통해 위에서 아래로 모달창이 떨어지는 느낌도 줘 봤다.  
    opacity: 0;
    margin-top: -50px;
  }
  to {
    opacity: 1;
    margin-top: 0;
  }
}
@keyframes modal-bg-show {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

2. 모달을 컴포넌트 화 시키는 것이 맞지 않을까?

여기서 하나 만들고 나니 의문점이 또 들었다. 바로. 모달창은 컴포넌트 화 시켜서 분명 그 밖의 틀은 그대로 유지하되, 안의 내용만 깔아 끼우는 것이 가능할 것 같은데... 그 방법을 알지 못 했다.

그래서 이 부분에 대해 한 번 멘토 재준님께 이 고민을 털어 놓았다.

네! 물론이죠~ 모달 창은 구조만 동일하면 안의 내용만 깔아끼우면 되도록 컴포넌트 화 시킬 수 있는 게 가장 베스트입니다!

그리고나서 그는 내게 이것을 하나 가르쳐 줬다. 기억 나시는가? 바로 children! 이게 바로 그 핵심 key! 안의 내용을 children을 통해 해결할 수가 있었다.

function Modal({ open, close, children }) {
  return (
    <div className={open ? 'openModal modal' : 'modal'}>
      {open ? (
        <section>
          <button className="close" onClick={close}>
            &times;
          </button>
          {children} // 이 children이 바로 흔히 말하는 그 섹션 안에 내용을 바꿔주는 작용법이었다. 마치 자식 컴포넌트를 끼워 넣어주는 것 같은 기법이다.
        </section>
      ) : null}
    </div>
  );
}
      <Modal open={modalOpen} close={closeModal}>
        {changeForm === 'signIn' && <SignIn change={transForm} />}
        {changeForm === 'signUp' && <SignUp />}
      </Modal>

다시 네브바 컴포넌트로 와서 코드를 보면, 이 children이 결국은 모달 컴포넌트가 쓰이는 네브바에 오면 이렇게 변하게 된다. 컴포넌트 안에서는 저렇게 children처리를 해두고, 직접적으로 모달 컴포넌트를 임포트 해서 쓰이는 컴포넌트에서는 저렇게 바로 중괄호 안에 자신이 원하는 컴포넌트를 써주면 된다. 그런데 만약 필자의 경우처럼, 한 화면 안에서 컴포넌트의 내용이 바뀐다던지, 혹은 버튼에 따라 다른 내용의 컴포넌트를 불러와야 한다면, 저렇게 중괄호를 여러 개로 만들어 그에 따라 조건을 붙여주면 된다. 지금은 위에서 봤던 사인인과 사인업 버튼에 네임을 붙인 함수의 작용에 따라 상태값을 구분해 줬고, 그에 따라 작용하는 컴포넌트 들을 달리 설정해 준 것이라고 보면 된다.

이를 통해 필자는 해당 컴포넌트를 내용만 바꿔서 갈아 끼울 수 있었다~

3. 이제 단순 모달은 쉽다.

모달창에 자신감이 붙은 필자는 이제 새로운 모달을 만들 필요성을 다시 느꼈다. 그건 바로, 메인 홈 랜딩 페이지가 아닌 리스트나 다른 디테일 페이지에서 모달창이 다시 필요했기 때문이다. 물론 재사용도 고려해 봤지만, 이미 그건 css나 적용 상태가 홈 랜딩 페이지에 맞춰져 있어서 새로 만드는 것이 낫다는 판단이 섰다. 그리고 되려 이제는 형태적 차이만 있을 뿐, 만드는 원리는 똑같다는 생각이었고 결론적으로 1시간도 안 걸렸던 것 같다.
아래는 리스트 페이지에서 쓰이는 모달을 조절하는 스테이트 문이다. 똑같다. 열고 닫는 상태만 정해 줬고, 온클릭시에 상태가 변하는 함수만 넣어줬다.

이 온클릭을 받는 것이 결국에는 리스트 페이지에서 저장 버튼을 누르면, 모달이 뜰 수 있도록 넣어준 것 뿐이다. 이후에는 기존처럼 isOpen이 참이면 boardModal이라는 컴포넌트의 내용이 보여지도록 처리해 준 것 뿐이다.

참고로 모달창은 따로 컴포넌트로 빼지는 않았다. 이번에는 그냥 해당 윈 리스트라는 카드 컴포넌트 안에서 해당 모달창 css를 만들어서 바로 적용해 줬다.

결론적으로 보면, 원리는 같다. 결국 필자는 이렇게 하면서 모달과 친숙해 질 수 있었고, 이제는 간단한 모달 정도는 충분히 만들 수 있겠다는 확신이 들었다.

4. 모달과 데이터 연결 후 기능 추가.

다음 과정으로는 모달안에 내용에서 추가적으로 데이터를 처리해 주는 과정이 필요했다.
물론 로그인 회원가입 역시 그 과정이 많았지만, 이번에는 그 기능들보다는 보드 모달 컴포넌트 안에서 처리 과정에 대해 적어보겠다.

1) 백엔드 통신의 과정

먼저 보드에 대한 정보를 받는 과정이 필요했다.

  1. 그래서 아래와 같이 보드 네임과 아이디, 윈 아이디를 전달해 주는 패치 함수를 작성했다.
  2. 프롭스로 이 데이터들을 다시 하위 컴포넌트인, 보드 리스트라는 보드 모달 안의 내부 컴포넌트로 전달해 줘야만 한다.
  3. 이때, 기존에 윈 리스트라는 모달이 쓰이는 장소에서 선언한 스테이트인 setIsopen도 아래 컴포넌트로 전달한다.
    물론 지금은 시간 관계상 이렇게 처리하긴 했지만, 컨텍스트 api를 쓸 필요성에 대해 느낄 수 있었다.


이때도, 사실은 바로 연결하기는 좀 힘들었고, 백엔드 태영님과 굉장히 많은 시행착오를 거쳐야만 했다.
일단, 백엔드에서 보내 주는 데이터에 따른 api구조도 계속 바뀌었고, 그에 따라 안에 프롭스 네임도 계속 바뀌었다.

2) 데이터 넣기 및 프롭스 전달의 과정.


1. 이제 보드 모달에서 다시 프롭스의 전달 과정이 필요하다. 결국 보드 리스트에서 보드 네임에서 해당 프롭스를 다 쓰니까.
2. 아래 그림처럼 결국 보드 네임은 프롭스로 받았고, 나머지는 데이터 처리에서 쓰이게 된다.
3. 우리 팀은 각각의 윈이라는 카드 컴포넌트에서 바로 보드로 저장하는 모달 기능을 원했기에, 필자는 보드 리스트에서 저장이 가능하게 금 기능 추가를 했다. 바로 저기 보다는 저장 버튼을 누를 시에, 세이브 보드라는 api 통신이 이루어 진다.
4. 말은 간단한데, 그 과정은 녹록치 않았다. 정말로... 백엔드 태영님의 설명을 들으면서 저 함수의 코드 구조를 하나씩 적어나갔다.
5. 결국 프론트 단에서 백엔드 단으로 전달할 내용은 식별을 위한 보드아이디와 윈 아이디였다. 그리고 그것을 통해 필자는 리스폰스로 두 가지 반응을 처리해서 사용자에게 보여줘야만 했다.
6. 그게 바로 저 .then(data=> 이후 조건 문 처리 과정이다. 여기서 세이브 석세느는 말 그대로 유저의 개인 보드 페이지에 해당 윈의 저장이 성공된 것이다. 그것을 알러트로 알려준다.
7. 다음은 이미 저장한 윈의경우 백엔드 단에서 추가 저장을 막아뒀기에 이미 저장했다는 리스폰스를 처리해 줬다. 그걸 다시 사용자에게 인지 시키기위해 알러트로 다시 표시해 줬다.
8. setIsOpe의 경우에는 결국 모달창을 닫는 스테이트인데, 저장을 하면 바로 창이 닫혔으면 좋겠다는 프론트 태준님의 의견에 따라 해당 부분에 유즈스테이트를 false로 바꿔줘서 창이 바로 닫히도록 처리해 줬다. 이를 통해 사용자의 편의성이 더 높아진 것 같다.

5. 총 소감.

처음에는 모달 창? 뭐 별거 있겠나? 이 느낌이었다.

하지만, 막상 내 오산이라는 것을 점점 코딩을 하면서 느꼈다. 이번을 계기로 기본적으로 모달이 구현되는 원리부터, 그것들을 응용하는 과정까지 정말 많이 배운 것 같다. 물론 아직 배울 것은 한 참 남았지만, 그래도 이제는 모달이 뜨면 이전처럼 "아... " 이렇게 얼타지는 않을 것 같다. 😭

사실 2부에서 마치려고 했으나, 이번에도 어김없이 3부까지 가야할 것 같다... ㅎㅎ 그래서 다음 3부에서는 남은 로그인 코드와 더불어 성찰포인트 및 총 소감으로 마무리하고 회고를 마무리하겠다.

3부 바로 가기

profile
개발도 예능처럼 재미지게~

0개의 댓글