프로덕트

아티클

전체 보기
자허토르테

자허토르테

2024년 개인 프로젝트 제작기 (2)

🗣️ 이번의 이야기

저번에는 프로젝트의 계기와 그 준비 과정에 대한 이야기를 적었다.

photo-1522435229388-6f7a422cd95b.jpg

여기서는 1주차에서 기본 요소라고 생각한 페이지들을 구현하면서 배우거나 알게 된 점들을 적어보고자 한다.

Next.JS가 아닌 Vite로 진행하니까 구현할 때, 설정 면에서 차이점을 느끼기도 했고, 또 하다 보니까 Firebase나 styled-components 등 이미 익숙하다고 생각했던 것에도 고민하거나 헤멘 부분들이 나오기도 했다.

그 중에는 진짜 글자 하나 때문에 발생한 사소한 실수도 있었는데, 그런 것까지도 이번에는 잊지 말자고 생각해 이렇게 글로 남겨봤다.

⚠️ Vite에서 환경 변수를 불러올 때의 주의점

Next.JS 등을 사용할 당시에 env 파일에 배치한 환경 변수를 불러올 때, 보통 process.env.(변수명)으로 불러왔던 기억이 있다.

Vite에서 환경 변수를 불러올 때는 그 형식이 좀 다르다는 걸 깨달았다.

  1. 일단 env 파일에서는 변수명 앞에 VITE_라는 접두사를 붙이자.

Untitled (19).png

Vite 내에서 사용할 환경 변수를 정의하고 싶다면, ‘VITE_’라는 접두사를 붙여야 한다. (envPrifix를 통해서 접두사 규칙을 변경할 수 있는데, 그게 아니라면 저 접두사를 꼭 붙여주자.)

  1. 환경 변수를 받아올 때에는 process.env.(변수명)이 아닌 import.meta.env.(변수명)이다.

Untitled (18).png

Vite에서 환경 변수를 쓸 때는 위의 이미지처럼import.meta.env.(변수명)으로 불러와 써야한다.

왜 이렇게 써야하는지, 변경할 수는 없는지 궁금해서 찾아봤지만 별다른 언급을 찾지 못해서 이대로 써야하는 것이 규칙인 듯 하니 Vite로 프로젝트를 하는 사람들은 이 점을 주의하는 게 좋겠다.

📩 Vite에서 public 폴더 경로에 있는 파일을 불러오는 방법

프로젝트 내 이미지 파일이나 svg 등 에셋들은 전부 public 폴더에서 관리하고 있다.

Vite에서는 참 고맙게도 public 폴더 자체에 대한 접근 경로를 따로 설정할 필요 없이 public 디렉토리를 바로 바라보도록 적용되어있다.

위와 같이 public 디렉토리가 기본적인 설정으로 되어있어서, 해당 디렉토리 내 추가 경로만 작성해주면 원하는 에셋을 가져올 수 있다.

🤦 env 파일에 배치한 Firebase API 변수 값을 받아오지 못 하는 문제

결과부터 말하자면, env 파일 내 환경 변수를 불러올 때 글자 하나를 지우지 않아서 발생한 문제였다.

정말 단순한 문제이지만, 오답 노트처럼 또 실수하지 않기 위해 남긴다.

Untitled (17).png

환경 변수에서 value 값 뒤에는 객체를 만들 때처럼 다음 key와 분리하기 위해 ‘,’를 찍을 필요가 없다.

바로 다음 줄로 넘어가서 다음 key와 value를 입력하면 되는데, 습관적으로 ‘,’를 찍은 바람에 나중에 API 테스트를 할 때 콘솔창에 Firebase의 API key가 없다는 경고가 나왔다.🤦

Untitled (16).png

사소한 문제지만, 그래도 안 짚고 가는 것보다 짚고 가는 게 중요하니 주의해두자.

❓ TypeScript에서 **try-catch** 문의 **error** 인수의 타입이 unknown이라고?

프로젝트 내에서 Firebase의 모듈을 데이터 처리를 할 때는 try-catch 문을 사용해여 에러 관리를 하고 있다.

근데 catch 문 안에서 error 메시지를 처리하려고 하니, error의 타입이 unknown으로 찍히고 있다는 것을 깨달았다.

Untitled (15).png

그냥 string으로 변환하면 되겠지-라고 생각하다가, 돌이켜보니 어라? 하고 급 호기심이 생겼다.

  • 왜 unknown인 걸까?

  • 그럼 해당 타입을 고려해서 예외 처리를 어떻게 진행하면 좋을까?

우선 왜 unknown인가에 대한 답을 찾기 위해서 구글링을 해봤는데, unknown인 타입 상태에서 어떻게 하면 문제를 해결할 수 있는가와 연관된 글은 많고, 왜 unknown으로 적용된 것인지에 대한 글은 찾기 힘들었다.

결국, 이 경우엔 ChatGPT의 힘을 빌려서 알아봤는데..

Untitled (14).png

결과적으로 catch 문 속에서 어떤 타입의 오류가 발생할지 모르므로 안정성 및 예측 가능성을 고려해 해당 타입이 디폴트로 적용된 것이라는 내용이었다.

단, 타입이 이런 만큼 error의 값을 제대로 사용하려면 타입을 명확하게 처리해야 한다. 안 그러면 아래와 같은 이슈가 발생한다.

