사용자가 마우스로 텍스트를 드래그하면 선택된 내용을 첨삭하는 기능을 구현했다. 해당 기능은 원하는 동작으로 잘 작동했지만, 이번 리팩토링 과정에서 치명적인 버그를 발견하게되었다. 사용자가 마우스 이벤트가 없는 지역에서 시작해 이벤트가 있는 p태그로 드래그할 때 첨삭 액션이 동작하는 버그가 있었다.
문제의 원인 getSelection API의 동작방식에 있었다. getSelection은 사용자가 드래그한 전체 영역을 기록하는데, 이로 인해 다른 지역에서 드래그하여 p태그로 넘어오면 p태그의 마우스 이벤트가 발생하고 첨삭 액션이 수행되는 상황이 발생했다.
문제를 해결하기 위해 Range객체를 사용하여 선택영역을 검사하기로 했다. Range객체는 선택된 텍스트의 시작점와 끝을 포함하는 DOM 정보를 제공하므로, 선택 영역이 특정 태그 내에서 이루어졌는지 검증할 수 있다.
if (range) {
const startTag = range.startContainer.parentElement;
const endTag = range.endContainer.parentElement;
const startTargetNodeTag = startTag?.nodeName.toLowerCase() === 'p';
const endTargetNodeTag = endTag?.nodeName.toLowerCase() === 'p';
if (!startTargetNodeTag || !endTargetNodeTag) return;
}
이 접근법은 어느 정도 문제를 해결하는듯 보였지만, 여전히 드래가 이벤트가 존재하지않는 P태그 외부에서 시작해도 이벤트가 존재하는 p태그로 넘어오면 첨삭 기능이 동작하는 문제가 있다. 이 문제를 완전히 해결하기 위해 추가적인 검토가 필요하다.
<DraggedAnswer
startText={시작점부터타겟까지문장}
selectedText={preSelectedText}
endText={타겟부터끝나는지점문장}
selectedColor={selectedColor}
onRemove={onRemove}
/>
다음으로 접근한 방식은 현재 첨삭기능이 저장한 텍스트 문자와 해당 범위의 상태값을 가져와서 다시 DraggedAnswer이라는 첨삭된 부분을 하이라이터 효과를 부여하는 컴포넌트에서 문장을 재조합하는 과정을 거친다. 해당 컴포넌트에 값이 전달되기전에 첨삭 이전에 렌더링중인 텍스트와 비교하는 것이다.
const 타겟지점더하기 = 시작점부터타겟까지문장 + preSelectedText + 타겟부터끝나는지점문장;
const 모든문장일치 = 타겟지점더하기 === answer;
{isSelected && 모든문장일치 ? (
<DraggedAnswer
startText={시작점부터타겟까지문장}
selectedText={preSelectedText}
endText={타겟부터끝나는지점문장}
selectedColor={selectedColor}
onRemove={onRemove}
/>
해당 방법은 효과는 있었지만, 코드가 너무 절차적이고 복잡하여 유지보수 측면에 좋지 못하다는 생각이 들었다. 두개의 방식을 조합하여 새로운 방법으로 접근해볼 필요성을 느꼈다.
const handleCorrection = useCallback(
(dragTitleId: number, targetId: number, originText: string) => {
const isSelected = selectedText.added;
if (isSelected) {
showWarring();
return;
}
const selectedCorrection = validateSelectedText(originText);
selectedCorrection
&& changeCorrection(selectedCorrection, dragTitleId, targetId);
},
[selectedText],
);
여러 방안을 고민한 끝에 originText를 이벤트 발생 시점에 전달받는 방식을 선택했다. 이 방식은 본문의 텍스트를 기반으로 드래그된 텍스트가 해당 본문에 존재하는지 판단하고, 이를 바탕으로 첨삭을 처리하는 방법이다. 이러한 접근은 본문의 구조가 span, p, div 등 어떤 요소로 구성되어 있든지 상관없이, 오직 드래그된 텍스트에만 집중할 수 있어 효율적이라고 판단했다. 현시점에서는 어떤 방식으로 이벤트를 발생시켜도 잘 필터링 되는 것을 확인할 수 있었다.