모던 리액트 Deep Dive - 컴포넌트

2025. 10. 7. 22:45·Study/모던 리액트 Deep Dive

⚡리액트 핵심 요소 깊게 살펴보기 - 2.3 클래스형 컴포넌트와 함수형 컴포넌트

2.3.1 클래스형 컴포넌트 

  • 클래스형 컴포넌트를 생성하기 위해서는 클래스를 선언하고 extends로 만들고 싶은 컴포넌트를 extends해야함

💡extends 구문에 넣을 수 있는 클래스

  • React.Component
  • React.PureComponent

※ 위의 클래스의 차이점은 shouldComponentUpdate를 사용함에 있음

🌕 컴포넌트를 생성할때 주로 쓰이는 것 : props, state

import React from 'react'

// props 타입을 선언한다.
interface SampleProps {
    required?: boolean
    text: string
}

// state 타입을 선언한다.
interface SampleState {
    count: number
    isLimited?: boolean
}

// Component에 제네릭으로 props, state를 순서대로 넣어준다.
class SampleComponent extends React.Component<SampleProps, SampleState> {
    // constructor에서 props를 넘겨주고, state의 기본값을 설정한다.
    private constructor(props: SampleProps) {
        super(props)
        this.state = {
            count: 0,
            isLimited: false
        }
    }   

    // render 내부에서 쓰일 함수를 선언한다.
    private handleClick = () => {
        const newValue = this.state.count + 1
        this.setState({
            count: newValue,
            isLimited: newValue >= 10  
        })
    }   

    // render에서 이 컴포넌트가 렌더링할 내용을 정의한다.
    public render() {
        //  props와 state값을 this, 즉 해당 클래스에서 꺼낸다.
        const {
            props: { required, text },
            state: { count, isLimited },
        } = this

        return (
            <h2>
                Sample Component
                <div>{required ? '필수' : '필수아님'}</div>
                <div>{text}</div>   
                <div>{count}</div>
                <button onClick={this.handleClick} disabled={isLimited}>
                    증가
                </button>
            </h2>
        )
    }
}

🌕 코드 분석

🌓 constructor ()

  • 컴포넌트 내부에 해당 생성자 함수가 있으면 컴포넌트가 초기화되는 시점에 호출됨
  • 여기서는 컴포넌트의 state를 초기화할 수 있음
  • 여기에 선언되어 있는 super()는 컴포넌트를 만들면서 상속받은 상위 컴포넌트로 즉, React.Component의 생성자 함수를 먼저 호출하여 필요한 상위 컴포넌트에 접근할 수 있게 도와줌

📂 constructor가 없어도 state를 초기화할 수 있지 않나?

: 가능은 하나, ES2022에서 등장한 문법이므로 ES2022환경을 지원하는 브라우저에서만 가능

import {Component} from 'react'

class SampleComponent2 extends Component {
	state = {
    	count: 1,
    }
    
    render() {
      const {
        state: { count },
      } = this
      
      return <div>{count}</div>
    }
}

🌓 props()

  • 함수에 인수를 넣는 것과 비슷하게, 컴포넌트에 특정 속성을 전달하는 용도로 사용됨

🌓 state()

  • 클래스형 컴포넌트 내부에서 관리하는 값을 의미하며, 해당 값은 항상 객체여야 함
  • 해당 값이 변화가 있을 때마다 리렌더링이 발생됨

🌓 메서드 

  • 렌더링 함수 내부에서 사용되는 함수이며, 보통 DOM에서 발생하는 이벤트와 함께 사용됨
  • 메서드를 만드는 방식은 크게 3가지로 나뉨

1. constructor에서 this 바인드를 하는 방법

  • this가 undefined로 나오는 현상을 겪게 되며, 해당 현상이 발생하는 이유는 생성자가 아닌 일반 함수로 호출하게 되면 this에 전역 객체(strict모드에서는 undefined)가 바인딩되기 때문임
  • 따라서 생성된 함수에 bind를 활용해 강제로 this를 바인딩해야 함

