[React] useEffect 실행 순서

초코침·2023년 4월 24일
3

React

목록 보기
4/14

useEffect는 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록할 수 있는 React Hook이다.


리액트 공식 문서를 보면, useEffect의 첫 번째 파라미터인 setup 함수에 대해 다음과 같이 나와있다.

setup: The function with your Effect’s logic. Your setup function may also optionally return a cleanup function. When your component is first added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. After your component is removed from the DOM, React will run your cleanup function one last time.

setup 함수는 optional dependencies에 무엇이 오든 간에 컴포넌트가 DOM에 마운트되면 일단 실행된다.

모든 리렌더링이 발생한 다음에는 (cleanup 함수를 작성했다면) 변경되기 이전 값으로 cleanup 함수를 호출하며 이후 변경된 새로운 값으로 setup 함수를 호출한다.

cleanup 함수

useEffect의 setup 함수가 리턴하는 함수로, 컴포넌트가 언마운트되기 전이나 업데이트되기 직전에 수행할 작업이 있는 경우 사용한다.


즉, (optional dependencies가 있는 경우) useEffect의 실행 순서는

  1. 컴포넌트 마운트 시 setup 함수 호출
  2. props/state 업데이트
  3. 리렌더링 발생
  4. 변경 이전 값으로 cleanup 함수 호출
  5. 변경 이후 값으로 setup 함수 호출

useEffect 실행 순서 확인하기

부모 컴포넌트가 될 Example과 부모 컴포넌트의 상태에 따라 보여지거나 숨겨질 Form 컴포넌트를 만들었다.

예시 코드

Example

import React, { useState, useEffect } from 'react';
import Form from './Form';

const Example = () => {
  const [visible, setVisible] = useState(true);
  const [text, setText] = useState('초기값');

  useEffect(() => {
    console.log('[Example.js - 1] Example 컴포넌트가 화면에 나타났어요! (마운트)');

    return () => {
      console.log('[Example.js - 1] Example 컴포넌트가 화면에서 사라집니다! (언마운트)');
    };
  }, []);

  useEffect(() => {
    console.log('[Example.js - 2] visible 업데이트!');

    return () => {
      console.log(`[Example.js - 2] 현재 visible 값: ${visible}`);
      console.log('[Example.js - 2] visible이 곧 업데이트 됩니다!');
    };
  }, [visible]);

  useEffect(() => {
    console.log('[Example.js - 3] 렌더링 완료~');
  });

  const handleClick = () => {
    setVisible(!visible);
  };

  return (
    <div style={{ marginTop: '50px', marginLeft: '50px' }}>
      <span style={{ marginRight: '20px' }}>text: {text}</span>
      <button onClick={handleClick}>{visible ? 'form 숨기기' : 'form 보이기'}</button>
      <hr />
      {visible && <Form setText={setText} />}
    </div>
  );
};

export default Example;

Form

import React, { useState, useEffect } from 'react';

const Form = ({ setText }) => {
  const [input, setInput] = useState('');

  useEffect(() => {
    console.log('[Form.js - 1] Form 컴포넌트가 화면에 나타났어요! (마운트)');

    return () => {
      console.log('[Form.js - 1] Form 컴포넌트가 화면에서 사라집니다! (언마운트)');
    };
  }, []);

  useEffect(() => {
    console.log('[Form.js - 2] input 업데이트!');

    return () => {
      console.log(`[Form.js - 2] 현재 input 값: ${input}`);
      console.log('[Form.js - 2] input이 곧 업데이트 됩니다!');
    };
  }, [input]);

  useEffect(() => {
    console.log('[Form.js - 3] 렌더링 완료~');
  });

  const handleClick = () => {
    setText(input);
    setInput('');
  };

  const handleChange = (e) => {
    setInput(e.target.value);
  };

  return (
    <div>
      <h3>Form</h3>
      <input type="text" value={input} onChange={handleChange} />
      <button onClick={handleClick}>변경</button>
    </div>
  );
};

export default Form;



첫 렌더링

Example과 Form이 처음으로 화면에 마운트되는 시점이다.

console

자식 컴포넌트가 먼저 렌더링 된 다음에 부모 컴포넌트가 렌더링되기 때문에 자식 컴포넌트의 setup 함수들이 부모 컴포넌트보다 먼저 실행된다.

useEffect 코드를 작성한 순서대로 setup 함수가 호출되고 있으며, optional dependencies를 작성한 Effect도 실행되었다. → setup 함수는 (optional dependencies 작성 여부와 관계 없이) 마운트 시 무조건 한 번 실행되기 때문!


Example의 visible 변경

form 숨기기 버튼을 눌러 visible의 상태를 false로 변경해보았다.

console