Untitled (13).png

자, 이제 error의 타입이 왜 unknown인지 알았으니, 예외 처리를 진행해보고자 한다.

‘왜?’ 라는 글은 찾기 힘들었어도, ‘어떻게?’ 라는 글은 많아서 둘러봤는데 대다수의 글이 ‘타입 좁히기/내로잉(Narrowing)’을 이용해 문제를 해결했다.

Object is of type 'unknown'

위의 글에서 catch 문의 error 인자값을 처리하는 방법으로 instanceof를 활용했다.

instanceof란 대상이 ****어떤 class나 생성자 함수를 사용하여 생성됐는지를 판단해주는 연산자이다.

(한동안 잊고 있었다가 예전에 이고잉 님의 강의를 보면서 정리했던 내용이 있는데, 궁금한 사람은 해당 내용을 참고해주길 바란다.)

이 연산자를 이용해서, error 인자에서 받아오는 값이 Error class를 가지고 있는지 분간하여 예외 처리를 진행하면 된다.

Untitled (12).png

기본적으로 흔히 우리가 자주 보는 Uncaught Error와 같이 콘솔에서 찍히는 에러들은 Error class를 가지고 있다.

따라서 이에 해당하는 친구들을 if문을 통해 분류하고, 아닌 친구들은 우선 문자열로 변환해 반환되게 임시로 처리했다.

지금 당장은 이 정도면 충분할 것 같다.

추후 프로젝트가 어느 정도 진행되면 Sentry를 배치할 예정인데, 양식을 만들고 나서 수정해도 늦지는 않을 거다.

🤔 Firebase Auth 템플릿의 작업 URL 처리, 그리고 페이지 렌더 이슈…

이렇게만 보면 어떤 문제인가 싶은데, 우선 Firebase Auth의 이메일 템플릿을 적용할 수 있는 부분은 이번 프로젝트에서 크게 두 가지로 갈린다.

  • 이메일 주소 인증을 위한 이메일 (본인 인증)

  • 가입한 이메일 주소를 통해 비밀번호 재설정 링크를 받을 이메일 (비밀번호 변경)

두 이메일은 같은 작업 URL을 공유하게 되는데, 받은 이메일을 통해 들어가야 하는 path가 각기 다른 점 때문에 작업 URL을 분리할 수 없다는 단점이 발생했다.

  • 이메일 주소 인증: /signupcomplete

  • 비밀번호 재설정: /findpassword

만약 이메일 주소 인증 경로(‘~~/signupcomplete’)로 작업 URL을 설정했을 시, 위의 두 케이스에서 보내주는 템플릿 메시지 내 URL은 아래와 같이 적용된다.

Untitled (11).png

이 경우, 비밀번호 재설정도 이메일 주소 인증 페이지로 이동하게 된다는 단점이 발생한다.

결국 어떤 식으로 동일한 작업 URL을 유지하게 할 것이냐가 문제 해결의 포인트인데, 이전 프로젝트에서는 하나의 페이지에서 switch-case를 이용해 경우에 따라 다른 컴포넌트들을 받아오게 했다.

하지만 이제와서 보니 case에 엮일 컴포넌트들을 다 배치해야 하는 만큼 무겁지 않을까? 비효율적이지 않을까? 라는 의문이 들어 좋은 방법이 아니라고 생각했다.

그렇다면 이번엔 차라리 공용으로 쓸 새로운 path를 추가하고 Custom Hook을 이용해 케이스에 따라 다른 path로 redirect 해주는 건 어떨까? 라고 생각해 쌈마이하게 방법을 바꿨다.

Untitled (10).png

redirect라는 path를 추가한 다음, Custom Hook에서는 진입한 링크의 params 중에 mode의 값이 이메일 인증이냐, 비밀번호 재설정이냐에 따라 react-router-dom의 useNavigate를 통해서 페이지를 redirect하도록 유도했다.

Untitled (9).png

보통이라면 그냥 useNavigate를 활용해서 보내주면 되겠지? 라고 생각하고 경로를 추가해서 바로 보내줬는데… 페이지로 이동은 되지만 렌더가… 바로 되지 않았다.

ezgif-2-824514c18a.gif

콘솔을 확인해보니 에러로 나오는 사항도 없었기 때문에 왜 이런 일이 생겼는지 솔직히 아직도 원인을 모르겠다.. 누군가 알려줬으면!

어쨌든 문제는 해결해야 하니까, ‘어떻게 하면 될까?’라고 고민하던 중에 혹시 렌더 타이밍이 너무 빠른 걸까? 싶어서 아래와 같이 setTimeout을 배치하고 시도해봤다.

Untitled (8).png

그래서 테스트 결과는 아래와 같이 문제 없이 redirect되면서, 페이지 렌더링도 잘 적용됐다.

ezgif-5-20eb812dab.gif

정말로 렌더 타이밍이 너무나도 빨라서 그런 걸까?

문제가 해결됐으니 만족스럽긴 한데, 정확한 원인을 모르니 체기가 남은 느낌이 든다...

😖 useLayoutEffect의 Dependency Array가 비어있는 데도 두 번 돈다?

이메일 인증 완료 처리와 관련하여 위의 이슈와는 별개로 인증 완료 페이지에 처음 진입 시, 의존성 배열(dependency array)를 비워놔도 useLayoutEffect 속 로직이 한 번 더 동작하는 것을 깨달았다.

