[weather-NextJS] 개발기 #3

ZenTechie·2023년 8월 2일
0
post-thumbnail

8.2 (수)

iOS, MacOS 날씨 어플 모달(?)

수행한 것

  • 디자인 관련
    • react-icon 패키지 설치
    • Tomorrow.io Weather Code에 따른 날씨 svg 세팅
  • 모달(?) 추가
    • 모달인지 사이드바인지 정확한 명칭은 잘 모르겠다.
    • 모바일과 데스크탑 별로 다르게 표시되도록 했다.
    • createPortal을 사용해서 구현했다.
  • 최저, 최고 온도 하드코딩 수정
    • 기존에 현재(오늘)날씨를 보면 최저, 최고 온도가 있는데 처음에는 +5, -5로 하드코딩 했었다. 그 이유는, Tomorrow.io에서 Weather Forecast, Realtime Weather 2가지를 제공하는데 현재 날씨에서는 Realtime을 사용했었다. 근데 이는 최저, 최고 온도를 제공하지 않았다.
    • 그래서 최저, 최고 온도 데이터를 제공하는 Weather Forecast로 바꿨다. 대신, 이는 1d 또는 1h 별로 날씨 데이터가 제공되기에, 배열의 인덱스를 적절히 사용해서 현재 시간에 맞는 날씨 데이터만 가져왔다.
  • 모달 팝업 시, 애니메이션 적용
  • 브라우저 크기에 따른 모달 위치 & 레이아웃 변경
    • 일단, 모바일 버전에서는 아래에 위치한 아이콘(=footer)을 클릭해서 모달을 띄우고, 데스크탑 버전에서는 왼쪽 위에 위치한 아이콘(=header)을 클릭해서 모달은 띄운다. (이들은 서로 다른 컴포넌트이다.)
    • 브라우저 크기 측정은, 초기 렌더링 시 한번만 이루어지는게 아닌 사용자가 브라우저 크기를 변경할 때 마다, 측정이 되야 한다. 이 때문에 useEffect를 사용할 수 밖에 없었다.(다른 방법이 없나 찾아봤는데 찾지 못했다). 그래서, headerfooter를 Client-Side로 만들 수 밖에 없었다.

고민한 것

  • 데스크탑 버전을 보면, 북마크(?)아이콘을 클릭해서 왼쪽의 사이드 바를 없앨 수 있다. 이때 사이드 바가 사라지면서, 오른쪽 레이아웃이 왼쪽으로 늘어나는 애니메이션이 수행된다.
    • 이를 위해 Framer Motion을 적용하려고 했는데, Framer motion은 Client Side에서만 작동한다. 현재, 내가 작성한 오른쪽 레이아웃에 해당하는 코드는 Server Side(page.tsx에 위치)이기에 이를 수정할 수는 없었다.
    • 왜 수정하지 못하냐면, Data Fetching을 page.tsx에서 하고 있고, 이는 Server Side에서만 동작하기 때문에, Client-Side로 바꾸게 되면 모든 코드를 다 고쳐야하는 불상사가 생기게 되기 때문이다.
    • 그래서 2가지 버전을 만들었다.
      • page.tsx에서 오른쪽 레이아웃에 해당하는 코드를 따로 tsx 파일을 만들어 여기다 저장한다. 그리고 이는 use client를 사용해서 Client-Side로 만든다.
      • 매끄러운 애니메이션을 포기하고 기존 버전 그대로 사용한다.
    • 자세히 보면, MacOS 날씨 어플에서 양쪽의 아이템의 width를 변경할 수 있다.(가운데 줄을 양 옆으로 드래그 해서..) 카페에서 모든 작업을 다 하고 집에 가면서 이게 어떤 기능인지 찾아보니 Splitter(스플리터)라고 한다. 그래서 애니메이션이 생각대로 적용되지 않으면, 차라리 깔끔히 포기하고 그냥 스플리터로 만들어야 겠다고 생각했다.
  • 모달(사이드 바)을 보면 사용자가 저장한 위치를 토대로 목록으로 해당 위치의 날씨를 보여주는데, 이를 그대로 구현하려면, localStorage를 사용해야 하는지, 아니면 firebase 같은 것을 사용해야 하는지를 고민했다. 규모가 그리 크지 않은 프로젝트지만, localStorage가 적절하지 않을 수도 있다는 생각에 더 고민해보고 추후 결정을 내릴 생각이다.
  • footer와 header내의 겹치는 모달 관련 코드를 어떻게 처리해야할까?

