
미리적는 결론: event 핸들러들을 의도에 맞게 잘 사용해서
조합하자.
어찌보면 정말 간단한 기능인데, 늘 그랬던 것 처럼(...) 빙빙 돌아
우여곡절을 많이 겪어 글로 남겨본다.
처음 custom modal을 도입 할 때는,
무조건 클릭으로만 닫을 수 있게 되어있었다.
하지만 기본 alert이나 custom message조차 enter키로 닫을 수 있고,
나도 평소에 다른 웹사이트를 이용할 때 그러한 메세지 창이 엔터키나
키보드 제어 할 수 없으면 뭔가 약간의 불편함을 느꼈었다.
그래서 이번 프로젝트에서는 enter키로 닫는 기능을 넣고자 했다.
가장 처음에 했던 방법으로는,
input box에서 data 입력 ->
ok나 fail modal display ->
focus가 아직 input box에 있으므로 enter키 누르면
이벤트 핸들러에 지정해둔 메소드가 동작 ->
따라서 메소드 맨 앞 부분에 data validation이 아닌
modal validation을 추가->
해당 validation을 통해 modal component에 props를 전달해 닫기
이러한 방법으로 구현 했었다.
일단 처음에는 기능이 잘 돌아가니깐 만족 했지만,
나중에 팀원분들의 피드백과, modal validation의 문제를 느꼈다.
일단 모든 modal이 disp이 될 수 있는 method에는 validation을 넣어야 했고,
modal validation조차 기능 구현을 위한 임의의 판별식들로 이루어져있어,
코드를 이해하기 난해했다.
그러다 항상 도움을 많이 받는 우리 팀원 중 한 분이 말씀해주신 의견이
focus를 input box에서 modal component의 버튼 부분으로 옮기자는 것이였다.
그 의견을 듣고 그러한 로직이 진짜 직관적인 로직이라 생각되었고,
감사하게도 관련해서 작업도 진행해 주셨다.
근데 대부분의 component에서는 잘 작동했는데,
몇몇 페이지의 component에서는 해당 기능이 잘 동작하지 않았다.
그 때 당시에도 결국 제대로된 원인을 찾지 못하고,
부랴부랴 다른 프로젝트의 개발을 진행해서 일단은 엔터키로 modal을 닫는
기능은 빼고 진행하였다.
그러다가 최근 시간적 여유가 생겨 이유를 분석 해보니,
우리가 생각한 로직(input box -> button focus이동)이 동작하지 않고있었다.
document.activeElement 메소드를 통해 확인해보니,
focus() 메소드를 호출하여도 여전히 input box에 focus가 되고 있었던 것이다.
그래서 그 때 부터 천천히 parent와 child component이 있을 때
렌더링이 어떻게 되는지 찾아보고, 어떻게 해야 될까 고민했다.
일단 처음에는 왜 focus가 되지 않는지 원인을 아는것부터 시간이 오래걸렸다.
다행히 life cycle을 계속해서 들여다보니 알게되었는데,
modal component는 ok, fail, confirm 세개의 modal component를 가지고
v-show에 따라 렌더링 되는 로직으로 구성해두었다.
focus() 메소드는 가상 dom이 아닌 실제 dom에서 element를 선택하는데,
v-show로 가상 dom에 숨겨져있다가 실제 dom에 렌더링 되니
focus()메소드는 정상적으로 동작하지 않는것이였다.
(에러라도 발생하면 좋으련만, 에러는 발생하지 않았다.)
그래서 찾아보니 vue의 nextTick 메소드를 이용하면,
실제 dom 렌더링 이후에 js method를 실행 할 수 있었다.
이 때 만해도 나는 문제가 금방 풀리겠거니 싶었다.
그런데 nextTick을 적용하니, modal자체가 뜨지 않는것이었다.
(focus는 정상적으로 이동함)
처음에는 현상을 이해 조차 하지 못하고, 막연히 parent와 child component간의 뭔가 있는건가 하고 넘어갔다.
그러다가 nextTick에 관한 article들을 몇 개 읽어보고,
(https://javascript.plainenglish.io/dont-ask-others-after-reading-this-nexttick-dbece1bcd8a4,
https://javascript.plainenglish.io/after-reading-this-nexttick-dont-ask-anyone-else-de06e162171b)
아 이게 child component에서 nextTick을 호출해서
parent component도 다시 렌더링 되서 child component가 다시 호출 되고 있는건가 하는 큰 헛 다리를 짚었다.
(다시 호출되면 props 전달 값이 falsy한 ''여서 v-show condition이
무조건 false가 되므로)
그래서 처음에는 이 구조에서 props 전달을 어떻게 해야하나...
한참을 고민했다.
그러다가 혹시 이게 그 문제가 아닌가? 싶어서 컴포넌트 코드를 다 들고와
parent에 다 넣고 같은 로직을 작동시켜보았다.
그랬더니 child에서 처럼 똑같이 modal자체가 뜨지 않는 것이였다!
그제서야 component간의 문제가 아니란걸 깨닫고,
다시 열심히 원인을 찾았다.
결국 며칠이나 걸려서 찾은 원인은...
별 생각 없이 넣어 뒀었던 이벤트 핸들러들의 조합 때문이였다...!
사실 여태까지 front에서 기능을 구현하면서,
@click, @input, @change, @keydown, @keyup등...
비슷하면서도 몇몇 element들에서는 크게 차이없이 동작하는
이벤트 핸들러들을 나는 크게 생각없이 써왔다.
그래서 어떤 페이지에서는 같은 input box에서의 키보드 이벤트 처리인데 keyup, keydown이 혼용되어 있었다.
물론 하나의 컴포넌트 안에서 하나의 메소드만 호출 할 때는 문제 없었지만,
이번처럼 modal component와 연동할 때는 이것이 문제가 되었다.
일단 문제가 있던 조합을 설명해 보자면,
input box에서 @keydown.enter로 조회 api 메소드 호출 ->
data validation에서 fail->
fail modal을 disp해주기 위해 props data 전달 ->
modal component에서 watch로 data변경 ->
data변경 후 nextTick으로 button focus->
button의 이벤트 핸들러는 @click하나이고, 이 상태에서 다시 enter를 누름->
enter만으로도 @click 이벤트 핸들러는 정상적으로 메소드를 호출 ->
해당 메소드는 데이터를 초기화하고, focus를 parent의 input box로 돌려줌.
이런 상태여서, enter를 누르자마자 data가 초기화되어 modal이
보이지 않는것이였다!
결국 여태까지 별 생각 없던 event 핸들러들을 다시 의도에 맞게 잘 정리해,
다음과 같은 조합으로 바꾸었다.
input box에서 @keyup.enter->
button은 @mouseup, @keyup.enter 둘 다 추가.
event 핸들러들을 정상적으로 바꾸니,
결국 처음에 의도한대로 enter키로 모달창이 잘 닫힌다!!
멋지네요