🔗일반 함수로 선언된 메서드에서 this 바인딩을 사용한 예제

import { Component } from "react";

// 빈 Props를 선언
type Props = Record<String, never>

interface State {
    count: number
}

class SampleComponent extends Component<Props, State> {
    private constructor(props: Props) {
        super(props)
        this.state = {
            count: 1
        }
        // handleClick의 this를 현재 클래스로 바인딩한다.
        this.handleClick = this.handleClick.bind(this)
    }

    private handleClick() {
        this.setState((prev) => ({ count: prev.count + 1 }))
    }

    public render() {
        const {
            state: { count },
        } = this    

        return (
            <div>
                <button onClick={this.handleClick}>증가</button>
                {count}
            </div>
        )
    }
}

2. 화살표 함수를 쓰는 방법

  • this가 상위 스코프로 결정되는 화살표 함수를 사용한다면 굳이 바인딩하지 않더라도 사용가능함

3. 렌더링 함수 내부에서 새롭게 만들어 전달하는 방법

  • 메서드 내부에서 새롭게 함수를 만들어서 전달하는 방법(최적화 수행이 어렵기에 지양하는 방법)
<button onClick={() => this.handleClick()}>증가</button>

🌚클래스형 컴포넌트의 생명주기 메서드

생명주기 메서드가 실행되는 시점 

  • 마운트(mount) : 컴포넌트가 마운팅(생성)되는 시점
  • 업데이트(update) : 이미 생성된 컴포넌트의 내용이 변경되는 시점
  • 언마운트(unmount) : 컴포넌트가 더이상 존재하지 않는 시점 

🌕생명주기 메서드

🌓render()

  • 리액트 클래스형 컴포넌트의 유일한 필수 값으로 항상 사용됨
  • 컴포넌트가 UI를 렌더링하기 위해서 사용됨
  • 마운트와 업데이트 과정에서 렌더링이 일어남
  • 주의해야할 점은 항상 순수해야하며 부수효과가 없어야 함(no side-effects) 즉, 같은 입력값(props or state)이 들어가면 항상 같은 결과물을 반환해야함
  • render()내부에서 state를 직접 업데이트하는 this.setState를 호출하면 안되며, state를 변경하는 일은 클래스형 컴포넌트의 메서드나 다른 생명주기 메서드 내부에서 발생해야 함
  • 최대한 간결하고 깔끔하게 작성하는 것이 좋으며, 함수 내의 부수 효과를 만들지 않고, 유지보수가 가능하게 도와줌

🌓componentDidMound()

  • 클래스형 컴포넌트가 마운트되고 준비되었다면, 그 다음 실행됨
  • 이 함수 내부에서는 this.setState()로 state의 값을 변경하는 것이 가능함
  • this.setState 호출 했다면 state가 변경되고, 즉시 다시 렌더링을 시도하는데 이 작업은 브라우저가 UI를 업데이트하기 전에 실행되어 사용자가 변경되는 것을 눈치챌수 없게 만듬
  • 해당 메서드는 만능이 아니며 성능에 문제를 일으킬 수 있음
  • state를 다루는 것은 생성자에서 하는 것이 좋음
  • API 호출 후 업데이트, DOM에 의존적인 작업(이벤트 리스너 추가) 등을 하기 위해서 componentDidMount에서 할수 밖에 없는 작업인지 확인해보고 사용하는 것이 좋음

🌓componentDidUpdate()

  • 컴포넌트 업데이트가 일어난 이후 바로 실행됨
  • state나 props의 변화에 따라 DOM을 업데이트하는 등에 쓰임
  • this.setState에서 사용가능하나, 적절한 조건문으로 감싸지 않는다면 계속해서 호출되는 일이 발생할 수도 있음
componentDidUpdate(prevProps: Props, prevState: State) {
  // 이 조건문이 없다면 매 순간 fetchDate가 실행될 것이다.
  if(this.props.userName !== prevProps.userName) {
    this.fetchData(this.props.userName);
  }
}

