[230518] 라이프 사이클 | Hook 렌더링 과정 | Custom Hook

윤지수·2023년 5월 18일
0
post-thumbnail

⚛️ 클래스 컴포넌트 - 라이프 사이클

컴포넌트는 세 가지 단계의 생애 주기를 가지고 있다.

  1. 마운트 : 컴포넌트가 맨 처음 화면에 그려지는 때
    장착할 때 사람이 말을 탈 때 카메라에 렌즈 장착할 때행위를 하려고 준비한 상태
    처음 state와 props를 가지고 컴포넌트를 생성한다.
  • 마운트 단계에서 실행되는 함수
    1. constructor
    2. getDerivedStateFromProps
    3. componetWillMount
    4. render
    5. componetDidMount
  1. 업데이트 : 인터랙션을 통해 컴포넌트가 업데이트 될 때
    마운트가 완전히 완료된 후 상태값이나 props의 변화가 생겼을 때, 리액트는 이를 감지하고 컴포넌트를 업데이트해준다.
  • 업데이트 단계에서 실행되는 함수
    1. getDerivedStateFromProps
    2. shouldComponentUpdate(true를 반환할 경우)
    3. componentWillUpdate
    4. render
    5. componentDidUpdate
  1. 언마운트: 컴포넌트가 더 이상 쓸모 없어져서 화면에서 사라질 때
    컴포넌트를 완전히 DOM에서 제거한다.
    ex. 동일한 페이지(SPA) 안에서 기존의 컴포넌트를 지우고 새로운 컴포넌트를 보여줄 때(다른 페이지로 이동하는 것 같은 효과)
    조건부 렌더링으로 보여주고 안보여주고 해서(업데이트) 화면에서 지워질 때
  • 언마운트 단계에서 실행되는 함수
    componentWillUnmount

    💡 함수를 선언하기만 해도 해당 시점에 리액트가 자동으로 함수를 실행시킨다.
    💡 리액트 17 버전부터 componetWillMount, componentWillUpdate 함수들은 레거시로 분류되어 사용이 더 이상 권장되지 않는다.

⚛️ 함수형 컴포넌트 - Hook 렌더링 과정

lazy initialize

useState를 사용하여 state를 초기화하는 과정을 lazy하게 실행하는 것

import { useState } from "react";

// 부하가 큰, 처리가 오래 걸리는 함수
function getName() {
  console.log("사실은 겁나 오래기다리는중...");
  return "개리";
}

function App() {
  const [name, setName] = useState(getName());
  const [num, setNum] = useState(0);
  return (
    <>
      <div>
        안녕하세요 {name}! 현재 숫자는{num}입니다
      </div>
      {/* 함수형 업데이트 */}
      <button onClick={() => setNum((prevNum) => prevNum + 1)}>{num}</button>
      {/* <button onClick={() => setNum(num+1)}>{num}</button> */}
    </>
  );
}

export default App;

컴포넌트가 렌더링될 때마다 useState가 실행돼서 getName 함수를 실행한다. -> 비효율적

함수 자체를 값으로 넘기고 리액트가 그 함수를 실행하도록 만들어서 최적화시킬 수 있다.

const [name, setName] = useState(getName);

마운트, 업데이트 과정을 리액트가 기억하고 있다. useState가 매번 실행되면서 마운트 과정인지, 업데이트 과정인지 검사를 하는데, 최초 컴포넌트 렌더링(컴포넌트 생성)이면 초기화를 진행하고 리렌더링이면 업데이트를 진행한다. 그리고 최초 초기화 진행 과정에서만 getName 함수를 실행하게 된다. 이후 업데이트(리렌더링 과정)에서 getName을 실행하는 부분이 생략된다.

복잡한 함수의 결과값으로 state 값을 초기화해야할 때, 앞으로 결과값이 바뀔 필요가 없을 때, 반복적으로 실행해야할 때 함수 자체를 넘겨주게 되면 마운트 됐을 때 한번만 실행되도록 설정하는 것이다.
lazy initialize는 초기값 계산에 많은 비용(연산 시간, 메모리 등)이 소요될 때 비효율적인 부분을 최적화하는데 사용할 수 있다.

