프로젝트 환경
React, Nextjs, Typescript
pathname 이 /main 일때만 검정 배경색,
/main 을 제외한 다른 페이지들에서는 하얀 배경색이 되는 GNV 를 만들고 싶었다.
아래와 같이 코드를 짰었는데, 두 가지 문제가 있었다.
- 배경색 전환 안됨
- main 이외의 페이지로 처음 접속시 깜빡임 현상
/main 에서 처음 접속해서 다른 페이지 이동할 때 배경색 전환이 안되는 문제는 useEffect 의 의존성 배열 문제였다.
GNV 는 최상단인 _app.tsx 에 있기 때문에 페이지를 이동해도 새로 불러와지지 않는다.
따라서 페이지 이동시 즉, pathname 을 변경할 때마다 컴포넌트를 새로 렌더링 하기 위해서는
의존성 배열에 pathname 을 넣어줘야 한다.
/main 이 아닌 페이지에 처음 접속했을 때, 검정배경이 보였다 사라지는 깜빡임 현상은
리액트 라이프 사이클을 정확하게 이해하지 못해서 발생한 문제였다.
이참에 라이플 사이클 개념을 다시 정리했다. 정리한 내용은 아래 포스트에서 확인!
리액트 lifecycle 정리! (feat. 컴포넌트 실행순서와 useEffect)
리액트 lifecycle 을 따라가 보면 깜빡임이 왜 발생했었는지 알 수 있다.
- 처음 페이지에 접속하면, 먼저 GNV 컴포넌트의 최상단부터 읽기 시작하는데,
- 여기서 theme 상태는 초기값인 ' ' 이므로 스타일드 컴포넌트에서 배경색으로 'black' 을 넣는다.
- 그 이후 실행되는 useEffect 에서 theme 상태값이 'light' 로 변경되고,
--- [ 상태값 변경으로 다시 컴포넌트가 리렌더링 된다 ] ---
- 다시 GNV 컴포넌트의 최상단부터 읽기 시작하는데,
- 지금은 theme 상태값이 'light' 이므로 스타일드 컴포넌트에서 배경색으로 'white' 를 넣으며 끝난다.
때문에 처음 컴포넌트가 실행될 때부터 현재 pathname 에 따른 theme 을 적용할 수 있도록,
pathname 의 값에 따라 달라지는 값을 theme 의 초기값으로 넣어주어야 한다.
const initialTheme = pathname === '/main' ? 'dark' : 'light';
const [theme, setTheme] = useState(initialTheme);
처음 발생했던 두 가지 문제를 모두 해결한 코드로,
처음 페이지 접속 시, pathname 에 따른 theme 초기값으로 스타일드 컴포넌트가 적용된다.
이후 pathname 이 변경될 때 마다 즉, 페이지를 이동할 때마다 다시 컴포넌트가 리렌더링 된다.
리액트 라이프 사이클을 따라가며 예를 들어보겠다.
post-list 페이지로 처음 접속한다면,
이후 /main 으로 페이지를 이동하면,
4. pathname 이 변경되었기 때문에 GNV 컴포넌트가 최상단부터 리렌더링 된다.
setTheme 가 새로 일어나지 않았기 때문에 아직 theme 은 기존에 바뀐 'light' 에 멈춰있다.
5. 그래서 스타일드 컴포넌트에 의해 GNV 배경색상은 이전과 그대로 'white' 로 들어간다.
6. 이후 useEffect 가 실행되면서 바뀐 pathname 에 의해 비로소 setTheme() 이 일어나고,
theme 상태값이 'light' 에서 'dark' 로 바뀌었기 때문에 다시 한 번 컴포넌트 리렌더링이 발생된다.
7. 다시 최상단부터 함수가 실행되는데, 이제 theme 은 바뀐 'dark' 로 들어가고,
8. 바뀐 theme 에 맞춰 스타일드 컴포넌트에 의해 'black' 배경색이 들어간 리턴문이 실행되며 끝난다.
useEffect 는 첫 번째 렌더링 후 'pathname'이 변경될 때 마다 호출되므로
이제 다른 페이지 이동으로 'pathname'이 변경되거나,
다른 상태값 변경으로 페이지 리렌더링이 일어나기 전까지는 다시 실행되지 않는다.
이 방법은 초기값 설정으로 처음 접속 시 깜빡임은 없앴지만,
페이지 이동시 렌더링이 두 번 일어난다는 단점이 있다.
그런데 오류를 해결하기 위해서 여러가지 방법을 시도하던 중,
Link 태그에 setTheme() 을 바로 넣는 방식을 실험해보면서 발견한 문제가 있었다.
바로 페이지 이동시 상태값 변화 없이도 리렌더링이 된다는 것이었다.
위와 같이 Link 태그에 setTheme() 을 바로 넣는 방식을 사용할 때,
일반적인 onClick 이벤트라면, 상태변화로 인한 한번의 리렌더링으로 멈춰야 하는데,
리렌더링이 한번 더 일어난다는 것을 발견했고, 몇 가지 실험을 진행했다.
먼저 어디서부터 렌더링이 발생하는지를 확인하기 위해 먼저
최상단이자 GNV 의 부모인 _app.tsx 에 콘솔을 찍고, Link 를 눌러 페이지를 이동했다.
일반적인 state 변경으로 인한 리렌더링이라면, GNV 만 리렌더링이 됐어야 하는데,
결과적으로 최상단인 _app.tsx 부터 리렌더링 되는 것을 확인했다.
링크 태그의 특징인가 싶어 div 태그로도 페이지 이동을 진행했는데,
마찬가지로 최상단인 _app.tsx 부터 리렌더링 되는 것을 확인했다.
결론적으로 페이지 이동은 무조건 _app.tsx 를 리렌더링 하면서 일어나게 된다는 것을 알게됐다.
혹시 몰라 GNV 내보내기에 memo 를 씌워봤지만, _app.tsx 와 GNV 의 리렌더링이 동일하게 나타났다.
export default React.memo(GlobalNavigationBar);
결과적으로 상태값 변경이 없어도 페이지 이동시 무조건 전체 렌더링이 일어나게 된다는 것을 알게 됐다.
그렇다면, 페이지를 이동할 때마다 GNV 가 어차피 리렌더링 되기 때문에
pathname 을 참조하는 theme 상태값을 만들 필요 없이
스타일트 컴포넌트에 내려주는 props 를 pathname 에서 참조해서 동적으로 내려주면
동일하게 작동하면서 렌더링은 한 번으로 줄일 수 있을 것이라고 생각했다.
확인해본 결과 동일하게 작동하면서 렌더링을 한번으로 줄일 수 있었다.
// 리턴문
return (
<GNBLayout theme={pathname === '/main' ? 'dark' : 'light'}>
<div>
...