🌓componentWillUnmount()

  • 컴포넌트가 언마운트되거나 더이상 사용되지 않기 직전에 호출됨
  • 메모리 누수나 불필요한 작동을 막기위한 클린 업 함수를 호출하기 위한 최적의 위치
  • this.setState를 호출할 수 없으며, 이벤트를 지우거나 API호출을 취소하거나 serInterval.setTimeout으로 생성된 타이머를 지우는 등 작업에 유용함
componentWillUnmount() {
  window.removeEventListener('resize', this.resizeListener)
  clearInterval(this.intervalId)
}

🌓shouldComponentUpdate()

  • state나 props의 변경으로 리액트 컴포넌트가 다시 리렌더링되는 것을 막고싶을때 사용
  • this.setState가 호출되면 컴포넌트는 리렌더링을 일으키지만, 컴포넌트에 영향을 받지 않는 화면에 대해 정의할 수 있음
shouldComponentUpdate(nextProps: Props, nextState: State) {
  // true인 경우, 즉 props의 title이 같지 않거나 state의 input이 같지 않은 경우 
  // 컴포넌트를 업데인트 한다. 이외 경우에는 업데이트하지 않는다.
  return this.props.title !== nextProps.title || this.state.input !== nextState.input
}

shouldComponentUpdate(nextProps, nextState) {
  // true → 다시 렌더링
  // false → 렌더링 생략
}

💊React.Component vs React.PureComponent 차이

렌더링 조건 setState()나 props가 변경되면 무조건 렌더링됨 이전 state와 props를 얕은 비교(shallow comparison) 해서, 값이 변하지 않았으면 렌더링을 생략함
성능 최적화 기본 자동으로 shouldComponentUpdate를 이용한 최적화 내장
shouldComponentUpdate() 직접 구현해야 함 내부적으로 자동 구현되어 있음

👉 React.Component는 기본 구현이 없음즉, setState()가 호출되면 무조건 다시 렌더링됨.
👉 React.PureComponent는 자동 구현이 있음 즉, props나 state가 얕은 비교에서 달라졌을 때만 렌더링함.

※ PureComponent는 얕은 비교만 하기 때문에,객체나 배열이 참조만 바뀌어도 렌더링되고 내부 값이 바뀌었는데 참조가 같으면 변화를 감지하지 못함 > 새배열로 만들어야함

// ❌ PureComponent에서는 변경 감지 안됨
this.state.list.push(4); // 같은 배열 참조 유지
this.setState({ list: this.state.list }); 

//새배열 생성
this.setState({ list: [...this.state.list, 4] });

🌓static getDerivedStateFromProps()

  • 사라진 componentWillReceiveProps를 대체하는 메서드
  • render() 직전에 호출되며, static으로 선언되어 있어 this에 접근할 수 없음
  • 여기서 반환하는 객체는 해당 객체의 내용이 state로 들어가게 되며 null을 반환 시 아무일도 일어나지 않음
  • 주의해야 할 점은 모든 render()실행시 호출된다는 점
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
  // 다음에 올 props를 바탕으로 현재의 state를 변경하고 싶을 때 사용
  if(props.name !== state.name) {
    return {
      name: props.name
    }
  }
  // state에 영향을 미치지 않는다.
  return null
}

🌓getSnapShopBeforeUpdate()

  • componentWillUpdate()를 대체하는 메서드
  • DOM이 업데이트 되기 직전에 호출되며, 반환되는 값은 componentDidUpdate로 전달됨
  • DOM에 렌더링되기 전에 윈도우 크기를 조절하거나 스크롤 위치를 조정하는 등의 작업을 처리하는 데 유용함
getSnapshopBeforeUpdate(prevProps: Props, prevState: State) {
   if(prevProps.list.length < this.props.list.length) {
     const list = this.listRef.current;
     return list.scrollHeight - list.scrollTop;
   }
   return null;
}