Hook 렌더링 과정

함수형 컴포넌트에서는 라이프 사이클 메서드를 직접적으로 사용하진 않는다. Hook으로 간접적으로 사용한다.
(이미지 출처 - 멋쟁이사자처럼 프론트엔드스쿨 5기 교안)

마운트

  1. Run lazy initializers: 컴포넌트가 만들어질 때 props, state 등의 값을 초기화한다. 최초 마운트가 될 때 단 1번 실행된다.
  2. Render: 컴포넌트 함수들이 실행된다. (return)
  3. React updates DOM
  4. Run LayoutEffects
  5. Browser paints screen
  6. Run Effects

업데이트

  1. Render
  2. React updates DOM: 기존 가상돔과 렌더링된 가상돔을 비교한다.
  3. Cleanup LayoutEffects: *
  4. Run LayoutEffects: 컴포넌트가 브라우저에 그려지기 전에 동작한다.
  5. Browser apints screen: 브라우저에 그려준다.
  6. Cleanup Effects: *
  7. Run Effects: 컴포넌트가 브라우저에 그려진 후에 동작한다.

* useLayoutEffect, useEffect Hook 안에서 return 키워드로 반환하는 함수를 Cleanup 함수라고 표현한다. Cleanup 함수는 useLayoutEffect 함수, useEffect 함수가 실행되기 전에 실행된다.

언마운트

  1. Cleanup LayoutEffects
  2. Cleanup Effects

💡 함수를 선언하기만 해도 해당 시점에 리액트가 자동으로 함수를 실행시킨다.

🔍 useLayoutEffect와 useEffect의 차이
- useLayoutEffect: 컴포넌트가 화면에 그려지기 전 동작 -> 번쩍거리는 현상 X
- useEffect: 컴포넌트가 화면에 그려진 후 동작 -> 번쩍거리는 현상 O

import { useEffect, useState, useLayoutEffect } from "react";
export default function App() {
  const [num, setNum] = useState(0);
  // 렌더링되고(11) 실행돼서(10) 번쩍 거림
  useEffect(() => {
    setNum(10);
  }, [num]);
  // 렌더링되기 전에 실행돼서 번쩍거리지 않음
  useLayoutEffect(() => {
    setNum(10);
  }, [num]);
  return (
    <>
      <div>{num}</div>
      <button onClick={() => setNum((prevNum) => prevNum + 1)}>클릭</button>
    </>
  );
}

기능은 똑같지만 실행되는 타이밍이 다르다

- useLayoutEffect: 동기적으로 동작
- useEffect: 비동기적으로 동작
useLayoutEffect는 번쩍거리는 현상이 없어서 좋은 사용자 경험을 제공할 수 있지만 useLayoutEffect가 오래걸리는 작업이라면 화면도 늦게 렌더링되기 때문에 사용자 경험에 좋지 않다.

💡 useEffect를 사용하다가 렌더링에 문제가 있다! 싶으면 useLayoutEffect를 사용하자

⚛️ Custom Hook

Custom Hook을 만들어 반복되는 로직들을 분리하고 재사용할 수 있다.
src 디렉터리에 hooks 디렉터리를 만들고 use라는 키워드로 시작하는 파일을 만들고 그 안에 함수를 작성한다.
그 안에서 useState, useEffect, useReducer, useCallback 등 Hooks 를 사용하여 원하는 기능을 구현해주고, 컴포넌트에서 사용하고 싶은 값들을 반환한다.

import { useState } from "react";

export default function useInput(initState) {
  const [value, setValue] = useState(initState);
  function onChange(e) {
    setValue(e.target.value);
  }

  return [value, onChange];
}

src/hooks/useInput.jsx

💡 fetch를 hook으로 많이 사용한다

0개의 댓글