REACT Chapter 04

박지현·2025년 1월 29일

REACT

목록 보기
4/5

4-1. Hooks 종류

1) Hooks 종류
React에는 기본적으로 10가지 내장 Hooks가 있지만, 주로 사용되는 것은 3가지

  • useState
  • useEffect
  • useCallback

2) Hooks의 작동 실습

✅ Counter 컴포넌트 예제

[Counter.js]
import { useCallback, useEffect, useState } from "react";

function Counter() {
  console.log("Render Counter!");

  const [value, setValue] = useState(0);

  useEffect(() => {
    console.log("[Function] useEffect []: 컴포넌트가 마운트 될 때, 한 번만!");
    
    const eventHandler = () => {
      console.log("click body");
    };
    
    document.body.addEventListener("click", eventHandler);

    return () => {
      console.log("[Function] useEffect return []: 컴포넌트가 언마운트 될 때,");
      document.body.removeEventListener("click", eventHandler);
    };
  }, []);

  useEffect(() => {
    console.log(
      "[Function] useEffect [value]: 컴포넌트가 마운트 될 때, + value가 변경되면,"
    );

    return () => {
      console.log(
        "[Function] useEffect return [value]: 새로 useEffect를 수행하기 전에,"
      );
    };
  }, [value]);

  const increaseValue = useCallback(() => {
    setValue((prevValue) => prevValue + 1);
  }, []);

  return (
    <div>
      <h1>value: {value}</h1>
      <button onClick={increaseValue}>Increase value</button>
    </div>
  );
}

export default Counter;

❌ 오류 발생 예시

if (value === 3) {
	useEffect() => {
    	console.log(
        	'[Fuction] useEffect [] : 컴포넌트가 마운트 될 때, 한 번만!'
    );

이렇게 코드를 사용한다면 오류 발생
Hooks는 항상 최상위에서 호출되어야 하는데, if (value === 3) { useEffect(...) }처럼 조건문 안에서 useEffect를 호출하면 React의 Hook 규칙을 위반하게 된다.

현재 코드는 useState, useEffect, useCallback 순서로 실행되는데, useCallback에서 useEffect의 결과를 참고한다.

그런데 useEffect가 if 문에 의해 실행되지 않으면 useCallback에서 예상한 상태가 설정되지 않아 오류가 발생할 수 있다.

✅ 해결 방법
useEffect를 항상 호출한 후 내부에서 if 조건을 체크하는 방식으로 수정해야 한다.

useEffect(() => {
  if (value === 3) {
    console.log("[Function] useEffect [] : 컴포넌트가 마운트 될 때, 한 번만!");
  }
}, [value]);

4-2.React 렌더링 과정

1)React 렌더링 과정
Rerendering
: 컴포넌트의 상태 즉 State 또는 Props가 변경되면서 해당 컴포넌트를 다시 실행하여 화면을 다시 그리는 것을 의미

function App(){
 const [value, setValue] = useState(0);

 return(
 	<div>
  	<h1>value : {value} </h1>
  	<button
     	onClick={() => {
    		setValue((state) => state + 1);
    	}}

    	Incresse value
  	</button>
  <div>
 );
}   

usestate(0)은 컴포넌트가 처음 마운트될 때 한 번만 실행됨

  • 즉, value의 초기값은 최초 렌더링 시 0으로 설정

이후 버튼을 클릭하면 setValue((state => state + 1);을 실행하여 value 값이 증가

2)React의 라이프 사이클
클래스형 컴포넌트와 함수형 컴포넌트로 구분 가능

1. 클래스형 컴포넌트의 라이프 사이클

[Mounting]

  • 컴포넌트가 실행되는 것 = 컴포넌트의 마운트
  • 마운트가 되었을 때 constructor 실행 -> getDerivedStateFromProps 메소드 실행 -> render 메소드 실행 -> conponentDidMount라는 메소드 실행

[Updating]

  • Updating 메소드에서 컴포넌트를 다시 rendering 할 것인지, 이번 상태 변화에 따른 rerendering은 생략할 것인지를 결정
  • 함수가 false를 반환하면 렌더링을 진행하지 않고 true를 반환하면 렌더링을 진행
  • rendering이 완료되면 마무리로 componentDidUpdate를 실행
  • getSnapshotBeforeUpdate는 DOM을 업데이트하기 전에 scroll의 위치라던가 그런 정보를 추출하기 위해 사용

[Unmounting]

  • 컴포넌트가 지워지는 것 = 언마운트

2. 함수형 컴포넌트의 라이프 사이클

[Mounting]
마운트 -> 바로 자기 자신을 실행 -> 반환된 JSX 값을 DOM에 반영
-> useEffect라는 Hook을 실행
-> 클래스형 컴포넌트의 componentWillUnmount 메소드 = useEffect hook으로 통일

[Updating]
-> 다시 함수를 실행
-> 그 JSX를 DOM에 반영
-> useEffect hook을 실행

[Unmounting]
언마운트 시엔 useEffect 함수만 마지막으로 실행

3) 실습

[FunctionalComponent.js]
import { useEffect, useState } from 'react';

