사실 부트캠프의 목적은 실전 프로젝트라고 생각할 만큼 갖는 의미가 크다.
그걸 연달아서 두번 할 수 있게 된 것도 정말 감사한 일이지만,
나는 언제나 인복이 좋았기 때문에 이번에도 여지없이 좋은 사람들과 함께 프로젝트를 할 수 있었다.
이번에는 내가 주인공이 아니었기도 하고,
두번째 하는 실전 프로젝트였기 때문에 부담감은 없었다.
첫 실전 프로젝트에서 욕심도 너무 많았고 자기객관화도 잘 되지 않아서
'나'를 어떻게 보여줄까에 너무 강박적으로 집착했었다가
오히려 슬럼프가 왔던 것과는 대조적이다.
그럼에도 정말 열심히 했고, 내가 할 수 있는거라면 최선을 다했다.
프로젝트를 진행하면서 가장 힘들었던 것은 팀원들도 아니고, 기술적인 어려움도 아니고
항상 주변 상황이었다.
가족이 사기에 당하기도 하고, 교통사고가 나기도 하고,
하다못해 요리하다가 손가락 끝이 잘려나가기도 했다.
새삼스럽지만 세상이 시련을 주더라도 결국 나의 일은 어쨌든 해야만 하는 것이다.
기술적인 회고보다 프로젝트의 소감을 정리하려고 했는데,
react-hook-form 에 관한 회고는 조금 필요할 것 같다.
react-hook-form 은 라이브러리 자체적으로 비제어 컴포넌트를 활용하여
직접 onChange 같은 이벤트를 사용할 때보다 성능 최적화에 큰 장점을 가지고 있다.
폼 자체적으로만 따져봤을 때 이보다 더 간결하게 코드를 작성할 수 없고,
유효성검사에 대한 자체적인 지원 덕분에 에러처리가 굉장히 쉽다.
개인적으로 굉장히 사용하기 편했고 앞으로도 자주 사용할 것 같다.
이는 useReducer 로 상태를 관리한 폼과의 비교를 통해서 조금 더 명확하게 비교할 수 있었다.
const reduceFormData = (state, action) => {
switch (action.type) {
case "SET_FORM_DATA":
return { ...state, [action.key]: action.value };
default:
return state;
}
};
const [formState, dispatch] = useReducer(reduceFormData, {});
useEffect(() => {
if (data) {
dispatch({
type: "SET_FORM_DATA",
key: "name",
value: data.data.data.name,
});
dispatch({
type: "SET_FORM_DATA",
key: "gender",
value: data.data.data.gender,
});
dispatch({
type: "SET_FORM_DATA",
key: "birth",
value: data.data.data.birth,
});
dispatch({
type: "SET_FORM_DATA",
key: "significant",
value: data.data.data.significant,
});
}
}, [data]);
<StyledProfile.InfoWrapper>
<li>
<StyledChildManage.SubTitle>이름</StyledChildManage.SubTitle>
<AutoResizeInput
defaultValue={data?.data?.data?.name}
readOnly={!isFixMode}
onChange={(e) =>
dispatch({
type: "SET_FORM_DATA",
key: "name",
value: e.target.value,
})
}
/>
</li>
<li>
<StyledChildManage.SubTitle>성별</StyledChildManage.SubTitle>
<AutoResizeInput
defaultValue={data?.data?.data?.gender ==="MALE" ? "남자" : "여자"}
readOnly={!isFixMode}
onChange={(e) =>
dispatch({
type: "SET_FORM_DATA",
key: "gender",
value: e.target.value,
})
}
/>
</li>
<li>
<StyledChildManage.SubTitle>생년월일</StyledChildManage.SubTitle>
<AutoResizeInput
defaultValue={data?.data?.data?.birth}
readOnly={!isFixMode}
onChange={(e) =>
dispatch({
type: "SET_FORM_DATA",
key: "birth",
value: e.target.value,
})
}
/>
</li>
</StyledProfile.InfoWrapper>
</StyledProfile.ProfileWrapper>
<StyledChildManage.SubTitle>특이사항</StyledChildManage.SubTitle>
<StyledProfile.SignificantArea
readOnly={!isFixMode}
defaultValue={data?.data?.data?.significant}
onChange={(e) =>
dispatch({
type: "SET_FORM_DATA",
key: "significant",
value: e.target.value,
})
}
/>
<StyledProfile.BtnWrapper>
<Buttons.Filter
colorTypes={!isFixMode ? "" : "primary"}
outlined={!isFixMode}
onClick={handleFixChildProfile}
>
{!isFixMode ? "수정하기" : "수정완료"}
</Buttons.Filter>
</StyledProfile.BtnWrapper>
</StyledProfile.Wrapper>
회고를 정리하면서 다시 코드를 보고 있는데
data.data.data.name 같은 이상한 value 를 보고 있자니
다시 머리가 아파오는 것 같다..
어쨌든 부가적인 설명을 하자면,
useReducer는 크게 새로운 개념은 아니고
useState로 하는 상태관리를 조금 조금 더 깔끔하게 하나의 state로 관리할 수 있게한다.
폼 데이터 처럼 여러개의 관련된 상태값을 관리할 때는 useState 보다 유리하다.
위의 코드에서는 reduceFormData
함수가 SET_FORM_DATA 라는 액션을 통해서
폼을 업데이트 하며 useEffect로 data 가 변경될 때마다
폼의 상태를 업데이트 할 수 있도록 구현했다.
fixMode 일 때만 수정할 수 있도록 isFixMode 라는 상태를 추가적으로 만들었고,
handleFixChildProfile
함수를 통해 isFixMode 가 true 일 때만
mutate 하는 간단한 프로필 수정 로직이다.
이 로직은 회원가입 로직을 어느정도 완성한 뒤에 개발했는데,
readOnly input 을 이용해 서버에서 불러온 데이터를 바인딩하고
readOnly 를 false 로 바꾸어 데이터를 수정할 수 있도록 했다.
이 로직은 일부러 리액트 훅 폼을 사용하지 않았다.
두 가지 이유가 있는데,
첫번째는 리액트 훅 폼을 사용하지 않으면서 상태를 컨트롤 하는 폼을 만들어
비교분석을 해보고 싶다는 이유였고,
두번째는 useReducer의 실 적용 예시가 필요했기 때문이다.
애초에, 에러 핸들링 로직을 서버에서 처리할 수 있었기 때문에 가능한 방법이기도 하다.
두 가지 방식을 비교해봤을 때
useReducer 를 사용하면 유연하게 상태관리를 할 수 있다는 점은 장점이라고 볼 수 있겠다.
리액트 훅 폼은 어쨌든 라이브러리의 양식을 지켜야 하기 때문에
정형화된 규칙을 크게 벗어날 수는 없다.
useReducer 는 이러한 관점에서 로직의 복잡성이 증가한다면 분명 좋은 사용처가 될 수 있다.
다만, 사람이 생각하는 범주, 초보 개발자로써 구현할 수 있는 한계점은
분명히 있기 때문에 크게 문제가 되지는 않는다고 생각한다.
가장 큰 문제점은 위에서 말한 에러 핸들링 로직에 대한 처리 문제라고 할 수 있다.
만약, 구현하고자 했던 폼이 에러 핸들링에 대한 처리 분기가 까다로운 로직이었다면
직접 구현했을 경우, 코드의 양이 비약적으로 증가할 수 밖에 없었을 것이다.
결론적으로, 앞으로는 어떤 폼의 형태를 가진 코드라면
코드량도 적고, 유효성 검사 등의 에러핸들링 로직을 지원하면서
비제어 컴포넌트로 최적화까지 신경쓸 수 있는
리액트 훅 폼을 사용하는 것이 좋을 것 같다는 생각을 많이 했다.
여담이지만 useReducer 관련 훅을 커스텀 훅으로 정리했으면 조금 더
깔끔하게 코드를 작성할 수 있었을 것 같다.
지난 프로젝트도 물론 좋은 사람들과 함께했지만,
이번 프로젝트에서는 내가 알고있는 모든 지식을 동원해서라도
꼭 팀원들이 성공적인 프로젝트를 진행할 수 있었으면- 하고 바랬을 정도로
함께하는 동안 너무 즐거웠다.
특히 프론트 팀장분이 기억에 오래 남을 것 같다.
처음에 프로젝트를 진행했을 당시,
코드 컨벤션이나 깃을 다루는 법, (심지어 풀 리퀘스트 하는 방법을 모르셨다)
리액트 폴더 디렉토리 같은 부트캠프에서는 알려주지 않지만 프로젝트에는 중요한 것들에 대해서
많은 부분을 알지 못하고 계셨고 그런 것들을 모르는 자신에 대해서 굉장히 답답해하셨다.
그뿐 아니라, 지금까지 코드의 구현에 급급해서 최적화나 클린코드에 대해서 생각할 수 없었다고 하셨다.
그런 상황에서 프론트 팀원도 한분 하차하셔서 많이 속상하셨으리라.
그래서 이 부분을 많이 신경써서 도와드렸다.
나도 많이 고민했던 부분이고, 나도 많이 도움받았던 부분이니까.
내가 아는 선에서는 충분히 도움을 줄 수 있을거라고 생각했다.
코드 컨벤션을 정하는 일 부터, 깃 플로우를 프로젝트에 적용하는 일,
프로젝트의 디렉토리 설계나 정리하는 방법, 나름의 코드 설계 원칙등을
매일같이 서로 상의하면서 바꿔나가고 적용해나갔다.
프로젝트를 진행한지 한 달이 넘었을 때, 팀장님께서는 이러한 방법들에 대해서
스스로 어떤 점이 좋을지 고민하고 나름의 방식을 적용해 나갔다.
그리고 항상 팀장으로써 어떤 기술이 프로젝트에 도움이 될지 끊임없이 연구했다.
프레이머 모션이나 로딩화면 같은 것도 전혀 모르시던 상태에서 하나하나 적용시켰다.
결국에는 초기 구상했던 것 보다 훨씬 예쁘고 멋진 웹 사이트를 만들 수 있었다.
그런 본인의 발전에 대해서, 팀장님께서는 나에게 고마워하셨다.
고작 부트캠프 두 기수 먼저 했다고 선배이고, 더 많이 알고 이런
선민의식 같은 것은 전혀 가지고 있지 않다.
다만, 부족한 내가 어떻게 부족한 점을 보완할 수 있을지 항상 생각하고
나름의 원칙들을 공유했을 뿐이고, 성장은 팀장님 본인이 이룬 결과다.
오히려 프로젝트 자체에 책임감과 애정을 가지고 잠도 줄여가면서 열심히 하시는 팀장님의 모습을 보고,
더 자극받아서 열심히 하게 됐던 것 같다.
어느 순간부터 문득 든 생각인데,
프론트엔드의 기술 숙련도라는건 결국엔 자바스크립트의 숙련도와 정비례한다고 생각이 들었다.
그 어떤 라이브러리도 사실 '러닝커브'의 문제는 크지 않다.
레퍼런스가 많은 것도 있고, 최근에는 GPT 를 통해서 예시 코드도 확인할 수 있으며,
공식문서는 점점 친절해진다고 생각되기 때문이다.
생각한 내용을 결과물로 구현하는 것은 최근에는 그렇게 어렵지 않다.
구현이 완료된 시점에서 필요해 의한 리펙토링을 꾸준하게 하는 것이 베스트.
물론, 아는만큼 설계할 때 제대로 로직을 설계한다면
리펙토링을 진행할 때의 수고로움이 줄어들겠지만, 애초에 완벽한 설계는 할 수 없다.
결론적으로, '어떻게 하면 효율적이고 예쁜 코드를 짤까'를 기획단계에서 고민하는 것 보다,
먼저 구현하고 그 뒤에 필요에 의한 리펙토링을 하는 편이 훨씬 덜 수고롭다는 것.
이런 결론이 이번 프로젝트에서 얻은 가장 값진 경험이라고 할 수 있겠다.