사진과 같이 Viewport의 크기에 따라 컴포넌트의 크기를 변경하려면 어떻게 해야 할까? 단순히 컴포넌트의 크기만 변경하는 것이라면 CSS를 이용하는 것이 좋지만, SearchBar 컴포넌트의 크기에 맞춰 Modal 컴포넌트의 크기가 같이 달라져야 하므로 Styled-Components를 이용해 Modal 컴포넌트의 너비를 동적으로 바꿔줘야 함. 하지만, SearchBar와 Modal은 별개의 컴포넌트이기 때문에 Context API를 사용하는 등의 방법이 필요함.
1. Resize 이벤트
A. 크기 변경
Viewport에 따라 변경되는 SearchBar 컴포넌트의 너비가 State여야 하므로 DOM의 resize 이벤트를 이용해 SearchBar의 너비를 가져와야 함. resize 이벤트는 window에만 존재하므로 window에 addEventListener() 메서드를 사용해줘야 함. 한편, SearchBar의 너비는 offsetWidth 속성을 이용할 수 있음.
크기 변경과 기본 로직은 동일함. x 위치는 Input 요소의 offsetLeft를 사용해도 문제가 없으나, y 위치는 SearchBox 컴포넌트의 구조로 인해 SearchBox 내부의 div 요소의 위치와 높이로 계산을 해줘야 함. 일반적인 상황이라면 offsetTop을 사용해도 문제가 없음.
이대로 이벤트 핸들러를 작성하면 모든 resize 이벤트마다 State 변경이 발생하므로 성능에 치명적인 문제가 발생함. Input 상자에 포커스가 되었을 때마다 Modal 사이즈, 위치 변경을 하면 되므로 포커스를 잃으면 더이상 State가 변경되지 않도록 해줘야 함. 따라서, SearchBar가 focus된 상태일 때만 resize 이벤트 핸들러가 실행되도록 하고, blur 상태일 때는 실행되지 않도록 해줘야 함.
isFocused라는 State를 만들고, onFocus 때는 true로, onBlur 때는 false로 할당해 줌. isFocused 값을 이용해 true일 때만 resize 이벤트 핸들러가 실행되도록 해줘야 함.
useEffect(() => {
const handleResize = () => {
if (!isFocused) return; // false이면 실행 x
resizeModal();
};
window.addEventListener('resize', handleResize);
// isFocused가 이전 상태일 때 생성되었던 handleResize를 제거
return () => window.removeEventListener('resize', handleResize);
}, [isFocused]); // dependency에 isFocused 추가
3. 클린업(Clean-up) 함수
서버 데이터 요청, 기타 작업 등에서 useEffect()에 클린업 함수를 사용해주는 것이 좋으나, 안 써도 문제가 발생할 일은 많지 않음. 하지만, useEffect()에서 window 이벤트 핸들러를 추가할 때는 반드시 클린업 함수를 넣어줘야 함!
검색창 focus 없이 창 크기 변경 시
SearchBar 컴포넌트가 처음 렌더링되고 focus를 하지 않은 상태에서는 isFocused가 계속 false 상태를 유지함.
focus 후 blur인 상태에서 창 크기 변경 시
하지만, 검색창 focus 후 blur가 되면 어째서인지 위 사진처럼 isFocused가 true, false 반복해서 변경됨. 분명히 onFocus, onBlur 이벤트 핸들러가 실행되지 않는데 어째서 isFocused가 변경되는 걸까?
isFocused가 변할 때마다 새로운 handleResize가 생성되기 때문에 주소값도 달라짐. 따라서, 매 Effect 실행마다 동일한 resize 이벤트에 계속 새로운 handleResize가 추가되기 때문에 이전 상태의 isFocused를 참조하던 handleResize가 실행되어서 isFocused가 변경되는 것처럼 보이게 됨.(Stale Closure) isFocused가 true인 환경에서 생성되었던 handleResize가 size, position State를 변경하면서 불필요한 렌더링이 발생하게 됨. 특히, resize 이벤트는 그 발생 빈도가 잦기 때문에 성능에 심각한 문제를 일으키게 됨. 따라서, useEffect()에서 이벤트 핸들러를 추가할 때는 반드시 클린업 함수를 작성해줘야 함!
Viewport에 따라 컴포넌트 크기 동적으로 변경하기
사진과 같이 Viewport의 크기에 따라 컴포넌트의 크기를 변경하려면 어떻게 해야 할까? 단순히 컴포넌트의 크기만 변경하는 것이라면 CSS를 이용하는 것이 좋지만, SearchBar 컴포넌트의 크기에 맞춰 Modal 컴포넌트의 크기가 같이 달라져야 하므로 Styled-Components를 이용해 Modal 컴포넌트의 너비를 동적으로 바꿔줘야 함. 하지만, SearchBar와 Modal은 별개의 컴포넌트이기 때문에 Context API를 사용하는 등의 방법이 필요함.
1. Resize 이벤트
A. 크기 변경
Viewport에 따라 변경되는 SearchBar 컴포넌트의 너비가 State여야 하므로 DOM의 resize 이벤트를 이용해 SearchBar의 너비를 가져와야 함. resize 이벤트는 window에만 존재하므로 window에 addEventListener() 메서드를 사용해줘야 함. 한편, SearchBar의 너비는 offsetWidth 속성을 이용할 수 있음.
DOM 이벤트 핸들러를 이용하지 않고 useEffect()에서 State 변경을 할 경우, 반드시 dependency를 지정해줘야 함. 하지만, resize 이벤트에서는 dependency로 설정한 변수를 찾기 힘들기 때문에 DOM 이벤트 핸들러를 이용하는 편이 좋음.
B. 위치 변경
크기 변경과 기본 로직은 동일함. x 위치는 Input 요소의 offsetLeft를 사용해도 문제가 없으나, y 위치는 SearchBox 컴포넌트의 구조로 인해 SearchBox 내부의 div 요소의 위치와 높이로 계산을 해줘야 함. 일반적인 상황이라면 offsetTop을 사용해도 문제가 없음.
2. focus, blur 이벤트로 성능 최적화하기
이대로 이벤트 핸들러를 작성하면 모든 resize 이벤트마다 State 변경이 발생하므로 성능에 치명적인 문제가 발생함. Input 상자에 포커스가 되었을 때마다 Modal 사이즈, 위치 변경을 하면 되므로 포커스를 잃으면 더이상 State가 변경되지 않도록 해줘야 함. 따라서, SearchBar가 focus된 상태일 때만 resize 이벤트 핸들러가 실행되도록 하고, blur 상태일 때는 실행되지 않도록 해줘야 함.
isFocused라는 State를 만들고, onFocus 때는 true로, onBlur 때는 false로 할당해 줌. isFocused 값을 이용해 true일 때만 resize 이벤트 핸들러가 실행되도록 해줘야 함.
resize 이벤트 핸들러에 isFocused를 검사하는 코드를 넣어줌.
3. 클린업(Clean-up) 함수
서버 데이터 요청, 기타 작업 등에서 useEffect()에 클린업 함수를 사용해주는 것이 좋으나, 안 써도 문제가 발생할 일은 많지 않음. 하지만, useEffect()에서 window 이벤트 핸들러를 추가할 때는 반드시 클린업 함수를 넣어줘야 함!
SearchBar 컴포넌트가 처음 렌더링되고 focus를 하지 않은 상태에서는 isFocused가 계속 false 상태를 유지함.
하지만, 검색창 focus 후 blur가 되면 어째서인지 위 사진처럼 isFocused가 true, false 반복해서 변경됨. 분명히 onFocus, onBlur 이벤트 핸들러가 실행되지 않는데 어째서 isFocused가 변경되는 걸까?
isFocused가 변할 때마다 새로운 handleResize가 생성되기 때문에 주소값도 달라짐. 따라서, 매 Effect 실행마다 동일한 resize 이벤트에 계속 새로운 handleResize가 추가되기 때문에 이전 상태의 isFocused를 참조하던 handleResize가 실행되어서 isFocused가 변경되는 것처럼 보이게 됨.(Stale Closure) isFocused가 true인 환경에서 생성되었던 handleResize가 size, position State를 변경하면서 불필요한 렌더링이 발생하게 됨. 특히, resize 이벤트는 그 발생 빈도가 잦기 때문에 성능에 심각한 문제를 일으키게 됨. 따라서, useEffect()에서 이벤트 핸들러를 추가할 때는 반드시 클린업 함수를 작성해줘야 함!
완성 결과
[참고]
Re-render a React Component on Window Resize
How to (really) remove eventListeners in React
'JavaScript > React' 카테고리의 다른 글