개요

디지털 명함 에디터를 개발하면서 가장 깊이 고민했던 기능 중 하나는 바로 Undo/Redo 기능이었다. 단순해 보이지만 실제로 구현하려면 복잡한 상태 관리, 요소 추적, ID 문제, 그리고 사용자 경험까지 고려해야 하는 만만치 않은 문제였다. 이 글에서는 Undo/Redo 기능을 개발하며 겪은 시행착오, 고민의 흔적들, 그리고 최종적으로 도달한 해결책을 공유하고자 한다.

1. "되돌리기" 기능의 중요성

에디터에서 사용자가 작업 중에 실수했을 때 "되돌리고 싶다"는 요구는 필수적이다. 이는 단순히 상태를 관리하는 것을 넘어서, 변경 전의 상태를 저장하고, 그 상태로 복원하는 메커니즘이 필요하다는 것을 의미한다.

2. 첫 번째 접근: 개별 요소 기반 저장 방식

처음에는 가능한 한 단순하게 접근하고 싶었기에, 개별 요소의 변경 사항만 기록하는 방식을 선택했다. 이러한 선택에는 다음과 같은 이유가 있었다:

이러한 판단으로 초기 구현은 다음과 같은 구조로 시작했다:

export interface EditorState {
  histories: TextElement[];  // 개별 요소들의 기록
  historyIdx: number;        // 현재 기록 위치
  showElements: TextElement[]; // 화면에 표시되는 요소들
  // ... 기타 상태 및 함수들
}

3. 발생한 문제점들

이 접근 방식은 초기에는 괜찮아 보였지만, 실제로 구현하고 테스트하면서 여러 문제점이 드러났다.

3.1. 첫 번째 요소만 기록되는 문제

const updatedElement = state.histories.map(el => el.id === id ? { ...el, ...updates } : el);
const updatedHistories = [...state.histories, updatedElement[0]];

이 코드에서는 map() 함수가 전체 배열을 반환하는데, 그 중 [0]번 요소만 히스토리에 추가했다. 이로 인해 수정한 요소가 아닌, 항상 배열의 첫 번째 요소만 기록되는 문제가 발생했다. 실제로 디버깅해보니 내가 수정하고 있는 요소의 ID는 048c...인데, 히스토리에 저장되는 것은 매번 0032...와 같은 고정된 ID였다.