2024년 5월 7일
현재 해당 블로그의 글쓰기 기능의 코드는 대략 이렇다.
export default function WriteForm({}) {
const postTitleHandler = () => {
// 게시글 제목 작성 핸들러
};
const postBodyHandler = useCallback(() => {
// 게시글 본문 작성 핸들러
}, []);
const postThumbnailImageHandler = () => {
// 게시글의 썸네일 이미지 핸들러
};
const postDescriptionHandler = () => {
// 게시글에 대한 설명 추가 핸들러
};
const openSubmitFormHandler = () => {
// 게시글 등록
};
const openSubmitFormHandler = () => {
// 게시글 등록
};
return (
<>
<TitleSection />
<ContentSection/>
<ButtonSection />
{isOpenSubmitForm && (
// 썸네일 이미지, 게시글 설명 등 추가 컴포넌트
)}
</>
);
}
가독성을 위하여 필요한 부분만 골라서 적은것이어서 간단해 보일 수 있다.
하지만 글 작성 중에 드래그앤드랍으로 이미지를 삽입하면, 해당 이미지의 cdn 링크도 받아오도록 되어있는 기능도 있기에 상당히 지저분하다.
현재 코드의 문제점이라고 한다면 관심사의 분리가 되어있지 않으며, 변동사항에 유연하게 대처하지 못하고 있는 점
이라고 생각한다.
이를 한번 컴파운드 패턴으로 리팩토링을 진행해보려고 한다.
컴파운드 패턴은 말그대로 합성 패턴
이라고 이해하면 된다 .
여러개의 컴포넌트를 합성하여서 UI를 분리하여 가독성을 좋게 만들어줄수 있고, 변경사항이 생겼을때 유연하게 대처가 가능하다.
보통 네임스페이스 패턴과 함께하여 가독성을 높여서 사용한다.
import { createContext } from 'react';
import { WritePostFormContextType } from 'types';
export const WritePostFormContext = createContext<WritePostFormContextType>({
postState: {
title: '',
description: '',
body: '',
thumbnailImage: null,
},
postTitleHandler: () => {},
postDescriptionHandler: () => {},
postBodyHandler: () => {},
postThumbnailImageHandler: () => {},
postSubmitHandler: () => {},
appendImageToContent: () => {},
});
const useWritePost = ({ title, description, body, accessToken }: UseWritePostFormProps) => {
// 기존 상태값과 핸들러 함수들
return {
postState,
postTitleHandler,
appendImageToContent,
postBodyHandler,
postThumbnailImageHandler,
postDescriptionHandler,
postSubmitHandler,
};
};
export default useWritePost;
export default function WritePostForm({
children,
title,
description,
body,
accessToken,
thumbnailImageSrc,
}: PropsWithChildren<UseWritePostFormProps>) {
const {
postState,
postTitleHandler,
appendImageToContent,
postBodyHandler,
postThumbnailImageHandler,
postDescriptionHandler,
postSubmitHandler,
} = useWritePost({
title,
description,
body,
accessToken,
thumbnailImageSrc,
});
const contextValue = useMemo(
() => ({
accessToken,
postState,
postTitleHandler,
appendImageToContent,
postBodyHandler,
postThumbnailImageHandler,
postDescriptionHandler,
postSubmitHandler,
thumbnailImageSrc,
}),
[
accessToken,
postState,
postTitleHandler,
appendImageToContent,
postBodyHandler,
postThumbnailImageHandler,
postDescriptionHandler,
postSubmitHandler,
thumbnailImageSrc,
],
);
return (
<WritePostFormContext.Provider value={contextValue}>{children}</WritePostFormContext.Provider>
);
}
위에서 useMemo
를 사용해서 핸들러들을 메모이제이션 해주었다.
컨텍스트를 사용할때의 주의사항이기도 한데, 컨텍스트를 구독하는, 즉 useContext
를 사용하여 컨텍스트의 값을 가져오는 컴포넌트들은 상태값이 업데이트 될때 전부 리렌더링 된다.
예를 들어서 Title 컴포넌트에서 상태 변화가 일어나더라도, Body 컴포넌트도 리렌더링이 되는 것이다.
그렇기 때문에 useMemo
같은 최적화 hook을 이용하여 필요한 상태값만 업데이트 되도록 해야한다.
export default function WritePostFormDescription() {
const { postState, postDescriptionHandler } = useContext(WritePostFormContext);
return (
<>
<textarea
value={postState.description}
placeholder="글의 짧은 간략한 설명을 입력하세요"
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => postDescriptionHandler(e)}
/>
</>
);
}
useContext
를 이용하여 필요한 값들을 가져와서 상태를 업데이트 해준다.
WritePostForm.Title = WritePostFormTitle;
WritePostForm.Content = WritePostFormContent;
WritePostForm.Thumbnail = WritePostFormThumbnail;
WritePostForm.Description = WritePostFormDescription;
WritePostForm.SubmitButton = WritePostFormSubmitButton;
WritePostForm.InfoCard = WritePostFormInfoCard;
육안으로 보기에도 가독성이 좋아진 것을 확인할 수 있다.
위에서 말했듯, 하나의 컨텍스트를 공유하는 자식 컴포넌트 A, B, C, D가 있다고 가정해보자.
이런 경우에 A의 상태값이 업데이트 됐을뿐인데, B, C, D까지 리렌더링이 일어나게 된다.
그렇기 때문에 컴파운드 패턴은 최적화를 염두에 두고 사용해야한다.
컴파운드 패턴을 적용하기 위해선 컨텍스트를 만들어주어야하고, 그것을 Provider에서 불러와서 value
에 넣어주어야한다.
또 context를 만들어줄때, 해당 컨텍스트 값들에 대한 타입을 지정해주어야한다.
이 과정에서 사용해야할 상태값이 많은 경우, 조금은 번거로울수 있겠다라는 생각이 들었다.