visible의 상태가 false로 바뀜에 따라 Form 컴포넌트와 input이 화면에서 언마운트되며, 언마운트됨에 따라 setup 함수의 리턴 부분인 cleanup 함수가 각각 실행된다.

useEffect(() => {
  console.log('[Form.js - 1] Form 컴포넌트가 화면에 나타났어요! (마운트)');

  return () => {
    console.log('[Form.js - 1] Form 컴포넌트가 화면에서 사라집니다! (언마운트)');
  };
}, []);

useEffect(() => {
  console.log('[Form.js - 2] input 업데이트!');

  return () => {
    console.log(`[Form.js - 2] 현재 input 값: ${input}`);
    console.log('[Form.js - 2] input이 곧 업데이트 됩니다!');
  };
}, [input]);

이때 실행되는 cleanup 함수 내부에서는 변경 이전 값을 참조하므로 input은 초기값인 ‘’(빈 문자열)다.


하위 컴포넌트인 Form에 대한 리렌더링과 Effect 실행이 끝난 이후, 부모 컴포넌트의 리렌더링이 시작된다.

visible의 상태가 false로 바뀌었기 때문에, 변경으로 인한 리렌더링 발생 후 cleanup 함수가 먼저 실행되고 그 다음 setup 함수가 실행된다.

useEffect(() => {
  console.log('[Example.js - 2] visible 업데이트!');

  return () => {
    console.log(`[Example.js - 2] 현재 visible 값: ${visible}`);
    console.log('[Example.js - 2] visible이 곧 업데이트 됩니다!');
  };
}, [visible]);

cleanup 함수 내부에서는 visible이전 값인 true를 참조하여 로그를 찍는다.


Form의 input 입력

Form의 < input >에 텍스트를 입력해 input 상태 값을 변경시켰다.

console - a 입력 시

Form 컴포넌트의 input 값에 변경이 발생했기 때문에 Form 컴포넌트의 리렌더링이 발생한다.

이후 input을 참조하고 있는 Effect Hook의 cleanup 함수가 호출되고, 그 다음 setup 함수가 호출된다.

마찬가지로 cleanup에서 input 값은 변경 이전 값인 ‘’(빈 문자열)이고, 그 다음 실행되는 setup에서 input 값은 변경 이후 값인 ‘a’다.

useEffect(() => {
  console.log('[Form.js - 2] input 업데이트!');

  return () => {
    console.log(`[Form.js - 2] 현재 input 값: ${input}`);
    console.log('[Form.js - 2] input이 곧 업데이트 됩니다!');
  };
}, [input]);

console - ab 입력 시

이번에도 Form 컴포넌트의 input 값에 변경이 발생했기 때문에 Form 컴포넌트의 리렌더링이 발생한다.

마찬가지로 cleanup 함수와 setup 함수가 차례대로 실행된다.


Example의 text 변경

에 값을 입력하고 변경 버튼을 눌러 Example의 text 상태 값을 변경했다.

console

변경 버튼 클릭 시 부모 컴포넌트(Example)의 text와 자식 컴포넌트(Form)의 input에 모두 변경 사항이 생겨 두 컴포넌트 모두 리렌더링이 필요한 상황이다.

const handleClick = () => {
  setText(input);
  setInput('');
};

Form에서는 input 값을 변경하고 cleanup 함수와 setup 함수를 차례로 호출하며 리렌더링과 Effect 실행을 마친다.

useEffect(() => {
  console.log('[Form.js - 2] input 업데이트!');

  return () => {
    console.log(`[Form.js - 2] 현재 input 값: ${input}`);
    console.log('[Form.js - 2] input이 곧 업데이트 됩니다!');
  };
}, [input]);

Example에서는 리렌더링을 마친 후, 매 리렌더링 이후 실행되는 setup이 실행된다.

useEffect(() => {
	console.log('[Example.js - 3] 렌더링 완료~');
});

정리

useEffect는 (리)렌더링이 발생한 다음 실행된다. 즉, 상태 변경이 먼저 일어난다.

자식 컴포넌트의 useEffect가 먼저 실행되고 그 다음 부모의 useEffect가 실행된다. 즉, 자식 컴포넌트가 먼저 렌더링된다.

useEffect의 setup 함수는 컴포넌트가 마운트될 때 무조건 한 번 실행된다. 따라서 오직 의존성 배열 요소에 변경이 있을 때만 실행시키려면 상태 값을 직접 비교하는 로직이 필요하다.

cleanup 함수가 있는 경우 변경이 발생하기 이전 값을 참조하여 cleanup 함수를 실행하고, 그 다음에 변경이 반영된 값을 참조setup 함수를 실행한다.

profile
블로그 이사중 🚚 (https://sungjihyun.vercel.app)

0개의 댓글