// 3번째 인수인 snapshot은 클래스 제네릭의 3번재 인수로 넣어줄 수 있다.
componentDidUpdate(prevProps: Props, prevState: State, snapshot: Snapshot) {
  // getSnapshopBeforeUpdate로 넘겨받은 값은 snapshot에서 접근이 가능하다.
  if(snapshot !== null) {
    const list = this.listRef.current;
    list.scrollTop = list.scrollHeight - snapshot;
  }
}

💡리액트 생명주기 다이어그램

🌓getDerivedStateFromError()

  • 정상적인 생명주기에서 실행되는 메서드가 아닌 에러 상황에서 실행되는 메서드임
  • 자식 컴포넌트에서 에러가 발생했을 때 호출되는 에러 메서드이며, 해당 메서드를 사용하면 적절한 에러 처리 로직을 구현할 수 있음

🌓componentDidCatch()

  • 자식 컴포넌트에서 에러가 발생했을 때 실행되며, getDerivedStateFromError()에서 에러를 잡고 state를 결정한 이후에 실행됨
  • componentDidCatch의 두가지 인수
    • getDerivedStateFromError와 동일한 error
    • 정확히 어떤 컴포넌트에 에러를 발생시켰는지 정보를 가지고 있는 info
  • 에러 시 로그 등 부수 효과 처리 좋음

🔗ErrorBoundary(에러 경계)
→ 렌더링 중, 생명주기 메서드 중, 또는 자식 컴포넌트의 constructor에서 발생한 자바스크립트 에러를 잡아서, 앱 전체가 죽지 않게 대체 UI를 보여주는 컴포넌트.

💡예시 코드

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  // ① 에러 발생 시 state 변경
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  // ② 에러 로깅 등 부수 효과 처리
  componentDidCatch(error, info) {
    console.error('Error caught:', error, info);
    // 예: Sentry 같은 외부 서비스에 전송
  }

  render() {
    if (this.state.hasError) {
      return <h2>문제가 발생했습니다 😢</h2>;
    }
    return this.props.children;
  }
}

🌕클래스형 컴포넌트의 한계

  • 데이터의 흐름을 추적하기 어렵다
  • 애플리케이션 내부 로직의 재사용이 어렵다
  • 기능이 많아질수록 컴포넌트의 크기가 커진다
  • 클래스는 함수에 비해 상대적으로 어렵다
  • 코드 크기를 최적화 하기 어렵다
  • 핫 리로딩을 하는데 상대적으로 불리하다

2.3.2 함수형 컴포넌트

💡 함수형 컴포넌트의 예제

import { useState } from "react";   

type sampleProps = {
    required?: boolean
    text: string
}

export function SampleComponent({ required, text }: sampleProps) {
    const [count, setCount] = useState<number>(0)
    const [isLimited, setIsLimited] = useState<boolean>(false)

    function handleClick() {
        const newValue = count + 1
        setCount(newValue)
        setIsLimited(newValue >= 10)
    }

    return (
        <h2>
            Sample Component
            <div>{required ? '필수' : '필수아님'}</div>
            <div>문자: {text}</div>
            <div>count: {count}</div>
            <button onClick={handleClick} disabled={isLimited}>
                증가
            </button>   
        </h2>
    )
}

2.3.3 함수형 컴포넌트 vs 클래스형 컴포넌트

🌚생명주기 메서드의 부재

클래스형 컴포넌트의 생명주기 메서드가 함수형 컴포넌트에서 존재하지 않음

: 이유는 함수형 컴포넌트는 props를 받아 단순히 리액트 요소만 반환하는 함수이지만, 클래스형 컴포넌트는 render 메서드가 있는 React.Component를 상속받아 구현하는 자바스크립트 클래스이기 때문!

즉, 생명주기 메서드는 React.Component에서 오는 것이기때문에 클래스형 컴포넌트가 아닌 이상 생명주기 메서드를 더이상 사용할 수 없음

