이슈 발췌일: 2022.08.26 (금)
이슈 내용
- 입력 폼 안에서 어떤 Input에 값을 입력하고 다른 Input으로 포커싱을 옮기기 위해 클릭을 했을 때, 포커싱이 없어지는 문제점이 있었다. 두 번 이상 클릭해야만 포커싱이 정상적으로 되었다.
- 해당 현상으로 인해 사용자가 폼에서 데이터를 입력하기 불편해졌다. 전체 폼을 입력하는 시간이 이전보다 길어지게 되는 문제점이 발생했다.
문제 원인 파악
당장 문제 현상만 봤을 때는 어떤 오류인지 감이 잘 잡히지 않았다. 따라서 예상되는 원인을 나열해보고 확인해보는 방식으로 문제 원인을 파악해보려고 했다.
일단 가장 유력한 원인으로 "컴포넌트의 불필요한 리렌더링"을 예측했다. Input 컴포넌트가 클릭되는 순간 어떤 state값이 변경되어 컴포넌트가 리렌더링 되었고, 포커싱 상태가 없어진게 아닐까 생각했다. Input은 공통 컴포넌트로 사용하고 있어 여러가지 props를 많이 받으니까 상태 관리가 꼬일 수도 있다고 생각했다.
현재 프로젝트에서 Context API를 사용하여 상태값을 관리하고 있었기 때문에 ContextStore에서 바뀐 state 값이 영향을 줬을지도 모른다는 생각도 들었다. 아래는 내가 처음에 예측했던 원인들을 확인해본 후 정리한 내용이다.
- Input의 어떤 props 값이 변경되어 불필요한 리렌더링을 발생시켰다.
- Input 컴포넌트로 받는 props 중에서 상태값은 value 밖에 없었는데, 이 값은 Input 컴포넌트에 "blur" 이벤트가 발생하면 변경되는 값이다. Input1 => Input2로 포커싱을 옮긴다면 Input1의 경우는 "blur" 이벤트가 발생하여 value 상태값이 바뀌니 리렌더링 되는게 맞다. 근데 그건 Input2와는 상관이 없다.
- Input에 입력한 값이 state에 저장될 때, 해당 state를 참조하는 부모 컴포넌트가 리렌더링 되어서 하위에 있는 Input 컴포넌트들도 리렌더링 된 것이다.
- Input 컴포넌트의 상위 컴포넌트에서 value state를 참조하고 있기 때문에 폼에 있는 하위 Input 컴포넌트 또한 리렌더링 되기는 한다. 근데 값이 변하지 않은 다른 Input 컴포넌트가 왜 포커싱이 없어지는 거냐 이말이다...
- 내 생각엔 포커싱이 없이지는 건 그 컴포넌트가 DOM 변경사항이 감지되어 DOM 업데이트가 되기 때문인 것 같다. 근데 예상일 뿐...왜 그런걸까는 잘 모르겠다....
사실 2번 원인까지 분석하고나서 약간 멘붕이 왔던 것 같다. Input 컴포넌트를 분석해봤을 때는 focusin 했을 때 DOM 요소가 바뀔 만한 상태값을 사용하고 있지 않았다. 이대로 문제를 그냥 넘기기에는 너무 찝찝해서 Input 컴포넌트를 보고... 또 보고... 계속 보던 중에 뭔가 눈에 띄는 부분을 발견했다.
const Input = (props) => {
// 조건 별로 상위에 다른 요소를 감싸주기 위해 따로 선언해준 것
const Element = () => {
return <input {...props} />;
}
if (...) {
return <Element />
}
return <div><Element/></div>;
}
Element 변수를 따로 선언해서 Input 컴포넌트를 정의하고, 조건에 따라서 상위에 뭘 감싸서 내보내는 식으로 코드가 짜여있었다. 여기서 Element 변수를 따로 선언한 부분이 뭔가 찜찜했다. 혹시 리렌더링 될 때, 변수에 할당되는 값이 바뀌어서 그런게 아닐까? 라는 생각이 갑자기 든 것이다. (리렌더링 될 때 함수 객체가 다시 생성되서 변수 값이 바뀌니까, Element 요소가 바뀌었다고 착각하는 게 아닐까?! 라는 생각 흐름이었다...)
그리고, 혹시나 해서 Element로 선언된 부분을 주석처리하고 내부 코드를 그냥 그대로 넣어 봤다. 그 결과는...
const Input = (props) => {
if (...) {
return <input {...props} />
}
return (<div>
<input {...props} />
</div>);
}
성공이었다. 더 이상 Input 컴포넌트 포커싱이 없어지는 문제가 발생하지 않았다.
문제에 대한 이해와 고민
문제가 해결되었다고 끝이 아니었다. 왜 저 부분이 문제가 된 것인지 확실하게 이해할 필요가 있었다.
이해하고 있었던 부분
- Form 컴포넌트의 하위 요소인 Input 컴포넌트에 값이 입력되어 value 값이 바뀌면 이를 참조하는 Form 컴포넌트가 리렌더링 되기 때문에 하위에 있는 모든 Input 컴포넌트가 리렌더링됨
- 리렌더링이 된다고 해서 항상 DOM 요소가 업데이트 되는 것은 아님
의문이 들었던 점
- 컴포넌트 내부에서 다른 컴포넌트 함수를 선언하여 사용했을 때, 왜 리렌더링 시 DOM 요소 변화가 감지되었을까?
내가 예상했던 것처럼 컴포넌트 내부에서 다른 컴포넌트 함수를 선언하게 되면, 리렌더링 시 마다 다른 함수를 생성하게 된다. React는 UI 트리에서 같은 위치에 같은 컴포넌트를 렌더링하면 해당 컴포넌트 상태를 계속 유지하지만, 위와 같은 경우는 리렌더링 시 마다 해당 위치에 다른 컴포넌트(컴포넌트 내부에 선언한 컴포넌트)를 렌더링하는 것으로 인식하여 리렌더링 시 마다 해당 위치의 DOM 엘리먼트를 다시 생성한 것으로 보인다.
react.dev 문서의 "Preserving and Resetting State(state 보존 및 재설정)" 부분을 참조하면 이해하기 쉬울 것이다.
참고자료
'이슈 로그' 카테고리의 다른 글
package 버전 문제 (lock 파일의 중요성) (0) | 2022.12.28 |
---|---|
웹 구글 지도 마커 렌더링 최적화 (0) | 2022.11.02 |
[React] useMemo를 사용한 캐싱으로 중복 호출 방지하기 (0) | 2022.10.24 |
z-index가 동작하지 않는 이유, 같은 위계에서 비교하기 (0) | 2022.06.11 |