1. 컴포넌트 구조, 어떻게 해야 할까 ?
<main>
<JoinTitleTextMessage>
재학생 인증을 위해
<br />
학교 이메일을 입력해주세요
</JoinTitleTextMessage>
<FormSection />
<SubmitSection />
</main>
위 사진의 현재 페이지 컴포넌트 구조이다. FormSection과 SubmitSection이 분리되어 있다. 주혁님이 지적해준 것 처럼, submit은 form안에 있는 게 정상적이다. 바깥이 Context로 둘러쌓여 있기에 현재 form과 submit이 따로 있는 것이 가능하다.나는 지금 표준적인 form의 형태를 벗어난 듯하다.
하나의 form안에 여러 개의 input이 있고, 가장 하단에 button이 있는 것이 표준적인 form의 형태이다.
그렇다면, 컴포넌트를 어떻게 나누어야 할까?
위와 같은 경우, 하나의 form일까, 두 개의 form일까?
html은 <form></form>에서 가장 가까운 <button></button>을 찾아서 submit을 한다. 그렇다면, 위의 경우는 두 번 제출이 이루어지므로 두 개의 폼이라고 볼 수 있다. 맞다. 폼은 제출의 단위이다. 그렇다면, 제출을 기준으로 컴포넌트를 나누어보자.
수정 후 컴포넌트 구조
<main>
<JoinTitleTextMessage>
재학생 인증을 위해
<br />
학교 이메일을 입력해주세요
</JoinTitleTextMessage>
<ModalContext>
<TimerContext>
<FormSection />
</TimerContext>
</ModalContext>
<NoticeSection />
</main>
- 유저와의 상호작용이 없는, server component에 해당하는
을 분리하였다. - 또한, FormSection 안에서 사용하는 ModalContext, TimerContext로 둘러쌓았다.
<section>
<EmailForm />
<CertificationForm />
</section>
- FormSection안에서 form을 두개로 나누었다. 이메일 입력 폼에 해당하는
EmailForm과 인증번호 입력 폼에 해당하는CertificationForm이다.
<form onSubmit={handleSubmit(onSubmit)}>
<EmailSection />
<BottomFixedDiv>
<Button text="인증하기" disabled={!isDirty} type="submit" />
<Spacing size={8} />
<Button text="다음에 인증하기" color="orange" onClick={nextStep} />
</BottomFixedDiv>
</form>
- 각 폼에는 입력하는
section과submit하는button이 있다.
2. Depth에 대한 고민
내가 맡은 회원가입 페이지.. 중구난방이다. 폴더 구조도, 상태관리도 재각각이다.
// step1
<main>
<AuthTitleTextMessage text={`휴대폰 번호를\\n인증해주세요`} />
<InputForm />
<Spacing size={18} />
<CheckboxSection />
</main>
// step2
<main>
<AuthTitleTextMessage text={`재학중인 학교\\n선택해주세요`} />
<InputForm />
<BottomModal />
</main>
// step3
<AuthTitleTextMessage text={`재학생 인증을 위해\\n학교 이메일을 입력해주세요`} />
<InputForm />
<NoticeSection />
// step4
<main>
<InputForm />
</main>
// step5
<main>
<AuthTitleTextMessage text={`사용자님의 성격을\\n선택해주세요!`} />
<p className="text-14 text-gray2">3개를 선택해주세요.</p>
<Spacing size={30} />
<InputForm />
</main>
각 InputForm 컴포넌트는 현재 다른 난이도의 추상화를 가지고 있다.
어떤 난이도의 추상화로 구현을 해야 가장 최선의 설계일까 ?
우선, useFunnel을 먼저 사용한 주혁님의 페이지 구조는 다음과 같다.
Funnel > Funnel.step > 페이지 컴포넌트 > TopNavigationBar, 메인 컴포넌트, FixedButton
먼저 Funnel을 구현했으니, 내가 이 폴더 구조를 따라가는 것이 맞다고 생각한다. 이 구조 또한 좋은 구조라 생각한다.
그럼 어디까지가 server component이고, 어디까지가 client component이어야 할까? 우선 페이지 컴포넌트까지는 server component이어야 한다. 왜냐하면, page 하위 있는 TopNavigationBar의 경우 서버에서 렌더링해야 하기때문이다.
그렇다면, 만약 메인 컴포넌트에 대한 상태값(예를 들어 인증 전, 인증 후)은 메인 컴포넌트 안에서 다루는 것이 맞다고 생각한다.
3. 컴포넌트 분리
위와 같은 페이지에서, 각 input들의 컴포넌트를 분리하는 것이 맞을까? 각 input을 분리하면, 각 input에 대한 로직들이 분리될 것이다. 이전 페이지들을 떠올려보면, 각 input들을 분리했다. 이 페이지 또한 각 input을 분리하는 것이 맞다고 생각한다.
여기서 고민되는 부분이 한 input값을 변경하였을 때, 다른 input값도 변경된다는 사실이다. 이는 Context API를 사용하기 때문이다. 그렇다면, 이를 어떻게 해결해야 할까? react-hook-form은 비제어 컴포넌트로 관리를 해주는데 왜 이런 문제가 발생하는 것일까?
그렇다. react-hook-form은 비제어 컴포넌트로 관리를 해준다. watch 함수를 react-hook-form에서 가져오지 않는 한 말이다. 그래서, watch가 아닌, 필요할 때만 사용하는 getValues를 활용하면 입력할 때마다 다른 컴포넌트가 리렌더링이 일어나지 않는다.
항상 아무 생각 없이 watch를 사용했기에 이런 문제가 발생했던 것이다.
그렇다면, watch를 사용하여 값들이 모두 채워지면 버튼이 활성화되는 로직을 약간 변경해야한다. 예를들어, 마지막 요소를 선택했을 때 이런 식으로 말이다.
4. Client Component와 Server Component
현재, useFunnel을 불러오는 JoinFunnel 컴포넌트는 훅을 사용하므로 당연히 client component이다. 그런데, JoinFunnel 컴포넌트는 Funnel 컴포넌트를 불러오고, Funnel 컴포넌트는 Funnel.step 컴포넌트를 불러온다. 그렇다면, Funnel 컴포넌트는 client component이고, Funnel.step 컴포넌트는 server component이다.
client component에서 import한 server component기에 Funnel.step 컴포넌트는 client component가 된다. 이를 server component로 사용할 수는 없을까?
useFunnel에 대해 다시 짚어 봐야 한다.
useFunnel은 클라이언트 측에서 컴포넌트를 교체하며 사용자들에게 다른 화면을 보여주는 훅이다. 그렇기에, client 컴포넌트로 구성되는 것이 당연한 것이다.
이러한, server에서 렌더링 가능한 부분도 client에서 구현을 하여 RSC의 장점을 못누린다는 점이 아쉽게 느껴진다.
이러한 점을 어떻게 개선할 수 있을까?
5. 컴포넌트 분리, 어떻게 해야 할까?
- Modal창 3개를 순차적으로 띄우는 페이지가 있다. 이 page에서 Component 분리를 어떻게 해야 할 지에 대한 고민을 많이 했다.
openModal,closeModal,modalName등 modal을 관리할 수 있는 커스텀훅인useModal이 있다. 이를 사용해서 3개의 Modal을 부모 컴포넌트에서 관리하려고 했다. 그리고, 미리 만든BottomUpModal컴포넌트의children으로Modal을 넣어서 사용하려고 했다. 그래서,modalName이 무엇이냐에 따라 다른 Modal을BottomUpModal의 children으로 넘겨주는 것이다.- 이렇게 구현을 했더니 코드가 200줄이 넘어가는 것이다.
BottomUpModal라는 공통 컴포넌트를 만들어서, children을 교체하는 식으로 구현을 하였다. 그런데 코드가 200줄이 넘어간다. 이 방식이 정말 맞을까? 공통 컴포넌트를 이런 방식으로 구현하는 것이 맞을까?- 주혁님이 이렇게
어떻게 할 지에 관한 선언형 프로그래밍 보다는,무엇을 할 지에 관한 명령형 프로그래밍을 하는 것이 맞다고 하시며, 각 Modal창을 컴포넌트화하라고 조언해주었다. 그래서 각BottomUpModal을 컴포넌트화하니 page의 코드가 120줄로 줄어들었다. 그리고, Modal상태, Modal을 제어하는 함수를 3개의 컴포넌트에 동일하게 넘겨주는 것을 확인하고, 이 Modal관련 상태와 함수를 전역 상태관리를 통해 관리하였다. 그리고, 각 컴포넌트에서는 전역 상태를 가져와서 사용하였다. 이렇게 구현하니 코드가 100줄로 줄어들었다. 보다 직관적이고,명령적으로 바뀌었음을 느꼈다. - 컴포넌트의 분리, 코드의 간결성 등 코드 퀄리티를 이렇게 신경 써본 적은 처음이다. 아직 보완해야 할 점이 많겠지만, 이렇게 코드를 짜고 보니 깔끔한 것이 느껴지고, 뿌듯했다. 마치 코딩테스트 문제를 내 힘으로 풀었을 때의 기분이었다. 그리고, 앞으로 코드를 짜며 분명히 도움이 될 도움이라 생각한다.
- 앞으로도 코드를 짤 때 이전에 짠 것 복사-붙혀넣기 하지 말고, '어떻게 하면 직관적으로, 간결하게, 명령적으로 짤 수 있을까?' 고민하면서 짜도록 하자.