페이지 접근제어를 구현할때 useEffect 대신 useLayoutEffect를 사용하면 화면이 깜박이는 현상을 해결할 수 있다.
useEffect
는 브라우저에 Paint 된후,useLayoutEffect
는 Paint 이전에 동작한다.
이전 프로젝트에서 유저 권한별로 접근할 수 있는 페이지와 접근하면 안되는 페이지를 분리하는 기능이 필요했다.
즉 한마디로 권한별 페이지 접근제어 기능인데 이를 isLoggedIn
이라는 상태와 useEffect
를 이용하여 구현하였다.
권한이 필요한 페이지에 처음 접근하면 useEffect
에서 isLoggedIn
상태를 체크하여 권한이 있으면 페이지를 보여주고 권한이 없으면 login 페이지로 리다이렉션하게 하였다.
// 권한이 필요한 페이지 컴포넌트
const {isLoggedIn} = useAuthStore(); // zustand 상태저장소
useEffect(() => {
if(!isLoggedIn) {
navigate('/login')
}
}, [isLoggedIn]);
// ...이하 생략
그러나 이코드는 문제가 있었는데 권한이 없을때 페이지에 접속하면 로그인 페이지로 리다이렉션이 정상적으로 되었으나 이동전 찰나의 순간에 잠깐 이동하려는 페이지의 화면이 보여지는 것이였다.
만약 권한이 필요한 페이지에 중요한 정보가 있다면 이는 큰 취약점이 될수도 있는 문제였다.
(문제를 구현한 Demo - 권한이 필요한 페이지에 지속적으로 접근했을경우 발생하는 화면)
페이지가 화면에 보여지기전에 권한 체크 로직이 실행되고 이후에 페이지 렌더링이 실행되는 방향으로 수정할 수 있다면 해결될 것 같았지만 그당시에는 구체적으로 해결법을 찾지 못했다.
해당 문제를 잊고있다가 테오의 프론트엔드 오픈채팅방을 통해 useLayoutEffect
Hook을 알게되었다. 해당 hook은 페이지 렌더링 이전에 로직이 실행된다는 것이었다. 나는 해당 내용을 보고 Demo 페이지를 구성하고 테스트 해보았고 정말로 페이지 노출 증상이 해결되었다.
// 권한이 필요한 페이지 컴포넌트
const {isLoggedIn} = useAuthStore(); // zustand 상태저장소
useLayoutEffect(() => { // useLayoutEffect로 변경
if(!isLoggedIn) {
navigate('/login')
}
}, [isLoggedIn]);
// ...이하 생략
(페이지 전환 과정에서 페이지 노출 현상 및 깜박임 문제가 해결되었다.)
그렇다면 useEffect와 useLayoutEffect는 정확히 어떤 차이가 있을까?
다음은 공식문서의 정의이다.
useLayoutEffect 함수의 시그니처는 useEffect와 동일하나, 모든 DOM의 변경 후에 동기적으로 발생한다.
useEffect와 useLayoutEffect 의 함수의 실행 과정은 다음과 같다.
여기서 2번, 3번이 중요한데 브라우저에 변경사항을 반영하기전에 useLayoutEffect 내부의 콜백 함수 실행이 동기적으로 실행된다.
이때 동기적으로 실행되기때문에 useLayoutEffect의 실행이 끝나기전까지 브라우저는 paint 하지않는다.
하지만 useEffect는 브라우저 paint 이후 비동기적으로 실행한다.
따라서 페이지별 접근 권한 분리를 구현할때 useEffect를 사용할 경우 브라우저 paint 이후 로직이 실행되어 그사이 텀에서 잠시 접근한 페이지 화면이 노출되었던 것이였다.
그리고 useLayoutEffect는 브라우저 paint 이전에 로직이 실행되고 끝날때까지 paint가 대기하고 있기 때문에 접근한 페이지 노출이 되지 않았던 것이였다.
useLayoutEffect를 사용하면 브라우저 화면에 그리기전에 해당 useLayoutEffect의 콜백함수가 끝날때까지 대기하게된다.
이는 만약 불필요한 상황에서까지 useLayoutEffect를 쓰게 될경우 브라우저 화면이 보여지는데까지 시간이 더 걸리게된다.
따라서 본문처럼 페이지 접근제한과 같이 꼭 화면이 보여지기전 꼭 처리가 완료되어야하는 로직이 있다면 useLayoutEffect를 사용하고,
이외에 화면이 보여지고나서 화면 업데이트를 해도 상관없는 것들은 useEffect를 사용하는 것이 렌더링 최적화에 도움이 될 것이다.