function FunctionalComponent() {
//1,5. Beginning 출력
  console.log('[Function] Beginning');
  const [value, setValue] = useState(0);

  //3. [Function] useEffect [] 출력
  useEffect(() => {
    console.log('[Function] useEffect []');

    return () => {
      console.log(
        '[Function] useEffect return []'
      );
    };
  }, []);

//4. [Function] useEffect [value] 출력
  useEffect(() => {
    console.log('[Function] useEffect [value]');
	//
    return () => {
      console.log(
        '[Function] useEffect return [value]'
      );
    };
  }, [value]);

//2, 6. End 출력
  console.log('[Function] End');

  return (
    <div>
      <h1>FunctionComponent</h1>
      <h1>value: {value}</h1>
      <button
        onClick={() => {
          setValue((state) => state + 1);
        }}
      >
        Increase value
      </button>
    </div>
  );
}

export default FunctionalComponent;

🔍 React useEffect의 Cleanup 실행 순서

useEffect(() => {
    console.log('[Function] useEffect []');
    return () => {
        console.log('[Function] useEffect return []');
    };
}, []);

이 부분의 cleanup이 실행되지 않고,

useEffect(() => {
    console.log('[Function] useEffect [value]');
    return () => {
        console.log('[Function] useEffect return [value]');
    };
}, [value]);

여기의 cleanup이 먼저 실행된 후, 새로운 useEffect가 실행되는 과정


🛠 useEffect의 실행 규칙

React에서 useEffect의존성 배열(Dependency Array)을 기준으로 동작

1. Cleanup은 항상 이전 effect가 실행된 후 수행된다.

  • React는 이전 effect의 cleanup을 먼저 실행한 후, 새로운 effect를 실행

2. 의존성 배열에 따라 useEffect의 실행 여부가 결정된다.

  • useEffect(() => {...}, [])처음 마운트될 때만 실행됨
  • useEffect(() => {...}, [value])value가 변경될 때마다 실행됨

📝 실행 과정 상세 분석
아래는 useEffect가 언제 실행되고, cleanup이 어떻게 동작하는지 정리한 흐름

✅ 초기 렌더링 (마운트 시)
최초 실행 시, useEffect는 다음 순서로 실행

  1. console.log('[Function] Beginning');
  2. useState(0)value의 초기값이 0
  3. console.log('[Function] End');
  4. useEffect([]) 실행 (마운트 시 한 번 실행됨)
    • [Function] useEffect [] 출력
  5. useEffect([value]) 실행 (value = 0)
    • [Function] useEffect [value] 출력

🔄 버튼 클릭 (value = 1로 변경될 때)
버튼 클릭 시, setValue((state) => state + 1) 실행되면서 리렌더링이 발생
이때 useEffect([value])가 실행되기 전에 기존 effect의 cleanup이 먼저 실행.

  1. 기존 useEffect([value])의 cleanup 실행

    • [Function] useEffect return [value] 출력
  2. 리렌더링 시작

    • console.log('[Function] Beginning'); 실행
    • console.log('[Function] End'); 실행
  3. 새로운 useEffect([value]) 실행 (value = 1)

    • [Function] useEffect [value] 출력

🤔 왜 useEffect([])의 cleanup은 실행되지 않을까?

  • useEffect([])마운트될 때 한 번만 실행되고, 언마운트될 때만 cleanup이 실행됨.
  • value가 변경될 때는 useEffect([])가 다시 실행되지 않으므로 그 cleanup도 실행되지 않음

🔹 최종적으로 정리하면

이벤트실행되는 코드
초기 렌더링[Function] useEffect [][Function] useEffect [value]
버튼 클릭 (value 변경)[Function] useEffect return [value][Function] useEffect [value]
추가적인 value 변경[Function] useEffect return [value][Function] useEffect [value]
언마운트 시[Function] useEffect return [value][Function] useEffect return []
[ClassComponent.js]
import React, { Component } from 'react';

class ClassComponent extends Component {
  state = {
    value: 0
  };

  // 컴포넌트가 생성될 때 가장 먼저 실행
  constructor(props) {
    console.log('[Class] constructor');
    super(props);
    this.state = {
      value: 0
    };
  }

  // 컴포넌트가 렌더링 되기 전에 실행
  // (return 값이 false면 렌더링 중단)
  shouldComponentUpdate(nextProps, nextState) {
    console.log('[Class] shouldComponentUpdate');
    return true;
  }

  // 컴포넌트의 마운트가 끝나면 실행
  componentDidMount() {
    console.log('[Class] componentDidMount');
  }

  // 컴포넌트가 업데이트 된 후 실행
  componentDidUpdate(prevProps, prevState) {
    console.log('[Class] componentDidUpdate');
  }

  // 컴포넌트 언마운트 되기 직전에 실행
  componentWillUnmount() {
    console.log('[Class] componentWillUnmount');
  }

  render() {
    console.log('[Class] render');

    return (
      <div>
        <h1>ClassComponent</h1>
        <h1>value: {this.state.value}</h1>
        <button
          onClick={() => {
            this.setState((state) => ({
              value: state.value + 1
            }));
          }}
        >
          Increase value
        </button>
      </div>
    );
  }
}

export default ClassComponent;
profile
예비 개발자

0개의 댓글