그러다보니 첫 동작 시에 인증은 완료되지만, 렌더 후에 한 번 더 인증 절차가 작동하게 되는 이슈가 발생했다.

다행히 Firebase 쪽에서는 비활성화된 코드라면서 에러를 내뱉고 동작을 막아줘서 큰 문제가 되지는 않는데, 그럼에도 한 번만 동작하면 되는 것을 두 번 동작되고 그러니 의도한 그림과는 달라서 이 부분을 해결해보고 싶었다.

Untitled (7).png

예전에 useEffect와 useLayoutEffect를 공부했을 때, 둘의 차이점은 렌더링 전/후를 기준으로 첫 동작 시점에 차이가 있다는 것 뿐이었고 의존성 배열에 대해선, 두 훅 모두 비어있다면 첫 동작만 실행된다는 공통점이 있다는 걸 기억하고 있다.

혹시 내가 잘못 알고 있었나..? 싶어서 일단 useLayoutEffect 훅 내 코드에 문제가 없던 건 아닌지 고쳐보기로 했다.

우선 인증 처리 함수를 useLayoutEffect 외부에서 생성해, 해당 함수를 useLayoutEffect 안에서 호출하도록 했다.

const applyActionCodeAndVerifiedAccount = useCallback(async () => {
  const actionCode = await searchParams.get("actionCode");
  if (actionCode === null) {
    throw new Error("The Action Code is invalid.");
  }
  await applyActionCode(auth, actionCode);
},[auth, searchParams]);

useLayoutEffect(() => {
  applyActionCodeAndVerifiedAccount();
}, [applyActionCodeAndVerifiedAccount]);

이 때, 외부로 뺀 함수 applyActionCodeAndVerifiedAccount는 렌더링이 될 때마다 유지되는 게 아니라 새로 생성되는데 그러면 useLayoutEffect도 해당 함수가 변화한 것을 인지하고 리렌더링을 일으키므로 잘못하다간 무한 리렌더링을 유발할 수 있다.

그래서, 함수에 useCallback을 적용하여 더 이상 렌더링이 일어나지 않게 방지했다.

(아, 참고로 저렇게 처리를 안 하면 React가 The ‘~~~’ function makes the dependencies of useLayoutEffect Hook change on every render.라면서 친절하게 수정하라고 경고해준다.^^)

저렇게 함으로서 함수도 변경되는 사항이 없을테니 한 번만 돌 것이라 생각하고 테스트해봤는데.. 똑같이 한 번 더 돌아서 에러가 내뱉어지는 건 여전했다.

Untitled (6).png

이러다보니 아예 함수를 useLayoutEffect 안에 넣어, 훅 내부에서만 돌도록 하면 될까? 라고 생각했지만, 이거도 생각해보니까 useLayoutEffect 속 내용이 두 번 도는 건 변함이 없다.

사고가 마비된 느낌이라서, 잠시 쉬었다가 다시 곰곰히 생각해봤는데..

그럼 사실 useLayoutEffect는 문제가 없고 외부의 요인에서 문제가 있는 건가? 🤔

접근을 다르게 보고 검색을 시도해봤더니 아래와 같은 블로그 글이 보였다.

