몰랐던 useEffect의 실행순서 (feat Suspense)

Kyle·2021년 12월 12일
17

React

목록 보기
3/3
post-custom-banner

며칠 전 코딩을 하면서 내 예상대로 렌더링이 되지 않는 경우가 있었다. 그리고 디버깅하는 과정에서

그동안 내가 잘못 예상하고 있었던 useEffect의 실행 순서에 대해 알게 됐다.

useEffect에 동작에 대해서 다시 한번 더 생각하는 기회가 됐다.

아래의 모든 글은 App, OuterBox, InnerBox로 구성돼있다.

컴포넌트 구조는 App컴포넌트부터 최상단 컴포넌트이다 App > OuterBox > InnerBox

export default function App() {
 
  return (
    <div className="App">
      <h1>useEffect 순서 테스트</h1>
      <OuterBox />
    </div>
  );
}

const OuterBox: FC = () => {
  return (
    <>
      <h2>Outer BOX</h2>
      <InnerBox />
    </>
  );
};

const InnerBox: FC = () => {
  return <h2>Inner Box</h2>
};

기본적인 useEffect 실행순서

결과부터 말하면 하위에 있는 컴포넌트 먼저 실행된다.

아래 코드를 보고 한번 결과를 예상해 보자.

지금까지 나는 아무생각없이 1 → 2 → 3 순서로 실행될 줄 알았다.

function App() {
  useEffect(() => {
    console.log(1);
  }, []);

  return ...
}

const OuterBox: FC = () => {
  useEffect(() => {
    console.log(2);
  }, []);

  return ...
};

const InnerBox: FC = () => {
  useEffect(() => {
    console.log(3);
  }, []);

  return ...
};
//실행결과
3
2
1

생각해 보면 당연했다.

useEffect는 컴포넌트가 렌더링이 된 후에 실행되는 것이다. Apprender되기 위해서는 OuterBox가 먼저 렌더링이 되어야 되고, OuterBox가 완전히 렌더링 되기 위해서는 InnerBox가 렌더링이 되어야 한다.

그러면 우리는 이제 알게 됐다. 하위에 있는 컴포넌트의 useEffect가 먼저 실행되는구나

Suspense를 사용해 비동기처리에서 로딩을 할 때 순서는?

위의 코드에서 약간의 비동기 처리와 Suspense를 활용해 로딩 처리를 추가해 보겠다.

아래 코드에서 InnerBoxconst repoStars = useRecoilValue(getStars);는 단순히 깃허브 star 개수를 가져오는 비동기 요청이라고 생각하면 된다.

OuterBox에서 로딩 처리를 위해 InnerBoxSuspense로 감싸줬다.

아래 코드를 보고 한번 결과를 예상 해보자.

function App() {
  useEffect(() => {
    console.log(1);
  }, []);

  return ...
}

const OuterBox: FC = () => {
  useEffect(() => {
    console.log(2);
  }, []);
  return (
    <>
      <h2>Outer BOX</h2>
      <Suspense fallback={<div>loading...</div>}>
        <InnerBox />
      </Suspense>
    </>
  );
};

const InnerBox: FC = () => {
  //깃허브 stars 수를 갖고오는 비동기 요청
  const repoStars = useRecoilValue(getStars);

  useEffect(() => {
    console.log(3);
  }, []);

  return ...
};
//실행결과
2
1
3

3번이 가장 마지막에 실행된다.

가장 하위 컴포넌트임에도 불구하고 가장 마지막에 실행되는 이유는 무엇일까??

useEffect는 컴포넌트의 렌더링이 완료가 되면 실행되기 때문에 3이 가장 마지막에 실행된 것이다.

**InnerBox에서 비동기 요청을 할 때 Suspense한테 렌더링을 interrupt** 당하기 때문에 OuterBox, App이 먼저 실행 되고 비동기 요청이 완료된 시점에 InnerBox가 렌더링이 되면서 3이 출력된 것이다.

결국 기억할 것은 useEffect는 컴포넌트의 렌더링이 끝나면 실행된다.

만약 방금전 InnerBox가 이렇게 생겼을때는?

const InnerBox: FC = () => {
  console.log(5);
  // 비동기 요청
  const repoStars = useRecoilValue(getStars); 

  console.log(4);

  useEffect(() => {
    console.log(3);
  });
	...
};
//실행결과
5
2
1
4
3

당연한 결과일 수 있겠지만 나는 조금 헷갈렸다. (5가 먼저 출력이 될까..? 라는 생각을 했다. )

이 결과로 인해 Suspense는 비동기 요청을 만나는 그 순간 interrupt해 간다는 것을 알 수 있다.

마지막으로 Suspense를 활용하지 않고 컴포넌트 내에서 로딩 처리했을 때의 결과를 알아보자.

InnerBox 내부에서 로딩 처리를 했을 때

마지막으로 Suspense를 활용하지 않고 내부에서 로딩 처리를 했다.

recoiluseRecoilValueLoadable를 활용해서 InnerBox안에서 로딩 처리를 해줬다.

아래 코드를 보고 한번 결과를 예상해 보자.

// App, OuterBox는 위에와 같고 Suspense만 지워줬다. 
// (Suspense를 지워도 결과는 같았다.)

const InnerBox: FC = () => {
  const repoStarsLodable = useRecoilValueLoadable(getStars);

  console.log(4);

  useEffect(() => {
    console.log(3);
  });

  return (
    <>
      {repoStarsLodable.state === "loading" && <div>loading...</div>}
      {repoStarsLodable.state === "hasValue" && (
        <>
          <h2>Inner Box</h2>
          <h3>내 레포 star 개수는 {repoStarsLodable.contents}</h3>
        </>
      )}
    </>
  );
};
//실행결과
4 //
3 // loading...일 때 출력되는 로그
2
1
4 // 
3 // 로딩 후 출력되는 로그

InnerBox 내부에서 로딩까지 하면서 로딩 처리될 때도 출력이 되고 로딩이 끝나고 나서 4,3이 한 번 더 실행되는 결과를 볼 수 있다.

마무리

사실 useEffect의 동작과정만 제대로 생각하면 바로 알 수 있는 것들이었다...

그래도 이제 알았으니 됐다!

profile
Kyle 발전기
post-custom-banner

2개의 댓글

comment-user-thumbnail
2022년 9월 17일

덕분에 알아가네요! 잘 읽고갑니다!!

답글 달기
comment-user-thumbnail
2023년 8월 31일

저도 상위 컴포넌트부터 렌더링 되는 줄 알았는데, 하위 컴포넌트부터 렌더링 된다니 놀랍네요 🫢
덕분에 아이디어 얻고 해결했습니다!

답글 달기