FE
[SvelteKit] 하나의 컴포넌트, 두 개의 얼굴 (mode, prop 하나로 글쓰기/수정 기능 모두 구현하기)
| 도입: 혹시 아직도 복붙하고 계신가요?
안녕하세요 팡일입니다.
웹 서비스를 만들다 보면 누구나 마주치는 기능이 있습니다. 바로 ‘작성’과 ‘수정’ 기능입니다. ‘새 글 작성’ 페이지를 열심히 만든 뒤 얼마 지나지 않아 똑같이 생긴 ‘글 수정’ 페이지를 또 만들어야 하죠.
이때 자연스럽게 이런 생각이 듭니다.
“UI도 거의 같고, 동작도 비슷한데… 그냥 복사해서 조금만 고치면 되지 않을까?”
하지만 이 선택은 미래의 나에게 큰 고통을 남깁니다. UI를 조금 수정할 때마다 두 페이지를 모두 고쳐야 하고, 미묘하게 동작이 달라지는 버그가 생기며 유지보수 난이도는 점점 높아집니다.
그래서 저는 이번 Svelte 프로젝트에서 이 문제를 해결하기 위해 단 하나의 컴포넌트 + mode prop 으로 작성/수정 기능을 모두 처리하는 구조를 완성했습니다. 적은 비용으로 중복을 제거하고, 유지보수성을 크게 올리는 방식이었죠.
아래에서 그 과정을 공유드리겠습니다.
| 문제 제기: 작성/수정 페이지를 따로 만들 때의 비효율
작성 페이지와 수정 페이지를 별도로 구성하면 코드가 이렇게 흘러갑니다.
<!-- WritePage.svelte -->
<h1>새 글 작성</h1>
<input bind:value={title} />
<textarea bind:value={content}></textarea>
<button on:click={handleSubmitNewPost}>제출하기</button><!-- ModifyPage.svelte -->
<h1>글 수정하기</h1>
<input bind:value={title} />
<textarea bind:value={content}></textarea>
<button on:click={handleUpdatePost}>수정 완료</button>겉으로 보기에는 간단해 보이지만, 실제로는 아래 문제가 계속 쌓입니다.
중복된 코드가 너무 많음 → 새로운 입력 필드가 생기면 두 곳을 모두 수정해야 함
버그 발생률 증가 → 한쪽 파일만 수정하고 다른 쪽은 놓칠 가능성
시간이 흐를수록 두 파일이 점점 달라짐 → 유지보수 난이도가 올라감
이 구조는 작을 때는 괜찮지만, 기능이 쌓일수록 팀 전체의 속도를 떨어뜨리는 치명적인 패턴입니다.
| 해결책 : 'mode' prop 하나로 재사용 구성하기
핵심 아이디어는 단순합니다.
“하나의 폼 컴포넌트를 만들고, 작성인지 수정인지 모드만 prop으로 넘기자.”
즉, UI 구조를 하나로 통합하고 동작만 mode = 'write' | 'modify' 값으로 분기하도록 만드는 것입니다.
1) 라우팅 단계에서 mode 전달하기
라우트에 따라 작성/수정 모드를 결정합니다.
/write → mode = "write"
/modify/[id] → mode = "modify"
<!-- src/routes/write/+page.svelte -->
<WriteContainer mode="write" /><!-- src/routes/modify/[id]/+page.svelte -->
<script>
import { page } from '$app/stores';
const { id } = $page.params;
</script>
<WriteContainer mode="modify" {id} />이제 WriteContainer는 스스로 어떤 역할을 수행해야 하는지 알 수 있습니다.
2) mode에 따라 서로 다른 초기 데이터 로딩
WriteContainer는 mode 값에 따라 다음과 같이 동작합니다.
modify 모드 → 해당 id의 기존 데이터를 불러와 store에 채움
write 모드 → store를 초기화하여 빈 폼 제공
// WriteContainer.svelte 내부
export let mode;
export let id = null;
onMount(async () => {
if (mode === 'modify' && id) {
const post = await getPostById(id);
hydrateWriteStore(post);
} else {
resetWriteStore();
}
});3) mode에 따른 UI + 제출 로직 분기
UI는 mode 값만으로 간단하게 바꿀 수 있습니다.
<h1>{mode === 'write' ? '새로운 회고 작성' : '회고 수정하기'}</h1>핵심 제출 로직도 분리하여 다음처럼 처리합니다.
// writeActions.ts
export function submit(mode, formData, postId) {
validate(formData);
if (mode === 'write') {
saveNewPost(formData);
} else {
updatePost(postId, formData);
}
}이 구조가 갖는 진짜 힘은 UI와 로직을 모두 단일 책임으로 묶어낼 수 있다는 점에 있습니다.
| 결론: 이 구조가 주는 3가지 확실한 이점
1) 단일 소스(Single Source of Truth)
: 폼 UI가 하나로 통합되기 때문에 파일을 한 곳만 관리하면 작성/수정 페이지가 모두 업데이트됩니다.
2) 일관된 UX 확보
: 작성/수정 둘 다 동일한 컴포넌트를 사용하니 전체적인 사용자 경험이 통일됩니다.
3) 유지보수성 및 확장성 증가
: 새로운 필드를 추가하거나 로직을 개선할 때 한 번만 수정하면 됩니다. 버그 발생 확률도 자연스럽게 줄어듭니다.
| 마무리하며
“작성”과 “수정” 기능은 서로 뗄 수 없는 쌍둥이 기능입니다. 이 두 기능을 별도로 관리하는 대신, mode prop만으로 하나의 스마트 컴포넌트로 묶어내는 패턴은 장기적으로 코드 품질과 개발 속도를 모두 끌어올립니다.
혹시 지금 만들고 있는 프로젝트에서도 비슷한 고민을 하고 계신가요? 그렇다면 이 패턴을 꼭 한 번 적용해 보시길 추천드립니다. 단순한 구조 변경이지만, 실제 개발 효율은 생각보다 훨씬 크게 개선됩니다.