실무에서 리액트로 업무를 하다가 라이프 사이클에 대한 고려가 부족해 발생한 이슈가 있었다.
라이프 사이클에 대한 이해를 바탕으로 문제를 해결하였고, 그 과정을 기록해본다.
TODO
유저의 상태가 담긴 데이터를 fetch 해오고 이 데이터를 바탕으로 컴포넌트를 렌더링 해야 함.
이 때 로그아웃 상태면 api 호출 없이 바로 로그아웃 화면 노출, 로그인 시에만 fetch를 해야 함.
의도한 동작
1. 로그인 함수를 불러오고 로그인이 성공하면 전역 context로 로그인 값을 설정,
그리고 이 값을 가져오는 custom Hook을 만듬.(useLogin)
2. fetch가 필요한 컴포넌트에서 useLogin을 이용해 로그인 체크
2-2. 로그인이 되어있다면 fetch 실행
3. response 값으로 userState를 업데이트 하면 컴포넌트가 유저 상태에 맞게 동적으로 렌더링 될 것으로 예상
👉 2-2 ~ 3은 useEffect 안에서 호출,
문제
1. 사내에서 제공하는 로그인 체크 함수가 느려서 state가 업데이트 되는 시간까지 딜레이가 존재, 그 사이에 디폴트 화면인 로그아웃 상태의 화면이 노출되는 문제 발생.
2. api 요청이 두번씩 들어온다는 문제도 발견.
컴포넌트의 라이프 사이클(함수형)
- mount : 컴포넌트가 최초로 실행될 때
- render : 컴포넌트 내의 엘리먼트 요소들을 화면상에 그리는 동작
- update : props를 새로 받거나, state가 업데이트 되거나, 부모가 리렌더링 되는 등의 상황에서 컴포넌트를 다시 그리는 동작
- unMount : 컴포넌트가 페이지에서 사라질 때
클래스 컴포넌트에서는 생명주기와 관련된 메소드들을 제공했었으나,
최근에는 함수형 컴포넌트를 권장하면서 사용되지 않고 있음.
클래스 컴포넌트 생명주기 관련 메서드
💡 마운트(mount)
☑️ constructor
: 컴포넌트 생성자 메서드, 컴포넌트가 생성되면 가장 먼저 실행되는 메서드.
☑️ getDerivedStateFromProps
: props로부터 파생된 state를 가져온다. props로 받아온 것을 state에 넣어주고 싶을때 사용.
☑️ componentWillMount
: 컴포넌트가 렌더링 되기 직전 실행되는 메서드.
☑️ render
: 컴포넌트를 렌더링하는 메서드.
☑️ componentDidMount
: 컴포넌트가 마운트 됨, 즉 컴포넌트의 첫번째 렌더링이 마치면 호출되는 메서드.
이 메서드가 호출되는 시점에는 화면에 컴포넌트가 나타난 상태이다.
여기서 주로 DOM을 사용해야 하는 외부 라이브러리, 해당 컴포넌트에서 필요로하는 데이터를 요청하는 등의 동작을 실행.
💡 업데이트(updating)
☑️ getDerivedStateFromProps
: 컴포넌트의 props나 state가 바뀌었을때도 이 메서드가 호출된다.
☑️ shouldComponentUpdate
: 컴포넌트가 리렌더링 할지 말지를 결정하는 메서드.
☑️ componentDidUpdate
: 컴포넌트가 업데이트 되고 난 후에 발생하는 메서드. (최초 렌더링에서는 발생하지 않는다.)
💡 언마운트(unmount)
☑️ componentWillUnmount
:컴포넌트가 화면에서 사라질 때 발생하는 메서드.
useEffect
- componentDidMount, componentDidUpdate, componentWillUnmount, getDerivedStateFromProps 의 역할을 구현할 수 있다.
- useEffect는 화면이 모두 업데이트 된 이후(render, paint 이후)에 실행된다. (비동기적 실행)
>> paint란? 실제 스크린에 Layout을 표시하고 업데이트하는 과정 - 따라서 DOM에 영향을 주는 코드가 있다면 화면 깜빡임이 발생할 수 있다.
실행 순서: 컴포넌트 마운트 실행 → 브라우저가 화면에 DOM 그리기(화면 업데이트) → effect 함수 실행
useEffect(() => {
... // 실행할 내용
return () => {
... // clenup
}
},[의존성 배열])
- 콜백 함수: update 시 수행할 작업.
- clean up 함수 == componentWillUnmount: 컴포넌트가 사라질 때 호출되는 함수
- 의존성 배열: update 조건 처리, 빈 배열이 들어가면 최초 1회만 실행됨.
useLayoutEffect
- useEffect와 사용 방식은 동일하다.
- 실행 순서가 조금 다른데, useLayoutEffect는 화면이 그려지기(paint) 직전에 실행된다. (동기적 실행)
- 따라서 useLayoutEffect 내부에 DOM을 조작하는 코드가 있더라도 화면 깜빡임이 발생하지 않는다.
실행 순서: 컴포넌트 마운트 실행 → effect 함수 실행 → 브라우저 화면에 DOM 그리기(화면 업데이트)
useLayoutEffect(() => {
... // 실행할 내용
return () => {
... // clenup
}
},[의존성 배열])
라이프 사이클을 알고 다시 바라본 문제
- 로그인 상태와 유저 상태가 함께 확인되어야 하는 상황이였으나 로그인 체크 함수는 app.js 안에서, 유저 상태를 불러오는 fetch 함수는 자식 컴포넌트에서 따로 호출 중이였음.
- 로그인 체크 함수는 느리고 비동기로 실행됨. 이 함수가 실행이 끝나지 않은 상태로 부모 컴포넌트가 렌더링 되기 때문에 첫 마운트 시 로그아웃 상태의 화면이 먼저 뜨고, 로그인 데이터가 업데이트 되면 다시 화면도 업데이트 됐던 것 -> 최상위 컴포넌트가 업데이트 되기 때문에 하위 컴포넌트도 함께 업데이트 되면서 api 호출도 두 번 이루어지게 됨.
- 데이터가 먼저 불러와진 다음 화면이 렌더링 되어야 함. 그러나 useEffect는 화면이 그려진 후에 실행되기 때문에 가장 첫 화면과 유저 데이터 간의 싱크가 맞지 않게 됨.
해결
- 하위 컴포넌트에서 불러오던 fetch를 로그인 체크 함수의 success 콜백 함수로 전달.
- 로그인 체크가 완료되면 유저 데이터를 불러오고, 컴포넌트 구성에 필요한 데이터를 하위 컴포넌트로 한번에 전달하여 컴포넌트가 렌더링 되도록 변경. (사실 context나 커스텀 훅까지는 필요 없던 것, 과감히 제거하였다.)
- 이 로그인 체크 함수는 useLayoutEffect 안에서 실행하여 화면이 그려지기 전에 데이터를 전달할 수 있도록 함.
➡️ 여기까지 했더니 1000ms 정도의 화면 딜레이가 20ms까지 줄어들었다. - 20ms 정도의 시간이긴 하지만 그래도 잠시 빈 화면이 노출되므로 이 시간 동안은 로딩 컴포넌트가 노출되도록 ux까지 고려하여 페이지를 완성시켰다.
수정 후 프론트엔드 로직
'트러블슈팅' 카테고리의 다른 글
웹에서 배경이 투명한 영상 사용하기 (0) | 2024.08.25 |
---|---|
URL을 이용하여 탭 메뉴 구성하기 (0) | 2024.08.25 |
뒤로가기 후 자바스크립트가 동작하지 않는 이유 (0) | 2024.08.25 |