footer와 header내의 겹치는 모달 관련 코드

내가 작성한 코드는 아래와 같다.

// Header
const [showModal, setShowModal] = useState(false);
const [windowSize, setWindowSize] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      setWindowSize(window.innerWidth);
    };
    console.log(windowSize);
    handleResize();

    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, [windowSize]);

  return (
    <div className='hidden lg:block lg:pl-5 pt-5 text-white '>
      <div className='cursor-pointer' onClick={() => setShowModal(prev => !prev)}>
        <BsCardList size={30} />
      </div>

      <AnimatePresence>
        {showModal && (
          <Modal
            onClose={() => {
              setShowModal(false);
            }}
            windowSize={windowSize}
          />
        )}
      </AnimatePresence>
    </div>
  );
// Footer
const [showModal, setShowModal] = useState(false);
const [windowSize, setWindowSize] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      setWindowSize(window.innerWidth);
    };
    console.log(windowSize);
    handleResize();

    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, [windowSize]);
  return (
    <div className='sticky bottom-0 flex justify-between items-center z-20 bg-slate-400 text-2xl px-5 py-2 lg:mt-5 lg:hidden'>
      <div className='cursor-pointer'>
        <ImMap2 size={23} />
      </div>
      <div>슬라이더</div>
      <div className='cursor-pointer' onClick={() => setShowModal(true)}>
        <AiOutlineUnorderedList size={25} />
      </div>

      <AnimatePresence>
        {showModal && (
          <Modal
            onClose={() => {
              setShowModal(false);
            }}
            windowSize={windowSize}
          />
        )}
      </AnimatePresence>
    </div>
  );

showModalwindowSize라는 state가 똑같이 선언되어 있고, 브라우저 크기를 측정하는 코드도 똑같다. 이에 따른 모달을 여는 코드도 똑같다..

코드의 낭비라고 생각이 되어, 애니메이션을 위해 client-side를 유지하면서 이를 어떻게 해결할 수 있을까를 고민하는데 시간을 좀 썼다. 이게 괜찮은 방법인지는 아직도 확신이 잘 서지 않지만, 2가지로 구현했다.

  1. page.tsx 내부의 레이아웃에 해당하는 코드를 Section.tsx라는 파일로 옮기고 Client-Side로 선언한다. 그리고 중복되는 코드는 Section.tsx에 선언한다.(Section.tsx는 Client-Side이기 때문에, useEffect, useState가 사용가능해진다.)
    그리고, Header와 Footer의 코드도 그대로 옮긴다. (<Footer/> <Header/> 로 선언하는게 아닌, 내부의 HTML 구조를 옮긴 것이다.)
  2. 그냥 기존의 방식 그대로 사용하기. (중복되는 코드던 뭐던..)

1번째 방식으로 해도 애니메이션이 매끄럽게 적용되지는 않았고, Splitter라는 기능을 알게되서 이걸로 다시 구현할 것 같기에 더 이상 코드를 수정하지는 않았다.

배운 것

  • Framer-motion에서 exit 속성이 있는데, 이를 사용하려면 <AnimatePresence>로 코드를 감싸야 한다.
  • TypeScript도 공부를 많이 해야겠다.
    • 자바, C, C++을 배운적이 있고 자바는 거의 최근까지도 메인 언어로 사용했기에 타입 언어에 대한 거부감은 없지만, TypeScript는 뭔가 어색하다..
  • layout.tsx에도 tailwindcss를 적용할 수 있다.
    • 초기에 inter.className으로 설정되어 있지만, 다중 클래스 이름을 부여하는 방식을 그대로 사용하면 된다.
      근데 이렇게 해도 괜찮은 방식인지는 잘 모르겠다.
  • layout.tsx 내부의 Inter는 폰트를 적용하기 위한 코드이다.

결과물

모바일 모달

데스크탑 모달(사이드 바)

정말 배울 것이 너무나도 많다. NextJS도 잘 모르고, TailwindCSS도 잘 몰라서 계속해서 공식 문서를 찾아보면서 하고 있다. 모르는 게 많지만 하나씩 적용해보는게 역시 재밌.짜릿

profile
데브코스 진행 중.. ~ 2024.03

0개의 댓글