[React]Life Cycle을 제대로 알고 나서야 해결한 이슈(useEffect, useLayoutEffect)

chaewon Im·2024년 5월 27일
1

트러블슈팅

목록 보기
9/10

실무에서 리액트로 업무를 하다가 라이프 사이클을 자세히 알지 못해 마주한 이슈가 있었다. 라이프 사이클을 공부하여 문제를 해결한 과정을 기록해둔다.

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

  • useEffect를 사용하면 componentDidMount, componentDidUpdate, componentWillUnmount, getDerivedStateFromProps 의 역할을 구현할 수 있다.
  • useEffect는 화면이 모두 업데이트 된 이후(render, paint 이후)에 실행된다. (비동기적 실행)
    - paint란? 실제 스크린에 Layout을 표시하고 업데이트하는 과정
  • 따라서 DOM에 영향을 주는 코드가 있다면 화면 깜빡임이 발생할 수 있다.

실행 순서: 컴포넌트 마운트 실행 → 브라우저가 화면에 DOM 그리기(화면 업데이트) → effect 함수 실행

useEffect(() => {
	... // 실행할 내용

	return () => {
		... // clenup
	}
},[의존성 배열])
  • 콜백 함수: 업데이트 시 수행할 작업.
  • clean up 함수 == componentWillUnmount: 컴포넌트가 사라질 때 호출되는 함수
  • 의존성 배열: update 조건 처리, 빈 배열이 들어가면 최초 1회만 실행됨.

useLayoutEffect

  • useEffect와 사용 방식은 동일하다.
  • 실행 순서가 조금 다른데, useLayoutEffect는 화면이 그려지기(paint) 직전에 실행된다. (동기적 실행)
  • 따라서 useLayoutEffect 내부에 DOM을 조작하는 코드가 있더라도 화면 깜빡임이 발생하지 않는다.

    순서: 컴포넌트 마운트 실행 → effect 함수 실행 → 브라우저 화면에 DOM 그리기(화면 업데이트)

useLayoutEffect(() => {
	... // 실행할 내용

	return () => {
		... // clenup
	}
},[의존성 배열])

라이프 사이클을 알고 다시 바라본 문제

  1. 로그인 상태와 유저 상태가 함께 확인되어야 하는 상황이였으나 로그인 체크 함수는 app.js 안에서, 유저 상태를 불러오는 fetch 함수는 자식 컴포넌트에서 따로 호출 중이였음.
  2. 로그인 체크 함수는 느리고 비동기로 실행됨. 이 함수가 실행이 끝나지 않은 상태로 부모 컴포넌트가 렌더링 되기 때문에 첫 마운트 시 로그아웃 상태의 화면이 먼저 뜨고, 로그인 데이터가 업데이트 되면 다시 화면도 업데이트 됐던 것 -> 최상위 컴포넌트가 업데이트 되기 때문에 하위 컴포넌트도 함께 업데이트 되면서 api 호출도 두 번 이루어지게 됨.
  3. 데이터가 먼저 불러와진 다음 화면이 렌더링 되어야 함. 그러나 useEffect는 화면이 그려진 후에 실행되기 때문에 가장 첫 화면과 유저 데이터 간의 싱크가 맞지 않게 됨.

해결

  1. 하위 컴포넌트에서 불러오던 fetch를 로그인 체크 함수의 success 콜백 함수로 전달.
  2. 로그인 체크가 완료되면 유저 데이터를 불러오고, 컴포넌트 구성에 필요한 데이터를 하위 컴포넌트로 한번에 전달하여 컴포넌트가 렌더링 되도록 변경. (사실 context나 커스텀 훅까지는 필요 없던 것, 과감히 제거하였다.)
  3. 이 로그인 체크 함수는 useLayoutEffect 안에서 실행하여 화면이 그려지기 전에 데이터를 전달할 수 있도록 함.
    ➡️ 여기까지 했더니 1000ms 정도의 화면 딜레이가 20ms까지 줄어들었다.
  4. 20ms 정도의 시간이긴 하지만 그래도 잠시 빈 화면이 노출되므로 이 시간 동안은 로딩 컴포넌트가 노출되도록 ux까지 고려하여 완성시켰다.
profile
빙글빙글 돌아가는 주니어 프론트엔드 개발자

0개의 댓글