위의 버튼을 눌러보면 UX 상으로도 전혀 문제가 없어보인다. 하지만 비교군이 없으면 제대로 이해하기 힘들테니 아래의 비교군을 살펴보자.
똑같이 위의 버튼을 눌러보면 button의 width가 줄어들었다가 늘어나는 미세한 차이점을 느낄 수 있다.
: 사실 결과적으로 생각했을 때는 위의 두 UX 모두가 그렇게 좋지는 않다. 1번 UX의 경우 버튼의 width가 value의 길이에 따라 변하고 있기 때문에 사실상 서비스로는 적합하지 않다. 하지만, 2번의 경우 그 정도가 심각하다(?). 방금 비교 분석한 내용을 실제로 react가 렌더링하는 그리고 useEffect, useLayoutEffect가 activate되는 시점을 통해서 구체적으로 분석해보자.
버튼을 누른다 -> setValue(0)가 activate 된다(onClick 이벤트) -> 컴포넌트가 리렌더링되기 시작한다 -> useLayoutEffect 속의 로직인
useLayoutEffect(() => {
if (value === 0) {
setValue(10 + Math.random() * 200);
}
}, [value]);
위의 로직이 실행되면서 value는 0에서 다시 랜덤값으로 세팅이 된다 -> onClick 이벤트로 activate됐던 setValue에 따른 리렌더링이 실제로 화면에 렌더링된다(컴포넌트 리렌더링 과정 !== 브라우저의 실제 렌더링이라는 기준으로 설명). 이 때, 위의 useLayoutEffect에서도 setValue가 실행됐었기 때문에 그에 따른 리렌더링도 실행된다 => 다시
useLayoutEffect(() => {
if (value === 0) {
setValue(10 + Math.random() * 200);
}
}, [value]);
위의 로직으로 접근하지만 이제는 value !== 0이기 때문에 setValue가 일어나지 않고 로직이 마무리 된다. 여기까지의 과정에서 포인트는 button onClick에 따른 setValue(0) 로직에 의해서 바로 화면 리렌더링이 일어나지 않고, useLayoutEffect를 거쳐서 랜덤값으로 재변형된 다음에 최종적으로 화면 리렌더링이 일어난다는 점이다. 다시 말해, 'state update -> 화면 렌더링' 사이에 미들웨어처럼 useLayoutEffect가 작동한다는 것이다.
버튼을 누른다 -> setValue(0)가 activate 된다(onClick 이벤트) -> 컴포넌트가 리렌더링되기 시작한다 -> 실제로 화면에 리렌더링 된다(이 과정 때문에 button의 input이 1번 로직과 비교했을 때 상대적으로 크게 흔들리고, 줄어드는 것처럼 보인다 0으로 리렌더링 하기 때문에) -> 리렌더링 되는 시점과 동시에 useEffect가 실행된다.
useEffect(() => {
if (value === 0) {
setValue(10 + Math.random() * 200);
}
}, [value]);
-> 위의 로직이 실행되고 value가 0으로 리렌더링(화면에)된 상태로 랜덤값으로 setValue를 다시한다 -> 컴포넌트 리렌더링이 시작되고, 화면도 다시 랜덤값으로 리렌더링 된다 -> 다시 useEffect 로직을 실행하지만 value !== 0 이라 끝이난다.
출처 : https://github.com/donavon/hook-flow
위와 같은 차이를 보인다고 이해하면 된다. 그래서 둘은 필요에 따라 구분해서 사용하면 될 것 같다. 만약에 내가 위에 구현해놓은 것과 같은 상황에서 0을 렌더링 하고 싶지 않다면 useLayoutEffect를 쓰면 될 것이고, 반대로 state 변경시에 custom 해야하는 로직이 시간복잡도 측면에서 굉장히 리소스가 많이드는 작업이라면 useEffect를 쓰는게 좋을 것이다. 그 이유는 useLayoutEffect의 경우에는 아까 말했듯이 화면에 렌더링을 하기 직전에 실행되기 때문에 만약에 거기에 리소스가 많이 드는 작업이 있다면 그만큼 화면 렌더링도 늦어지고, 비효율적인 로직이 될 것이기 때문이다.