const {projectId} = useParams(); const { sendCommentMutation } = useSendCommentMutation( Number(projectId) ) ...
const {projectId} = useParams(); const { deleteCommentMutation } = useDeleteCommentMutation( Number(projectId )ㄱ ...
const {projectId} = useParams(); const { editCommentMutation } = useEditCommentMutation( Number(projectId ) ...
현재 프로젝트 상세정보를 받아올때 뿐만 아니라 Mutation Hook의 함수를 받아올때 주소의 프로젝트 아이디가 필요하기 때문에 매번 useParmas를 통해 호출하고 있다.
하지만, 현재 projectId가 유효한지에 대한 검증을 하고 있지 않다. 만약 전체코드에서 그것을 추가한다고 해도 확장성과 유지보수성을 고려한다면 이것을 공통적으로 해주는 무언가가 있으면 좋다.
보통의 경우에는 hooks를 사용하면 되지 않을까 싶지만 코드가 길어지고 여러 훅들을 사용하다보면 자연스럽게 코드를 받아오는 부분과 얼리리턴문의 물리적 거리가 멀어져서 가독성도 떨어지고, 로직내에서 somethingId를 사용하려면 값이 있는지 매번 확인해야 하는 번거로움이 있다.
따라서, HOC를 통해 재사용성을 극대화해보자.
HOC는 옛날에 사용되던 리액트 디자인 패턴중 하나이지만 props나 hooks으로 해결이 안되는 경우도 있어서 아직도 유용해보인다.
HOC는 여러 컴포넌트에서 공통되는 로직을 담은 컴포넌트로 인자로 들어온 컴포넌트에 로직을 수행하거나 스타일을 입혀서 그렇게 변화된 컴포넌트를 return하는 기능을 가진다.
내 코드에서는 useParams로 projectId를 받아오는 로직을 공통적으로 HOC 컴포넌트에서 해결하면 된다.
다행히 팀원중 한분이 이것에 관해 추천해주시면서 관련된 링크를 보내주셔서 쉽게 구현할 수 있었다..!
// HOC 컴포넌트 import { ComponentType } from "react" import { useParams } from "react-router-dom" export interface ProjectIdProps { projectId: string } export const withProjectId = <P extends ProjectIdProps>( WrappedComponent: ComponentType<P>, ) => { const ComponentWithProjectId = (props: Omit<P, keyof ProjectIdProps>) => { const { projectId } = useParams<Record<string, string>>() if (!projectId) { return null } return ( <WrappedComponent {...(props as P)} projectId={projectId} /> ) } return ComponentWithProjectId }
// 사용하는 컴포넌트 const CommentsForm = ({ projectId }: CommentsFormProps) => { const { sendCommentMutation } = usePostCommentMutation(Number(projectId)) ... } export default withProjectId(CommentsForm)
사용하는쪽에서는 검증된
projectId
를 받아서 사용한다.그리고 export할때는 Provider로 감싸듯이 HOC컴포넌트로 감싸서 반환한다.
Component Type
말그대로 HOC가 감싸는 컴포넌트는 다양한 컴포넌트가 올 수 있으므로 제너릭으로 P로 감쌌다.
Omit
유틸리티 타입으로 여러가지를 담고있는 타입객체에서 특정 타입을 뺀 새로운 객체를 반환한다.
상위에서 이미 projectId를 받았는데 사용하는곳에서 다른 props들을 유연하게 받기 위해서 Omit을 사용하였다.
Record
유틸리티 타입으로 객체의 키와 value의 지정할 수 있다.
훅의 경우는 그냥 훅을 호출하면서 뒤에 <>안에 interface를 넣으면 되는데 useParams는 에러가 난다.
useParams
의 반환 타입이 기본적으로 Record<string, string | undefined>
형태이기 때문이다.그래서 인터페이스를 사용할거면
export interface ProjectIdProps { [key: string]: string; projectId: string; }
이렇게 key값을 유연하게 받을 수 있어야하고
기본적으로 Recode타입이 지정되어있으니 Record를 사용해 key와 value로 모두 string을 지정해주면 된다.
문제점
확장성을 고려하여 여러 컴포넌트에서 쓸 수 있도록
projectId
가 아닌 somethingId
로 해보려 했지만 url을 가져올때 페이지마다 라우터에 묶여있는 아이디들이 projectId, profileId, .. 등으로 다양하기 때문에 우선은 projectId로만 적용해봤다.어떻게 해야할지 모르겠다.
마치며
HOC를 처음 접해봤는데 예전에는 공통로직은 이런식으로 뺐다고 생각하니까 예전 개발자들은 참 대단한 것 같다.
아직, 다른 디자인 패턴과의 장/단점, 차이점을 100% 이해하지는 못했지만 이런 방법도 있구나 라는 것을 깨달을 수 있었다.
그리고 타입스크립트의 제너릭 문법이나 유틸리티 타입등에 대한 지식이 부족해서 예시코드를 보고 이해하는데 한참이 걸려서 자괴감이 드는 순간이 있지만 극복할 수 있어서 다행이다..
오류
vite에서 eslint-refresh관련하여 오류를 자꾸 낸다.
보통 refresh는 한 파일에서 jsx,tsx외에 utils를 같이 export하는 경우에 오류를 내는 것인데 전혀 그렇지 않은상황인데 내고 있다..
vite + react 환경에서 HOC를 사용했을때 나타나는 고질적인 문제인 것 같긴한데.. 검색해봐도 잘 모르겠어서 lint를 무시해서 임시로 해결해놨다.