[개발일지 #3] UseEffect는 왜 두번 실행되는걸까?

한 마디로, React에서 내부 로직을 엄격하게 검사하기 위한 StrictMode의 영향이라는 듯 하다. 그래서 StrictMode를 지워보고 테스트해봤다.

Untitled (5).png

깔끔하게 해결이 됐다.. 휴, Hook과 관련하여 내가 여태까지 잘못 알고 있었나 싶어서 걱정했는데 다행히 틀리진 않은 것 같다.

아무튼 원인을 알았으니 StrictMode를 지우고.. 쓰려고 했는데 내부 코드까지 검사해준다는데 개발 당시에는 지우지 않아도 괜찮을 것 같아서 유지하기로 했다.

🔍 닉네임 글자 검사를 Byte 수로 체크하면 안되는 걸까?

이번 프로젝트는 다국어 지원을 염두해두고 있는데, 그러다보니 닉네임과 관련하여 고민이 생겼다.

‘사람마다 닉네임을 지을 언어가 다 다를텐데 이걸 어떻게 처리하는 게 좋을까’라는 부분이었는데 이에 대한 해결책으로는 Byte로 계산해서 처리하자! 라고 답이 쉽게 나왔다.

흔히 기억하기로 영어랑 숫자는 1바이트이고, 한글이나 일본어, 한자는 2바이트라는 이야기를 건너건너 들은 적이 있어서 그렇게 처리하면 되겠지? 라고 생각했는데…

Untitled (4).png

…정말 안일한 생각이었다는 걸 반성한다. 뭐든지 건너 들었다고 해서 ‘이거면 되겠지?’라고 생각하지 말고, 검증을 해보자.

  1. 우선 string을 Byte로 계산하고 싶은데 방법이 있을까?

있다. Blob 인스턴스를 통해 string을 UTF-8로 인코딩해서 Byte 수를 계산하면 된다.

Get Byte size of the string in Javascript

function countStringConvertToBytes(string) {
    return new Blob([string]).size;
}

이렇게 하면 string이 총 몇 Byte인지를 알 수 있다.

  1. 그래서 영어와 숫자는 1 Byte, 일본어랑 한국어는 정말 2Byte일까?

결과는 아래 이미지로 대체하겠다.

Untitled (3).png

‘16 Byte로 제한하면 되겠지’라고 단순하게 접근한 내 스스로가 원망스럽다..

이렇게 될 경우, 한글과 일본어는 의도와 다르게 5글자까지 입력이 될 것이고, 영어와 숫자는 혼합하더라도 16자까지 입력은 가능하다.

여기서 더 케이스를 생각해본다면..

  • 차라리 24 Byte로 늘리는 건?

    • 영문, 숫자도 24글자 입력되는 건 너무 긴 것 같다.

  • 그렇다면 영어랑 숫자만 있다면 16Byte로 제한하는 건?

    • 사이에 한글이나 일본어가 들어간 순간의 예외 처리는?

      • x지죤너구리x 같은 닉네임의 처리는?

Untitled (2).png

이러다보니 답이 도저히 나오지 않았고, 끝내 대안을 두 가지 정리해봤다.

  • 영어와 숫자로만 닉네임을 만들 수 있도록 하여 16자까지 입력 가능하도록.

  • 혹은 이메일 주소의 아이디 부분을 닉네임으로 강제로 부여하도록.

그 밖에도 정규식으로 해결볼 수 있지 않을까? 라는 생각도 해봤는데, 이건 ChatGPT를 통해서 좀 핑퐁해봐야 할 것 같고… 닉네임이 쓰이는 페이지가 현재로서는 메인 페이지 밖에 없어서 우선은 2안으로 진행하기로 했다.

사실 1안이 가장 Best라고 생각하고 있는데, 우선은 기능 추가 전까지는 2안을 유지하고, 분명 닉네임을 더 쓸 수 있는 요소가 추가되면 1안으로 바로 되돌릴 수 있도록 유연하게 생각하기로 했다.

💅 styled-components의 prop 처리 - Transient Prop?

공용 버튼 컴포넌트를 제작한 후, 페이지에 배치를 하던 중에 bgColor라는 props을 만든 뒤, ‘돌아가기’ 버튼의 배경색을 변경해야 해서 해당 prop에 ‘invalid’로 값을 적용했던 적이 있었다.

그러더니 콘솔에서 다음과 같은 에러가 출력했다.

Untitled.png

해당 에러가 발생하는 이유는 bgColor라는 저 Prop이 DOM에 있는 HTML 요소들에 직접적으로 연결될 수 있다는 건데, 에러 메시지 쪽에서는 이런 문제를 방지하기 위해 스펠링을 모두 소문자 케이스로 변경하라고 하지만, props를 카멜 케이스로 쓰도록 규칙을 잡아놨으니 그건 좀 어려울 것 같았다.

다른 방법이 있을까 구글링을 해봤는데, prop의 앞에 달러 사인(’$’)을 붙여 해당 Prop이 HTML 요소에는 직접 관여하지 않으며, 오로지 styled-components에서만 쓰인다고 알리는, transient prop을 사용하라는 글들이 많았다.

Transient Props in styled-components

[ React ] styled-component에 props 보낼 때 나오는 warning 해결

Transient이 어떤 뜻인지 사전에서 알아보니 ‘일시적인, 순간적인, 일시적으로 머무르는’이라는 의미를 가지고 있다.

즉, ‘일시적인 프로퍼티’라는 건데 styled-components 문서를 보면 그 설명이 더 잘 나와있다.

스타일이 지정된 구성 요소에서 사용하도록 의도된 prop이 기본 React 노드로 전달되거나 DOM 요소로 렌더링되는 것을 방지하려면 prop 이름 앞에 달러 기호($)를 붙여 임시 prop으로 전환할 수 있습니다.

styled-components: API Reference

이걸 이용하면 styled-components에서만 사용하는 Prop을 DOM 요소에 직접 반영되는 문제를 피할 수 있다.

export type ButtonColorProp = {
  $bgColor?: "primary" | "invalid";
};
const Wrapper = styled.button<ButtonColorProp>`
  background-color: ${(props) =>
  isButtonBgColorPrimaryOrInvalid(props.$bgColor)};
`
export default function Button(props: ButtonProp) {
  const { text, type, bgColor, onClick } = props;
  return (
    <StButton.Wrapper type={type} $bgColor={bgColor} onClick={onClick}>
      {text}
    </StButton.Wrapper>
  );
}

이번 프로젝트에서 사용한 코드 중 일부인데, 버튼의 색을 경우에 따라 다르게 바꿔야 할 필요가 있다보니 위와 같이 작성하였다.

bgColor라는 Prop은 DOM 요소에는 없는 속성이니 이런 식으로 Transient Prop을 통해 styled-components에서만 반영되어서 쓰도록 해 DOM 요소에 영향을 주는 것을 방지했다.

한편, Transient Prop 관련 내용을 찾다보니 해당 기능을 사용할 때의 주의점을 정리해주신 분이 계셨는데 위의 기능을 쓰려는 사람들은 한 번 읽어보는 것이 좋을 것 같다.

Untitled (1).png

[styled-components] Transient props($propName) 사용 시 주의할 점 (feat. shouldForwardProp)

🎙️ 다음 이야기는…

1주차에서 하고 싶은 말을 다 했으니, 이제 2주차 내용을 정리하기 시작해야겠다.

2주차에는 메인 페이지와 준비물 생성에 관한 이야기를 작성하고자 한다. 슬슬 Firestore 배치도 진행하고, 준비물을 어떤 구조로 저장하고 받아올지도 생각해봐야 하고…

한 고비 산을 넘겼더니 다음 고비 산이 떡하니 나를 맞이해주고 있는데 그 산을 넘어가면 조금이라도 성장한 내가 있겠지? 긍정적으로 생각하고 작업에 집중해야겠다.

au revoir!

🔖 참고 자료
Vite-Vite의 환경 변수와 모드

Vite-envPrifix

Vite-public 디렉토리

[09/29] 'Uncaught ReferenceError: process is not defined' error

Object is of type 'unknown'

Get a catch block error message with TypeScript

TypeScript에서 catch block error message 사용하기

'instanceof'로 클래스 확인하기

A Complete Guide to useEffect — overreacted

React.js - exhaustive-deps-warning, react, react-hook

Get Byte size of the string in Javascript

Transient Props in styled-components

[ React ] styled-component에 props 보낼 때 나오는 warning 해결

[styled-components] Transient props($propName) 사용 시 주의할 점 (feat. shouldForwardProp)

1
0
자허토르테

자허토르테

2024년 개인 프로젝트 제작기 (1)

💭 개인 프로젝트를 다시 시작하다.

회사에서 6개월 동안 일을 하고 계약 만료로 그만두게 됐다.

이후에는 지쳐있던 멘탈을 관리해야겠다 싶어 한동안 코드를 멀리하고 12월에는 여행을 다니고, 1월에는 많은 분들과의 커피챗을 나누면서 개인적인 시간들을 보냈다.

그렇게 기력이 조금씩 회복되니 취업도 다시 생각해야 하고, 조금씩 코드를 다시 잡아야겠다는 생각이 들면서 사고치기 주도 개발(?)에 따라 오랜만에 개인 프로젝트를 시작하기로 했다.

이번 프로젝트의 계기는 12월에 진행했던 18박 19일의 배낭 여행이었다.

하도 숙박을 여러 곳에서 머물게 되다 보니 놓고 가거나, 잃어버린 물건이 없는지 짐 체크는 여행 중의 일상이었다.

그러다보니 언제부턴가 준비물 리스트를 종이에 써서 체크하며 다녔는데, 매번 이 종이를 꺼내서 체크하는 게 나중가서는 귀찮게 느껴지기 시작했다.

여행에서 돌아오고 나니 이 때의 경험을 기반으로 프로젝트로 만들고 싶다고 생각했다.

하지만, 1월은 여전히 기력이 없어서 좀 더 다양한 분들과 이야기를 나누면서 휴식을 취했고, 2월이 되어서야 본격적으로 프로젝트를 건드리기 시작했다.

시작 전에 혹시나 있을까 싶어 레퍼런스도 찾아봤는데 여행 관련 플랫폼에서도 관련 기능을 지원해주는 곳이 없는 것 같아서 적은 수요는 그래도 있을 것이라고 판단했다. (후에 초링(@earthloverdev) 님과 커피챗에서 ‘트리플’에 관련 기능이 있다는 걸 들었다..ㅋㅋ)

Untitled.png(트리플의 여행 준비 체크리스트. 엄청 깔끔하다..)

이번 프로젝트의 이름은 ‘준비물 챙겼어?’이고, 이번 프로젝트의 목표는 다음과 같이 결정했다.

  • 당연히 최소 목표는 런칭이다.

  • 작업하면서 그 과정들을 블로그에 서술해보고자 한다.

  • 여행 커뮤니티에 홍보를 올리고, 1달 동안 MAU 10명 이상을 유지해볼 것.

  • 꾸준하게 고객의 이야기를 듣고 기능을 개선해나갈 것.

    • 기회가 되면 웹 앱으로의 변환도 고려해보자. (React-Native의 Webview 기능을 통해서)

작년에 진행한 프로젝트 경험에서 많은 걸 느껴서 그런지, 기획-디자인-개발-마케팅까지 모두 혼자 힘으로 하는 프로젝트를 이번에도 이뤄내보고 싶었다.

다만 저번에는 유지에 실패한 만큼 이를 반면교사 삼아 꾸준하게 관리할 수 있는 프로젝트로 만들어보고 싶다.

✌️ 초기 기술 의사 결정

이번 프로젝트의 초기 기술 선택은 아래와 같이 했다.

  • TypeScript

    • 요즘은 어느 모집 공고를 봐도 필수로 적혀있는 것 같다.

    • 앞으로도 TypeScript와는 계속 친해져야 하고, 잘 이해하면서 쓰고 싶어서 선택했다.

  • Vite

    • [2024-02-16 수정] 댓글의 김무용 님께서 말씀해주신 대로 Next.js는 프레임워크이고 Vite는 빌드 도구이다. 잘못된 비교 내용을 작성했어서 내용을 정정했다.

    • 폴더명으로 라우팅을 구축하는 Next.JS보다는 Create React App과 React-Router-Dom을 이용해 원하는 path와 원하는 컴포넌트를 연결짓는 방식을 선호한다.
      그래서, 이번 프로젝트는 빌드 도구를 통해서 직접 React 환경을 구축하기로 했다.

    • Vite를 쓴 이유는 Webpack과 달리 프로젝트를 구축하는 시간이 덜하다는 점이었다.
      Webpack으로 초기 세팅을 했을 당시, 세세한 옵션들까지 다 설정하는 것 때문에 시간이 오래 걸렸는데, Vite는 필요한 라이브러리만 결정하면 기본 세팅을 완료해주니 React 프로젝트를 시작하는 게 굉장히 편했다.

    • 아울러 Webpack보다 빌드 시간이 더 빠르다는 특징도 많이들 언급해주셔서 Webpack 기반인 Creact React App을 이용해 프로젝트를 진행했던 사항과 달리 이번엔 Vite를 기반으로 React 프로젝트를 진행해보고 싶었다.

  • Jotai

    • 상태 관리는 지금도 고민중이지만, 지난 경험을 돌이켜볼 때 이번 프로젝트에서도 클라이언트 내에서 관리할 값이 많지 않을 것이라고 생각했다.

    • Recoil은 저번 프로젝트에서 써봤으므로 이번엔 Jotai를 써보기로 했다. (zustand나 Redux Toolkit은 이번 프로젝트에서 쓰기엔 store로 관리할 만큼 큰 규모가 필요하지 않을 거라고 판단했다.)

  • Firebase

    • 한 번 먹어본 맛이 가장 익숙하고 좋은 맛이라고 했다.

    • 사실 Supabase를 추천해주신 분들이 계셨는데, 프로젝트가 한 계정 당 2개까지만 무료로 제한된다는 이야기를 듣고 접어뒀다.

      • 본 프로젝트가 이용자가 많아져 마이그레이션에 대한 고민이 생기거나, 다른 대규모 프로젝트를 계획하게 된다면 그때 써보지 않을까?

  • styled-components

    • 이전에 CSS-in-JS를 쓰는 이유에 대해서 고민한 적이 있다.

      • 이 방식을 쓰는 건 단순히 컴포넌트의 재사용성 때문에 쓰는 거였을까?

      • 돌이켜보면 특정 값만 props를 통해서 변경하면 되는, 재사용성보다는 상태 변화에 따라 즉각적인 값 변화가 가능해서 쓴 것에 큰 장점이라고 판단했었다.

      • 그래서 이번에도 무난하게(?) styled-components를 선택하기로 했다.

    • 근데, 가장 큰 이유는 이런 css 라이브러리들이 Window OS에서 이슈가 있는 듯 하다..

      • 서버 컴포넌트에서 기능을 적용해봤지만 되지 않고, ‘use client’를 입력해 클라이언트 컴포넌트로 적용해야만 기능이 잘 작동됐다.

      • 원인이 궁금해서 이슈들을 찾아봤지만, 이 이슈들이 closed라고 해도 내 쪽에서는 계속 발생했으며 일부 이슈 수정 방향에 대해서 다른 사람들이 만든 패키지들을 설치하라는 것도 있었는데 이럴 경우는 package.json이 지저분해질 걸 생각하니 좋지 않다고 생각했다.

      • 그런 이유에서 사실 드랍했는데, 지금 글을 쓰면서 생각해보니 지금 작업하는 환경이 Vite이고 CSR이라서 생각해보면 문제되지 않는 이슈….잖아?🤦

        • 이럴거면 진작 쓸 걸 그랬다, 으흑흑….

        • 하지만, 이미 진행이 좀 된 상태라… 나중에 마이그레이션 하게 된다면 고민해봐야겠다(…)

        • 혹시나지만 이러니 나한테 맥 쓰라는 사람 있으면 돈 없는 백수에게 사줄 거 아니면 그런 말 하지 맙시다ㅂㄷㅂㄷ

  • i18n-next

    • 이 라이브러리는 희망사항으로 설치했던 건데, 해외에도 이용자가 있으면 좋겠다는 바람이 있어서 설치해보고 적용해보기로 했다.

    • 언어 지원 기능을 구현한다는 가정 하에 기회가 된다면 해외 쪽 홍보도 해보고 싶은데.. 이건 게스트하우스 연줄을 이용해봐야겠다..(될 지, 안 될 지 모르겠지만!)

우선 기초 라이브러리는 추후에 또 변경이 있을 수는 있지만, 이렇게 시작하기로 했다.

추가로 좀 더 생각하고 있다면 만든 걸 가지고 React-Native로 옮긴 뒤 WebView로 써먹고 싶다는 건데 그건 일단 정착이 되고 나서 생각해도 늦지 않다..^^

지금은 구현하고 런칭부터 하는 것까지가 목표다.

🎨 대략적인 디자인 그려보기

요즘 여러 UI 관련 사이트에서 다양한 앱을 구경한 덕분인지, 디자인에 필요한 요소들이 어떤 것인지 판단되는 것들이 생기니까 디자인 초안은 생각보다 빠르게 그려낼 수 있었다.

다만 체크리스트니까 TodoList 기반의 웹사이트라고 해도, 이왕이면 쓰는 사람들이 좋아할 만한 디자인이었으면 좋겠다고 생각해 약간 욕심을 부렸는데, 이게 오히려 개인적으로는 화를 자초한 듯한 느낌이 든다..^^ (넣을 기능이 많아졌다는 이야기이다…😭)

스크린샷 2024-02-12 210657.png

색상에 대해서는 파란색으로 테마를 잡았는데, ‘도전’이나 ‘자유’를 의미한다고 해서 여행을 준비하는 사람들의 이미지와 어울릴 거라 생각하고 이쪽으로 결정했다.

현재 디자인은 아래 링크를 통해서 구경할 수 있다.

많이 부족하지만 일단 현재는 이 정도로 하고 추가할 게 있으면 조금씩 늘려갈 예정이다.

https://www.figma.com/file/p7uM1yZSHFh05LIsu5L0tj/개인-프로젝트?type=design&node-id=12%3A45&mode=design&t=WIrWKTVSY1NqLCB1-1

기획서는 공책에만 써두고 디지털로 옮겨놓지 않았는데, 요 figma 문서에 모든 기획 내용이 다 담도록 할 예정이다.

어느 정도 가닥이 잡힌 뒤에는 추후 ReadMe나 github wiki를 통해서도 작성해 두려고 한다.

👍 1주차에 한 작업들

  • 기본 요소 페이지 제작

    웹 사이트에서 기본 요소라고 했을때 떠오르는 것들이 몇 가지가 있다. (물론 사람마다 다르겠지만…)

    • 로그인, 회원가입, 비밀번호 찾기, 비밀번호 변경

    이번 주는 디자인 초안을 완료한 뒤, 바로 위의 페이지들의 구현을 진행했다.

    스크린샷 2024-02-14 161322.png

    figma의 Dev Mode가 결제자들을 대상으로만 지원해준다는 걸 듣고 좀 아쉬웠는데.. 어차피 기획, 디자인, 개발까지 다 하는 입장에서 생각해보면 사실 혼자서 모든 정보를 다 알고 있으니 큰 문제는 되지 않으리라.

    아무튼 개인적으로 필요한 값들은 figma에 별도로 메모해서 기록해뒀다.

  • 폴더 및 파일 구조에 대해서

    1. 케밥 케이스

    이번 폴더 구조에서 특이점이 있다면 파일명이 ‘케밥 케이스’로 되어있다는 건데, 처음에는 카멜 케이스나 파스칼 케이스를 쓰려고 했지만 이런 부분에서는 어디는 대문자로, 어디는 소문자로 쓰여서 통일성이 없다는 생각이 계속 들었다.

    이 부분에 대한 고민을 하고 있었는데, 마선생(@horse_sensei) 님께서 케밥 케이스를 말씀해주셨다.

    https://x.com/horse_sensei/status/1754095580274700321?s=20

    확실히 이 방식을 차용하니 생각했던 고민들이 다 해결되어서 만족스러웠다. 고마워요, 마선생 님!😄

    1. 같은 파일명

    이번 프로젝트에선 하나의 컴포넌트와 엮어있는 사항들은 전부 같은 이름을 차용하기로 했다. 아래의 이미지처럼 말이다.

    스크린샷 2024-02-14 162713.png

    위와 같이 컴포넌트명이 동일하다면, 엮어있는 hooks나 styles, types 등도 같은 파일명을 쓰도록 했다.

    이런 식이라면 해당 컴포넌트는 같은 이름으로 된 파일들에서만 관리된다는 걸 알 수 있고, 또 이슈 수정 시 컴포넌트를 특정하여 트래킹하기도 쉬울 거라고 판단했다.

    단, input, button과 같은 공용으로 쓰이는 요소들에 대해서는 common이라는 폴더에서 따로 관리하기로 했다.

    스크린샷 2024-02-14 163524.png

    1. 정책(policy) 폴더

    이번 폴더 구조에서 특이점인 폴더가 있는데 policies, 즉 정책을 가리키는 폴더이다.

    이 부분은 전에 회사를 다닐 때, 잠시 계시다 떠나신 시니어 개발자 분께서 코드는 내용이 무엇인지 읽히는 게 명확해야 한다고 말씀해주시면서 조건을 관리할 때 이런 식으로 하면 좋을 것 같다고 제안해주셨던 방이었다.

    스크린샷 2024-02-15 011118.png스크린샷 2024-02-15 011006.png확실히 여러가지 조건문을 놔둔 채로 있는 것보다 함수명을 명확히 작성하여 해당 코드가 뭘 의미하고 싶은지를 알 수 있도록 하는 게 보기 좋다고 생각했고, 이 방법을 언젠가 차용해서 프로젝트에 써 먹고 싶다고 생각했는데 때마침 이번에 기회가 됐으니 각 페이지 별, 그리고 common폴더에 policies라는 폴더를 만들어 따로 관리하도록 했다.

  • 코드에 대해서

    1. 합성 컴포넌트에 대해서

    이전 회사에서 같이 일했던 이트루(@lxxtrue16) 님께서 어드민 페이지를 작업했을 당시, 그 밖에도 특정 기능을 만드셨을 당시에 Object.assign 메소드를 차용한 합성 컴포넌트 방식으로 코드를 짜신 것을 본 적이 있었다.

    ([Object.assign은 두 개의 객체를 합쳐 하나의 새로운 객체로 반환해주는 메소드이다.](https://www.notion.so/2022-11-23-JavaScript-2-cf08027413c94888944a4c6c4c141a2e?pvs=21))

    당시에는 그 방식이 솔직히 귀에 들어오고, 눈에 보이던 상황이 아니었고(일에 쫓겨서 생각이 들어올 뇌의 용량이 없었다... 죄송함다…!!🤦), 나중에 작업하셨던 코드를 보고 그 때 이야기를 해주신 것이 생각나서 뒤늦게 관련 내용을 찾던 중에 아래의 글을 발견하고 읽어봤다.

    합성 컴포넌트로 재사용성 극대화하기 | 카카오엔터테인먼트 FE 기술블로그

    합성 컴포넌트의 장점은 하나의 메인 컴포넌트에 귀속되는 서브 컴포넌트들을 포함시켜, 필요한 사항에 맞춰 서브 컴포넌트들을 이용해 다양한 방식으로 커스텀 할 수 있다.

    스크린샷 2024-02-14 170828.png스크린샷 2024-02-14 170846.png

    이렇게 하면 하나의 기능에 대해서 다양한 방식으로 만드는 것이 가능하므로 재사용 하기에도 좋고, 컴포넌트 자체에도 유연성이 보장된다.

    다만, 현재 프로젝트에서는 재사용되는 컴포넌트가 위의 Description과 같은 하나의 Common 컴포넌트로 관리해서 해당 컴포넌트 자체를 통짜로 가져와 쓰고 있다보니 위에서 설명한 합성 컴포넌트의 의의가 현재 프로젝트 내에서는 굉장히 얕다는 문제점이 있지만...

    그럼에도 불구하고 이 방식을 채택한 이유는 필요에 따라 예외 케이스가 발생했을 때 이용해볼 수 있다고 생각했으며, 그게 아니더라도 UI 컴포넌트 요소 자체를 그룹핑하기에도 좋은 방법이라고 판단했다.

    따라서 현재 각 페이지 및 요소에 따른 컴포넌트들은 합성 컴포넌트 방식을 차용해서 코드를 짜고 있다.

    1. Custom Hook 배치

    솔직히 말하자면 회사를 다니기 전까지 Custom Hook이라는 걸 스스로 만들어 본 적이 없었다.

    웨잇세컨드에서 했던 것은 타인이 만든 코드를 참고해서 한 것 뿐이라 어디까지나 맛보기라는 느낌이었고, 실제 업무에서 Custom Hook을 접했을 당시에는 이를 어떻게 만들어야 할 지 전혀 감이 오지 않았다.

    때문에 회사를 다니면서 다른 사람들이 코드를 짜는 방식을 보고, 직접 이것저것 시도해보고 나서야 Custom Hook에 익숙해질 수가 있었다.

    이번에 컴포넌트를 만들 때 결심한 것은 하나의 컴포넌트 위에서 return 위의 코드는 무조건 Custom Hook으로만 배치하여 컴포넌트만 보게 만들자는 생각이었다.

    스크린샷 2024-02-14 172430.png

    이렇게 함으로서 컴포넌트 안에서는 컴포넌트만 볼 수 있고, 훅에서는 기능과 관련한 코드만 볼 수 있으니 깔끔하게 구역 정리가 가능하다.

    1. 디자인 시스템? 규격화?

    디자인 시스템? 이라고 이야기하기도 부끄럽지만, UI 등을 이번에 작업할 때 규격화를 해두면 나중에 특정 값을 모두 다 변경할 사항이 있을 때 이를 통해 다 같이 바꿀 수 있으므로 관리하기에 편할 거라는 생각이 들었다.

    이전 회사에서 퇴사하셨던 분 중에 디자이너 분과 함께 디자인 시스템을 연구하셨던 분이 계셨는데, 이분이 정리하셨던 유틸 방식이 개인적으로 엄청 좋았고 배울 점이 많았다.

    그래서인지 스스로 만들 프로젝트에서도 그분의 방식을 차용해보고 싶었고, 때마침 그 방식을 이번 프로젝트에서도 사용해보기로 했다.

    스크린샷 2024-02-14 173013.png

    위처럼 객체에 하나의 케이스를 묶어 놓은 뒤, 스타일에서 이 객체를 불러와 특정 key을 적용함으로서 객체 속에 담겨있는 스타일 요소를 한꺼번에 불러올 수 있다.

    스크린샷 2024-02-14 173134.png

    지금은 위처럼 폰트만 관리하고 있는데, 추후에 색상 등도 이쪽으로 해서 통일성을 가질 수 있도록 반영해 놓으려고 한다.


🎙️ 다음 이야기는…

초기 세팅은 언제나 많은 생각을 하게 만드는 것 같다.

만들면서 이 내용, 저 내용을 기록해두다보니 하고 싶은 말은 너무 많은데, 읽는 사람이 지루해지지 않을 정도의 글을 쓰려고 하니 분량이 참 쉽지가 않아서, 이번 글은 여기까지 쓰기로.

다음 이야기는 1주차에 하면서 몰랐던 것들, 헤멘 것들에 대한 간단한 것들을 정리해보려고 한다.

Vite의 환경 변수 설정이라던가, TypeScript의 try-catch문 내 catch 부분의 error 인자 타입에 관한 내용이라던가, 닉네임 란을 포기한 이야기 등 프로젝트를 구현하면서 고민했던 것들을 좀 더 정리하는 시간을 가져보고자 한다.

뒤늦게지만 이렇게 시동을 건 만큼, 이번 프로젝트도 좋은 결과를 만들어 낼 수 있기를.

Adios!

https://github.com/DrunkenNeoguri/project-elements

2
4

포스트

아직 포스트가 없습니다.