본문 바로가기
React

React Component와 Hooks의 Lifecycle

by 2__50 2023. 2. 9.
공부한 내용을 정리한 글입니다 
내용에 오류가 있거나 더 좋은 의견이 있다면 댓글로 남겨주세요.
배움에 큰 도움이 됩니다. 🖋

 

 

Component의 생명주기


https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

 

모든 React component는 동일한 생명 주기를 가진다.

  • mount : component가 화면에 추가될 때
  • update : 새로운 props, state를 받을 때
  • unmounts : component가 화면에서 제거될 때

constructor
- component가 생성될 때 가장 먼저 실행되는 함수로, component 만드는 과정에서 미리 해야 할 작업을 한다
- method를 바인딩하거나 state를 초기화한다. 해당 작업이 없다면 constructor를 구현하지 않아도 된다


render

- class component에서 반드시 구현돼야하는 유일한 method

- this.props, this,state를 활용하여 무언가를 반환하는데, 보통은 JSX를 이용해 React element를 생성한다


componentDidMount

- component가 생성된 직후(tree에 삽입된 직후)에 호출되는 method
- eventListener 부착, API 요청 등을 한다

componentDidUpdate
- 갱신이 일어난 직후 호출되며, 이전, 지금의 페이지가 바뀐 시점에 어떠한 작업을 하고싶을 때 사용한다
- 최초 렌더링에서는 호출되지 않는다. 


componentWillUnmount

- component가 DOM에서 제거되기 직전 호출되는 method
- EventRemove, timer 제거, api 요청 취소 작업 등을 한다

 

자세한내용

 

 

Hooks의 생명주기


https://beta.reactjs.org/learn/lifecycle-of-reactive-effects 을 바탕으로 배운 내용을 정리한 글.

자세한 내용은 상단 링크 참조

 

https://github.com/Wavez/react-hooks-lifecycle

 

Hooks의 생명주기는 Component와 다르다. 우리는 이전에 Effect를 Component의 mount, update, unmount 시점에 맞춰 생각했다. 그러나 Effect가 하는 일은 딱 2가지다. 동기화의 시작과 중지. 이 주기는 Effect가 의존하는 props, state의 값이 변경됨에 따라 여러 번 발생할 수 있다. Effect는 동기화가 필요할 때마다 실행된다.

 

Effect에게 Component의 mount, update, unmount 여부는 중요하지 않다. 동기화를 시작하고 중지하는 것에 집중한다.

 

https://beta.reactjs.org/learn/lifecycle-of-reactive-effects

 

function ChatRoom({ roomId /* "general" */ }) {

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId); // Connects to the "general" room
    connection.connect();
    return () => {
      connection.disconnect(); // Disconnects from the "general" room
    };
  }, [roomId]);
  // ...

 

Effect가 다시 동기화되는 주된 이유는 사용중인 일부 데이터가 변경됐을 때다.

예를 들어 초기 렌더링 : ["general"] -> 다음 렌더링 : ["travel"] 이라면 React는 "general"과 "travel"을 비교한다. 이들은 다른 값(Object.is와 비교)이므로 React는 Effect를 다시 동기화한다. 반면에 Component가 다시 렌더링되지만 roomId가 변경되지 않은 경우 Effect는 동일한 방에 연결된 상태로 유지된다.

roomId : "travel" -> "music"
1. React가 cleanup을 호출해 Effect 동기화를 중지한다. ("travel" 방과의 연결 해제)
2. 새로운 roomId의 ChatRoom Component에 동기화를 시작한다. ("music" 방에 연결)
3. 사용자가 다른 화면으로 이동하면 ChatRoom이 unmount된다. 이제 연결 상태를 유지할 필요가 없어졌으므로 React는 Effect 동기화를 중지한다. (music" 방과 연결 해제)

 

 

roomId는 왜 Effect의 종속성 배열에 필요할까? 


동기화에 필요하기 때문이다. Effect 안에서 참조하는 roomId 값은 시간이 지남에 따라 변경될 수 있다.
roomId가 변함에 따라 Effect 내부 로직의 결과값도 변경되므로 그 때마다 Effect가 동기화를 진행할 수 있도록 알려주는 것이다.

 

 

각 Effect는 별도의 동기화 과정을 나타낸다


  • ASIS
function ChatRoom({ roomId }) {
  useEffect(() => {
    logVisit(roomId);
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId]);
  // ...
}

 

위 코드는 사용자 방문을 기록하는 logVisit, url에 연결을 하는 connection을 [roomId]를 종속성으로 가지는 하나의 Effect 안에서 수행하고있다. 두 작업 모두 모두 roomId 값을 사용하기 때문이다.

그런데 만약 connection에서 사용하는 serverUrl 또한 변경 가능하도록 정책이 바뀌어 종속성에 추가된다고 생각해보자. logVisit은 같은 Effect 내에 있기 때문에 serverUrl 값을 사용하지 않음에도 해당 값이 변경될 때마다 새로 호출될 것이다. 따라서 별도의 일을 수행하는 logVisit과 connection은 별도의 Effect 내에서 동기화해야 한다.

 

  • TOBE
function ChatRoom({ roomId }) {
  useEffect(() => {
    logVisit(roomId);
  }, [roomId]);

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    // ...
  }, [roomId]);
  // ...
}

 

 

