포커스는 어디로 다녀오나
제목을 고치고 본문을 클릭하면 커서가 한 번 다녀온다. 그 짧은 왕복을 따라가며 알게 된 것 — 포커스는 상태가 아니라 여러 장치의 합이다.
포커스는 어디로 다녀오나
데스크톱 상세 화면에서 재미있는 현상을 발견했습니다. 제목을 고치고, 저장하지 않은 채 본문(Description)을 클릭하면 커서가 본문에 들어갔다가 잠깐 나갔다 옵니다. 타이핑을 시작하려는 찰나에 한 박자가 비는 것이죠. 데이터는 멀쩡하고 저장도 정확합니다. 순수하게 포커스만의 이야기입니다.
포커스를 움직이는 장치는 몇 개인가
따라가 보니, 이 순간에 포커스를 만질 수 있는 장치가 하나가 아니었습니다.
첫 번째는 편집 종료 장치입니다. 제목 입력에는 “명시적으로 저장하면(체크 버튼이나 Enter) 편집을 끝내고 포커스를 패널로 돌려보내는” 동작이 있습니다. 다음 ESC가 모달을 닫게 하려는, 그 자체로는 올바른 설계입니다. 흥미로운 건 타이밍이었습니다. 이 장치가 명시적 저장뿐 아니라 자연스러운 blur 저장(다른 필드를 클릭해서 제목이 저장되는 경우)에도 걸려 있었고, 저장은 비동기라 완료 시점이 “사용자가 이미 본문에 들어간 뒤”입니다. 과거의 저장이 미래의 포커스를 움직이는 구조였던 겁니다.
두 번째는 동기화 장치입니다. 상세 화면은 문서 모델이 갱신될 때마다, 편집 중이 아닌 에디터들을 서버 값으로 리셋합니다. 다른 기기의 변경을 화면에 반영하는 성실한 장치인데, 리셋의 수단이 리마운트입니다. 제목 저장의 응답도 “모델 갱신”이므로 이 장치가 돌고, 방금 클릭만 하고 아직 타이핑하지 않은 본문 에디터는 “편집 중 아님”으로 분류되어 리마운트됩니다. 리마운트된 에디터는 포커스를 가지고 있지 않습니다.
두 장치가 같은 순간에 겹쳐 있었습니다. 하나를 조정한 뒤에도 현상이 남아 있었던 덕분에, 두 번째 장치의 존재를 알게 됐습니다.
각 장치에 “언제”를 붙이다
수정은 두 장치 모두에 조건을 정확히 붙이는 일이었습니다. 편집 종료 장치에는 “명시적 저장일 때만”. 동기화 장치에는 “그 필드가 포커스 중이 아니고, 그 필드의 서버 값이 실제로 바뀌었을 때만”. 제목을 저장해도 본문 값은 그대로이니, 이제 본문 에디터는 아무 일도 겪지 않습니다.
부수적으로 좋아진 것도 있습니다. 첨부를 업로드할 때도 모델이 갱신되는데, 이때도 본문 무관 리마운트가 사라졌습니다. 조건을 정확히 좁히면 원래 목표 밖의 군더더기도 함께 정리되는 경우가 많습니다.
리뷰의 가장 값진 산출물은 반박이었다
동기화 장치의 조건을 좁히는 건 신중할 변경이라 코드 리뷰를 돌렸습니다. 결과가 흥미로웠습니다.
가장 값진 산출물은 새 발견이 아니라 반박이었습니다. 리뷰어가 세운 최악 시나리오 — “리셋을 건너뛰면 다른 기기의 변경이 화면에 반영되지 않고, 오래된 내용으로 팀원의 저장을 덮을 수 있다” — 를 스스로 검증하다가, 에디터 자체에 값 동기화가 이미 내장되어 있음을 확인해 준 겁니다. 캐럿을 보존한 채로요. 리마운트를 건너뛰어도 화면은 낡지 않는다는, 수정의 전제가 그렇게 증명됐습니다.
발견도 있었습니다. 태스크 상세에서 고친 것을 템플릿 상세에도 대칭으로 적용해 뒀는데, 템플릿 쪽 동기화 효과는 애초에 문서가 바뀔 때만 돌게 되어 있어 그 코드가 실행될 일이 없다는 지적이었습니다. 템플릿에는 이 현상 자체가 없었던 겁니다. 실행되지 않는 수정은 걷어냈습니다. 대칭 적용을 하기 전에, 대상 코드가 실제로 같은 조건에서 도는지부터 보는 습관을 하나 얻었습니다.
오늘 배운 것
포커스는 하나의 상태처럼 보이지만, 실제로는 여러 장치가 각자의 이유로 만지는 합입니다. 그래서 “포커스가 이상하다”는 관찰은 대개 장치 하나의 문제가 아니라, 장치들의 “언제”가 헐거운 문제입니다. 각 장치에 정확한 시점 조건을 붙여 주면, 장치들은 서로를 방해하지 않고 각자의 일을 합니다. 그리고 그 조건이 맞는지 확인하는 데에는, 나와 다른 질문에서 출발하는 두 번째 시선이 유용했습니다.