그림 그리는 개발자
  • 리액트 렌더링 시점 및 훅 호출 순서 확인해보기
    2022년 05월 16일 08시 00분 15초에 업로드 된 글입니다.
    작성자: 루루개발자


    안녕하세요. 루루개발자 입니다.
    요즘 한창 리액트 렌더링 시점과 훅 호출 순서 등에 대한 테스트를 진행해보고 있는데,
    테스트 과정 및 결과를 공유드리고자 합니다.

    먼저 다음과 같은 파일 및 코드가 있다고 가정해봅시다.

    -- index.tsx

    import { useCallback, useEffect, useState } from "react";
    import { AppTestBox } from "../../../src/components/boxes/app-test-box/app-test-box";
    import { useTest } from "../../../src/hooks/use-test-hook/use-test.hook";
    
    const Index = () => {
      const [count, setCount] = useState(() => {
        console.log('count useState() 호출됨');
        return 0;
      });
    
      const refHandler = useCallback((el: any) => {
        console.log('refHandler() 호출됨');
      }, []);
    
      useEffect(() => {
        console.log('첫번째 useEffect() 호출됨');
      }, []);
    
      const [count2, setCount2] = useState(() => {
        console.log('count2 useState() 호출됨');
        return 0;
      });
    
      const renderingCheck = useCallback(() => {
        console.log('렌더링됨');
        return '';
      }, []);
    
      useTest();
    
      useEffect(() => {
        console.log('두번째 useEffect() 호출됨');
      }, [count]);
    
      const countPlus = useCallback(() => {
        console.log('countPlus() 호출됨');
        setCount(count + 1);
      }, [count]);
    
      useEffect(() => {
        console.log('세번째 useEffect() 호출됨');
      });
    
      const countPlus2 = useCallback(() => {
        console.log('countPlus2() 호출됨');
        setCount2(count2 + 1);
      }, [count2]);
    
      return (
        <div ref={refHandler}>
          <AppTestBox
            callback={() => { console.log('AppTestBox 컴포넌트의 callback() 호출됨.') }}></AppTestBox>
          {
            renderingCheck()
          }
          <div>
            { count }<br />
            { count2 }
          </div>
          <button onClick={countPlus}>count 증가</button>
          <button onClick={countPlus2}>count2 증가</button>
        </div>
      );
    };
    
    export default Index;


    -- app-test-box.tsx

    import { useCallback, useEffect, useState } from "react";
    
    interface Props {
      callback: () => void;
    }
    
    export const AppTestBox = (props: Props) => {
      const [hookVar1, setHookVar1] = useState(() => {
        console.log('AppTestBox 컴포넌트의 hookVar1 useState() 호출됨.');
        return 0;
      });
    
      const [hookVar2, setHookVar2] = useState(() => {
        console.log('AppTestBox 컴포넌트의 hookVar2 useState() 호출됨.');
        return 0;
      });
    
      useEffect(() => {
        console.log('AppTestBox 컴포넌트의 첫번째 useEffect() 호출됨.');
      });
    
      const refHandler = useCallback((el: any) => {
        console.log('AppTestBox 컴포넌트의 refHandler() 호출됨.');
      }, []);
    
      const _callback = useCallback(() => {
        console.log('AppTestBox 렌더링됨.');
        props.callback();
        return (
          <div ref={refHandler}></div>
        );
      }, [props, refHandler]);
    
      return _callback();
    };


    -- use-test.hook.ts

    import { useEffect, useState } from "react";
    
    export const useTest = () => {
      const [hookVar1, setHookVar1] = useState(() => {
        console.log('useTest 훅의 hookVar1 useState() 호출됨.');
        return 0;
      });
    
      const [hookVar2, setHookVar2] = useState(() => {
        console.log('useTest 훅의 hookVar2 useState() 호출됨.');
        return 0;
      });
    
      useEffect(() => {
        console.log('useTest 훅의 첫번째 useEffect() 호출됨');
      });
    
      useEffect(() => {
        console.log('useTest 훅의 두번째 useEffect() 호출됨');
      });
    };



    index.tsx 파일은 페이지 컴포넌트이고, app-test-box.tsx 파일은 제가 만든 컴포넌트 입니다. use-test.hook.ts 파일 또한 제가 만든 커스텀 훅입니다.

    위 상태에서 index.tsx 페이지 컴포넌트에 접근하면 브라우저의 콘솔창은 아래와 같이 표시됩니다.


    1. 부모 컴포넌트의 useState 가 호출됩니다.
    2. 부모 컴포넌트에 명시된 커스텀 훅의 useState 가 호출됩니다.
    3. 부모 컴포넌트의 뷰(View)가 렌더링 됩니다.
    4. 자식 컴포넌트인 AppTestBox 의 useState 가 호출됩니다.
    5. 자식 컴포넌트인 AppTestBox 의 뷰(View)가 렌더링 됩니다.
    6. 자식 컴포넌트인 AppTestBox 에서 요소의 ref 속성에 넘겼던 콜백함수가 호출됩니다.
    7. 부모 컴포넌트에서 요소의 ref 속성에 넘겼던 콜백함수가 호출됩니다.
    8. 자식 컴포넌트인 AppTestBox 의 useEffect 함수들이 호출됩니다.
    9. 부모 컴포넌트에 명시된 useEffect 함수들이 호출됩니다.
    9-1. 작성한 순서에 따라 useEffect 함수는 순차적으로 호출됩니다.
    9-2. 부모컴포넌트에 작성된 useEffect 함수들 중간에 커스텀 훅이 존재하고 커스텀 훅 안에 useEffect 가 존재하면 커스텀 훅 안의 useEffect 가 호출되고, 그 다음에 명시된 부모컴포넌트의 useEffect 가 호출됩니다.


    위 결과로 인해 알 수 있는 점을 다음과 같이 정리해보았습니다.

    1. 요소의 ref 속성에 넘기는 콜백함수는 부모컴포넌트보다 자식컴포넌트에서 먼저 호출된다.
    2. 렌더링 되기 전에 해당 컴포넌트의 모든 useState 가 호출된다.
    3. 부모 컴포넌트의 뷰(View)가 렌더링 된 이후에 자식 컴포넌트의 useState 가 호출된다.
    4. 부모컴포넌트보다 자식컴포넌트의 useEffect 가 먼저 호출된다.
    5. 작성된 useEffect 함수 중간에 커스텀 훅이 존재하면 커스텀 훅 안에 존재하는 useEffect 가 모두 호출되고 그 다음 작성된 useEffect 가 호출된다.


    테스트를 진행해보고 결과를 정리해보니 모르고 있었던 부분들이 있었네요.테스트를 해보길 잘한 것 같습니다.

    읽어주셔서 감사합니다. :)

    댓글