종속성에 전하는 것은 시간이 흐르며 변하는 값이어야 한다


Effect에서 serverUrl, roomId를 읽고 있지만 serverUrl은 한 번 정해지면 바뀌지 않는 값이다. 그렇기 때문에 사용자의 상호작용에 따라 값이 변하는 roomId만 종속성에 추가한다.

 

 

props와 state를 이용해 계산하는 값도 종속성에 포함된다


prop, state가 변경되면 Component가 리렌더링되고 여기서 계산된 값도 변경된다. 렌더링 시 계산되는 값은 리렌더링으로 인해 변경될 수 있으므로 Effect의 종속성에 포함한다. 

 

function ChatRoom({ roomId, selectedServerUrl }) { // roomId is reactive
  const settings = useContext(SettingsContext); // settings is reactive
  const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl is reactive
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId); // Your Effect reads roomId and serverUrl
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId, serverUrl]); // So it needs to re-synchronize when either of them changes!
  // ...
}

 

 

ref.current는 종속성에 포함하지 않는다


useRef로 생성한 객체는 Component의 전 생애주기 동안 유지된다. ref.current로 값을 변경할 수는 있지만 이로 인해 렌더링이 다시 발생하지 않고 useEffect가 트리거되지 않기 때문에 종속성에 포함시킬 필요가 없다.

 

+) 갱신된 ref의 값을 사용해야 한다면 ref와 Effect 대신 useCallback으로 함수를 메모하는 방법을 생각해 볼 수 있다.

 

const refRect = useCallback((node) => {
    if (node === null) return
    setReact(node.getBoundingClientRect());
 }, []);

 

 

객체와 함수를 종속성에 전달하면 안되는 이유


경우에 따라 props에서 객체를 받을 수 있다. 주의할 점은, 부모 Component가 객체를 렌더링 중에 생성한다는 것이다.
이렇게 생성된 객체를 Effect에서 읽으면 값이 부모 Component가 리렌더링될 때마다 달라지기 때문에 Effect가 매번 다시 동기화된다.

 

<ChatRoom
  roomId={roomId}
  options={{
    serverUrl: serverUrl,
    roomId: roomId
  }}
/>

 

function ChatRoom({ options /* 객체 */ }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [options]); // ✅ All dependencies declared
  // ...

 

이는 Effect 외부에서 객체를 구조분해할당하여 해결할 수 있다.

 

function ChatRoom({ options }) {
  const [message, setMessage] = useState('');

  const { roomId, serverUrl } = options; // Effect 외부에서 options를 구조분해 할당
  useEffect(() => {
    const connection = createConnection({
      roomId: roomId,
      serverUrl: serverUrl
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId, serverUrl]); // ✅ All dependencies declared
  // ...

 

그러면 위 코드처럼 외부에서 구조분해할당한 값을 받아와 Effect 내에서 다시 객체를 생성하는 작업이 추가돼 코드가 반복된다. 그러나 Effect가 실제로 의존 하는 값을 명시적으로 나타낼 수 있고, 부모 컴포넌트가 리렌더링 되며 Effect 내에서 일부 값을 참조하고 있는 객체가 다시 생성되어도 Effect가 매번 다시 동기화 되는 것을 방지할 수 있다.

 

Hooks의 흐름


 

https://github.com/donavon/hook-flow

 

 

 

 

 

참고


https://beta.reactjs.org/learn/lifecycle-of-reactive-effects

 

Lifecycle of Reactive Effects

A JavaScript library for building user interfaces

beta.reactjs.org

https://ko.reactjs.org/docs/react-component.html

 

React.Component – React

A JavaScript library for building user interfaces

ko.reactjs.org

https://beta.reactjs.org/learn/removing-effect-dependencies

 

Removing Effect Dependencies

A JavaScript library for building user interfaces

beta.reactjs.org

 

댓글