week13의 심화 요구사항을 이번 주차에 구현하기로 마음먹고, next.js로의 마이그레이션과 심화 요구사항을 구현했다.
심화 요구사항
- 상단에 있던 링크 추가하기 영역이 가려져 보이지 않을 때 최하단에 링크 추가하기 영역을 고정하도록 만들었나요?
- 푸터가 시작되는 지점에서는 최하단에 고정된 링크 추가하기 영역이 보이지 않도록 했나요?
이 요구사항과 함께, Intersection Observer를 이용해 구현해보라는 코멘트도 함께 달려있어서 사용해보았다
Intersection Observer 사용법
위 요구사항에서 나는 folder 페이지 컴포넌트의 header와 footer을 주시 대상으로 지정해야 했다.
// folder 페이지 구성
<FolderHeader/>
<S.ItemsContainer>
<SearchBar />
<FolderContent/>
</S.ItemsContainer>
<Footer />
기본 HTML 태그를 사용했을 땐 바로 useRef()를 쓰면 되겠거니 생각하고 있었는데,
나는 컴포넌트 단위를 ref 로 받아온 적이 없었다. (방식은 비슷하지 않겠나 생각 함)
처음에는 ref를 prop으로 넘겨주면 될 것이라고 생각했지만, 내 생각과는 달랐다.
prop으로 넘겨주는 것이 아니라, ref 로 인지하고자 하는 컴포넌트를 forwardRef로 감싸주어야 한다.
const FolderHeader = forwardRef(
(
//...
) => {
//...
return (
<div ref={ref as React.RefObject<HTMLDivElement>}>
//...
</div>
);
}
);
이제는 주시해야하는 header과 footer를 ref로 지정해줄 수 있게 됐으니, IntersectionObserver을 이용해 state 값을 변경 시켜보자!
-> 이 state 값을 이용해 styledComponents의 속성을 동적으로 변경시켜줄 예정이다.
const headerRef = useRef<HTMLElement>(null);
const footerRef = useRef<HTMLElement>(null);
useEffect(() => {
const HeaderObserver = new IntersectionObserver((entries) => {
const entry = entries[0];
if (entry.isIntersecting) {
setIsHeaderVisible(true);
} else {
setIsHeaderVisible(false);
}
});
HeaderObserver.observe(headerRef.current as Element);
const FooterObserver = new IntersectionObserver((entries) => {
const entry = entries[0];
if (entry.isIntersecting) {
setIsFooterVisible(true);
} else {
setIsFooterVisible(false);
}
});
FooterObserver.observe(footerRef.current as Element);
}, []);
state값을 바꿔주고, 이를 styledComponents의 prop으로 넘겨줘서 boolean 값에 따라 position : fixed; + a 를 추가하도록 구현했다.
-> 심화 요구사항은 기능은 완성! (기모링~)
하지만, 짜놓고 보니 중복되는 로직이 너무나도 명확히 보여서 커스텀 훅으로 분리하기로 마음 먹었다..! (한번 도 안 해봄)
useIntersectionObserver라는 이름으로 커스텀 훅을 만들 것이다. (더 좋은 이름 추천 좀ㅠ)
export function useIntersectionObserver() {
const targetRef = useRef<HTMLElement>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const targetObserver = new IntersectionObserver((entries) => {
const entry = entries[0];
if (entry.isIntersecting) {
setIsVisible(true);
} else {
setIsVisible(false);
}
});
return { isVisible, targetRef };
}
이렇게 해줬는데,
() => { isVisible : boolean ; targetRef : RefObject<HTMLElement> } 형식의 인수는 'EffectCalback' 형식의 매개 변수에 할당될 수 없습니다.}
라는 오류 메시지가 떴다. 이 오류가 왜 떴는지 알고 싶어서 리액트 공식 문서를 찾아봤다.
useEffect와 정리 함수
react에서 useEffect 훅은 side effects를 수행하기 위해 사용되는데, 컴포넌트가 렌더링 될 때마다 실행되다보니 정리하지 않으면 데이터가 쌓여 메모리 누수가 발생할 수 있다.
이를 방지하기 위해 useEffect는 정리 함수 (clean-up function)를 반환한다.
- 해당 함수가 실행되기 전에 이전의 효과를 정리하는데 사용
- 컴포넌트의 라이프사이클에 따라 자원을 올바르게 관리할 수 있도록 해줌
예시 )
DOM 요소에 대한 이벤트 리스너를 추가하거나, 외부 자원에 대한 구독을 설정한 경우
-> 컴포넌트가 언마운트되거나 업데이트 될 때 해당 리소스를 정리해야 함
이러한 이유로 내가 짰던 커스텀 훅에 정리 함수가 필요하다는 사실을 알게 되었다!
이와 더불어, target.current가 null이 들어갈 수도 있는 부분을 고려해서 if 문으로 한 번 더 감싸주었다.
최종 코드
// useIntersectionObserver.ts
export function useIntersectionObserver() {
const targetRef = useRef<HTMLElement>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const targetObserver = new IntersectionObserver((entries) => {
const entry = entries[0];
if (entry.isIntersecting) {
setIsVisible(true);
} else {
setIsVisible(false);
}
});
if (targetRef.current) {
targetObserver.observe(targetRef.current);
}
// 정리 함수
return () => {
if (targetRef.current) {
targetObserver.unobserve(targetRef.current);
}
};
}, [targetRef]);
return { isVisible, targetRef };
}
// 사용하는 곳
function Folder(){
const { isVisible: isHeaderVisible, targetRef: headerRef } = useIntersectionObserver();
const { isVisible: isFooterVisible, targetRef: footerRef } = useIntersectionObserver();
const floatingState = !isHeaderVisible && !isFooterVisible ? true : false;
return(
<>
<FolderHeader
ref={headerRef}
isFloating={floatingState}
/>
<S.ItemsContainer>
<SearchBar />
<FolderContent data={categoryData} />
</S.ItemsContainer>
<Footer ref={footerRef} />
</>
);
로직을 분리하면 좋다고 듣기만 하고 실제로 분리해본 적이 없었는데,
처음 만들어봐서 아주 뿌듯했고, 분리를 하면 사용하는 곳에서 훨씬 간결하게 사용할 수 있어서 가독성도 좋아졌다!
앞으로도 이렇게 분리할 수 있는 로직들이 많아지면, 훨씬 깔끔하게 코드를 짤 수 있을 것 같다.
✅ 첫 커스텀 훅 도전기 성공!
다음에는 InputComponents를 만들던 중, 에러 메시지를 출력하도록 하는 로직을 조금 더 파보고 싶어서 그 내용을 가지고 와볼 것 같다.
그럼 안녕~!
'💡뚝딱뚝딱 만들어보자 ~! :) > Linkbrary' 카테고리의 다른 글
[Refactoring] 모듈화 및 추상화를 통한 API함수 개선해보기 (0) | 2024.05.21 |
---|---|
[Refactoring] ContextAPI로 Modal 컴포넌트 개선하기 (0) | 2024.04.10 |
react-hook-form으로 로그인, 회원가입 기능 구현하기 (1) | 2024.04.06 |
40% 부족한 검색 기능 완성 시키기 (0) | 2024.03.24 |