함수형 컴포넌트는 useEffect 훅을 사용해 생명주기 메서드인 componentDidMount, componentDidUpdate, componentWillUnmount를 비슷하게 구현할 수 있음 (useEffect는 생명주기를 위한 훅이 아님)

🌚함수형 컴포넌트와 렌더링된 값

  • 함수형 컴포넌트는 렌더링된 값을 고정하고, 클래스형 컴포넌트는 고정하지 못함
import React from "react";

interface Props {
    user: string
}

// 함수 컴포넌트로 구현한 setTimeount 예제
export function FunctionalComponent(props: Props) {
    const showMessage = () => {
        alert('Hello ' + props.user)
    }

    const handleClick = () => {
        setTimeout(showMessage, 3000)
    }

    return <button onClick={handleClick}>Follow</button>
}

// 클래스 컴포넌트로 구현한 setTimeout 예제
export class ClassComponent extends React.Component<Props, {}> {
    private showMessage = () => {
        alert('Hello ' + this.props.user)
    }

    private handleClick = () => {
        setTimeout(this.showMessage, 3000)
    }

    public render() {
        return <button onClick={this.handleClick}>Follow</button>
    }
}

 

더보기

여기서 FunctionalComponent와 ClassComponent는 같은 작업을 하고 있음
handleClick을 클릭하고 3초 사이에 props를 변경하면 어떻게 될까?

classComponent의 경우

3초 뒤에 변경된 props를 기준으로 메세지가 뜨고, FunctionalComponent는 클릭했던 시점의 props 값을 기준으로 메시지가 뜸

클래스 컴포넌트는 props의 값을 항상 this로 가져오고, props는 외부에서 변경되지 않는 이상 불변 값이지만 this가 가리키는 객체

즉, 컴포넌트의 인스턴스의 멤버는 변경가는한 값임

render 메서드를 비롯해 리액트의 생명주기 메서드가 변경된 값을 읽을 수 있음

💡함수형 컴포넌트

  • 렌더링일이 일어날때마다 그 순간의 값인 props와 state를 기준으로 렌더링됨
  • props와 state가 변경된다면, 그값을 기준으로 함수가 호출된다고 볼 수있음

💡클래스형 컴포넌트

  • 시간의 흐름에 따라 변화하는 this기준으로 렌더링이 일어남

'Study > 모던 리액트 Deep Dive' 카테고리의 다른 글

모던 리액트 Deep Dive - 메모이제이션  (0) 2025.10.14
모던 리액트 Deep Dive - 렌더링  (0) 2025.10.14
모던 리액트 Deep Dive - 가상 DOM과 리액트 파이버  (0) 2025.09.30
모던 리액트 Deep Dive - JSX  (0) 2025.09.30
모던 리액트 Deep Dive - 타입 스크립트  (1) 2025.09.23
'Study/모던 리액트 Deep Dive' 카테고리의 다른 글
  • 모던 리액트 Deep Dive - 메모이제이션
  • 모던 리액트 Deep Dive - 렌더링
  • 모던 리액트 Deep Dive - 가상 DOM과 리액트 파이버
  • 모던 리액트 Deep Dive - JSX
happy_dev
happy_dev
복사하고 붙여넣기 잘하고 싶어요
  • happy_dev
    happy의 개발일지
    happy_dev
  • 전체
    오늘
    어제
    • 전체 (43)
      • Java (0)
      • React (1)
      • DB (1)
      • Study (41)
        • 친절한 SQL 튜닝 (9)
        • 모던 리액트 Deep Dive (18)
        • 젠킨스로 배우는 CI,CD 파이프라인 구축 (14)
        • Studyd (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Jenkins
    oracle
    SQL
    인덱스의기본
    리액트
    젠킨스
    DB
    toad
    조인
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
happy_dev
모던 리액트 Deep Dive - 컴포넌트
상단으로

티스토리툴바