- Published on
useReducer -> zustand 마이그레이션 작업
- Authors
- Name
- Charles
엄청 오랜만에 블로그 글을 작성한다. 반기 회고를 먼저 작성하려고 하였으나 갑작스럽게 바뀐 회사 사정으로 인해 다음달부터 취준생이 되어버려서 반기 회고는 패스하고 앞으로 블로그에 추가 하고 싶었던 기능, 개선 하고 싶었던 부분을 찾아서 수정하는 부분에 대해서 기록하도록 해야겠다.
기존 reducer를 제거하고 zustand로 마이그레이션 작업
- 먼저 기존 reducer에서 zustand로 마이그레이션 작업을 해야겠다고 생각한 부분은 기능상에 전혀 문제가 없지만 reducer를 사용하면서 이전에도 redux를 사용하면서도 느꼈던 부분이지만 보일러플레이트에 대한 부분이 개인적으로 개선하고 싶었던 부분이었던 것 같다.
- 그리고 zustand를 계속 사용하면서 필요한 부분만 자유롭게 store를 만들어서 사용할 수 있는 장점이 있다보니깐 제일 처음 개선하고 싶은 작업이 되었다.
src/component/editor/reducer
import { Post } from '@/types'; export enum ActionKind { title = 'title', sub_title = 'sub_title', markdown = 'markdown', tags = 'tags', clear = 'clear', } export type Action = | { type: ActionKind.title; payload: string; } | { type: ActionKind.sub_title; payload: string; } | { type: ActionKind.markdown; payload: string; } | { type: ActionKind.tags; payload: string[]; } | { type: ActionKind.clear; payload: null; }; export function postReducer(state: Post, action: Action): Post { const { type, payload } = action; switch (type) { case ActionKind.title: return { ...state, title: payload, }; case ActionKind.sub_title: return { ...state, sub_title: payload, }; case ActionKind.markdown: return { ...state, markdown: payload, }; case ActionKind.tags: return { ...state, tags: payload, }; case ActionKind.clear: return { title: '', sub_title: '', markdown: '', tags: [], }; default: throw new Error('존재하지 않는 액션 타입입니다.'); } } export const initialState: Post = { title: '', sub_title: '', markdown: '', tags: [], };
위의 reducer를 이용해서 사용할 때는 src/component/editor/EditorContainer.tsx
에서 중앙 관리하는 방식으로 정의해서 사용하였는데 그 당시에는 이렇게 관리하는게 편할 것 같다는 생각을 했던 것 같다.
그런데 해당 컴포넌트에서 모든 함수를 중앙으로 관리하는 것 보다 Title
, SubTitle
, Tag
의 아이들은 본인이 사용하는 함수를 props
로 전달받아서 사용하는게 아닌 각자가 책임을 지도록 하는게 맞다는 생각을 하게되어서 마이그레이션 작업을 진행하게 되었다.
export default function EditorContainer() { const [state, dispatch] = useReducer(postReducer, initialState); const handleTitleChange = (e: ChangeEvent<HTMLInputElement>) => { const { value } = e.target; dispatch({ type: ActionKind.title, payload: value }); }; const handleSubTitleChange = (e: ChangeEvent<HTMLInputElement>) => { const { value } = e.target; dispatch({ type: ActionKind.sub_title, payload: value }); }; const handleMarkdownChange = ( value?: string, event?: ChangeEvent<HTMLTextAreaElement>, state?: ContextStore, ) => { value && dispatch({ type: ActionKind.markdown, payload: value }); }; const handleImageMarkdown = (value: string) => { dispatch({ type: ActionKind.markdown, payload: value, }); }; return ( <div className="h-[65vh] w-full p-4 dark:bg-transparent"> <Title title={state.title} onChange={handleTitleChange} /> <SubTitle sub_title={state.sub_title} onChange={handleSubTitleChange} /> <Separate /> <Tags tags={state.tags} dispatch={dispatch} /> <Suspense fallback={<Skeleton className="h-[623px] w-full" />}> <MDEditor value={state.markdown} onChange={handleMarkdownChange} height={500} onDrop={async event => { await onImagePasted(event.dataTransfer, handleImageMarkdown); }} /> </Suspense> <div className="flex w-full justify-between border-b border-t py-3"> <GoBackButton /> <PublishButton state={state} dispatch={dispatch} /> </div> </div> ); }
reducer
-> zustand
로 변경하면서 많이 간결해 진 부분도 좋았다.
import { Post } from '@/types'; import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; type PostState = { addPost: Post; }; type PostAction = { setAddPostInit: () => void; setAddPost: (post: Post) => void; }; export const usePostStore = create( immer<PostState & PostAction>(set => ({ addPost: { title: '', sub_title: '', markdown: '', tags: [] }, setAddPostInit: () => set(state => { state.addPost.title = ''; state.addPost.sub_title = ''; state.addPost.markdown = ''; state.addPost.tags = []; }), setAddPost: post => set(state => { state.addPost = post; }), })), );
위에서 EditorContainer.tsx
에서 중앙으로 관리하던 것에서 이제는 Title.tsx
, SubTitle.tsx
, Tag.tsx
에서 각각 본인이 해야할 작업에 대해서만 처리를 하도록 하였다.
export default function Title() { const { addPost, setAddPost } = usePostStore(); const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { value } = e.target; setAddPost({ ...addPost, title: value }); }; return ( <input value={addPost.title} className="h-28 w-full px-[12px] text-5xl font-bold caret-primary-500 focus:outline-none dark:bg-transparent" placeholder="제목을 입력하세요" onChange={handleTitleChange} /> ); }
만약 Title
, SubTitle
, Tag
이 친구들이 다른 곳에서도 재사용 될 가능성이 있다고 한다면 컴포넌트의 구조는 또 다르게 되었을 것 이라 생각된다.
하지만 내가 구성한 블로그에서는 추가적인 재사용 가능한 컴포넌트가 되지 않을 것 같아서 관심사를 분리해서 개선한 컴포넌트도 완료하였다.
다음 이야기
- 다음 작업할 부분은 블로그에 댓글, 대댓글이 가능한 컴포넌트 및 서버로직을 만드려고 계획하고 있다.
- 프론트만 하다보니깐 서버를 직접 다 구현하기 보다 현재 구축되어있는
supabase
를 이용해서 테이블을 정의하고 댓글, 대댓글에 필요한 로직을 구성해 보면서 기존에 리버스쿨을 하면서 프론트로서 api 연동하고 화면만 만들었던 것에 그치지 않고 내가 보지 못했던 부분에 대해서 충족시키는 시간을 갖도록 할 것이다.