React - 3

Dear·2025년 4월 22일

TIL

목록 보기
11/74

💙React 이벤트

React 이벤트 시스템은 Synthetic Event(합성 이벤트) 라는 것을 사용
-> 브라우저 간의 호환성을 위해 만들어진 일종의 래퍼

// HTML 에서의 이벤트
// 문자열, 브라우저 고유 이벤트
<button onclick="handleClick()">클릭</button>

// React 에서의 이벤트
// 함수 참조, SyntheticEvent (React가 만든 이벤트)
<button onClick={handleClick}>클릭</button>

// 버튼 클릭 이벤트
function ClickExample() {
  const handleClick = () => {
    alert('버튼이 클릭됨!');
  };

  return (
    <button onClick={handleClick}>클릭하세요</button>
  );
}

// 이벤트 객체 사용
// React 이벤트 핸들러가 이벤트 객체를 자동으로 넘겨줌
function InputExample() {
  const handleChange = (event) => {
    console.log(event.target.value); // 입력값 출력
  };

  return (
    <input type="text" onChange={handleChange} />
  );
}

💙React 생명주기

컴포넌트가 생성, 사용, 소멸될 때 까지의 과정이다.

마운트(Mount)

DOM이 생성되고 브라우저에 나타나는 시점이다.
컴포넌트의 생서자(Constructor)가 실행된다.

업데이트(Update)

컴포넌트가 여러 번 렌더링 되는 것이다.
컴포넌트의 props가 변경되거나 setState() 함수 호출에 의해 state가 변경되거나, forceUpdate()로 강제 업데이트 함수 호출로 인해 컴포넌트가 렌더링될 때, 부모 컴포넌트가 새로 리렌더링 한다.

언마운트(Unmount)

컴포넌트를 DOM에서 제거하는 것
상위 컴포넌트에서 현재 컴포넌트를 더 이상 화면에 표시하지 않게 될 때 언마운트 된다.

💙생명주기 메소드

render()

클래스형 컴포넌트에서 필수로 정의해야 하는 메서드
JSX를 반환하며, 그 JSX가 사용자 화면(UI)에 출력된다.
컴포넌트의 state나 props가 변경되면, React는 render()를 다시 호출하여 화면을 업데이트한다.

import React, { Component } from 'react';

class Hello extends Component {
  // render() 안에서 return되는 JSX가 실제로 브라우저에 그려진다
  // this.props, this.state 등을 통해 동적으로 내용을 바꿀 수 있다
  render() {
    return <h1>안녕하세요, {this.props.name}!</h1>;
  }
}

constructor(props)

클래스의 생성자 함수이다.
React 컴포넌트에서 초기 설정을 하며 this.state를 초기화하는 데 자주 사용된다.
super(props)를 반드시 호출해야 this.props를 사용할 수 있다.

import React, { Component } from 'react';

class MyComponent extends Component {
  constructor(props) {
    super(props); // 부모 클래스의 생성자 호출 (반드시 필요)

    // 상태값(state) 초기값 설정
    this.state = {
      name: 'React'
    };

    // this.handleClick = this.handleClick.bind(this); // 메서드 바인딩 (필요한 경우)
  }

  render() {
    return <h1>안녕하세요, {this.state.name}</h1>;
  }
}

getDerivedStateFromProps

React 클래스형 컴포넌트의 정적(static) 생명주기 메서드 중 하나로, props의 변경에 따라 state를 동기화하고 싶을 때 사용한다.

props가 변경되었을 때 자동 호출된다.(컴포넌트 생성 시 + 업데이트 시)
리턴 값으로 새로운 state 객체를 반환하거나 null을 반환해서 state를 그대로 둘 수 있다.

static getDerivedStateFromProps(nextProps, prevState) {
  // props 또는 state를 기반으로 새로운 state를 반환하거나 null
  return null; // 혹은 { someState: updatedValue }
}

// prop를 통해 state 업데이트
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      message: ''
    };
  }

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      message: ''
    };
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.greeting !== prevState.message) {
      return {
        message: nextProps.greeting
      };
    }
    return null; // state를 변경할 필요 없을 때
  }

  render() {
    return <p>{this.state.message}</p>;
  }
}

props로 받은 값에 따라 내부 상태(state)를 동적으로 업데이트해야 할 때 사용한다.
ex) 외부에서 새로운 값을 props로 넘겨줄 때 컴포넌트 내 표시 값도 갱신되어야 하는 경우

componentDidMount()

컴포넌트가 화면에 "처음" 렌더링(마운트)된 직후 실행되는 메서드

데이터 가져오기(API 요청), 이벤트 리스너 등록, 서드파티 라이브러리 초기화 등에 적합하다.

class MyComponent extends React.Component {
  componentDidMount() {
    // 여기서 API 호출, 타이머 설정, DOM 접근 등 가능
    console.log('컴포넌트가 화면에 나타났습니다!');
  }

  render() {
    return <div>Hello, world!</div>;
  }
}

// 데이터 불러오기
// componentDidMount() 내부에서 fetch를 실행해서 데이터를 가져오고,
// 데이터가 도착하면 this.setState()로 업데이트 → 자동으로 render()가 다시 실행
class UserList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      users: []
    };
  }

  componentDidMount() {
    // 마운트 후 API 호출
    fetch('https://jsonplaceholder.typicode.com/users')
      .then((res) => res.json())
      .then((data) => this.setState({ users: data }));
  }

  render() {
    return (
      <ul>
        {this.state.users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    );
  }
}

shouldComponentUpdate(nextProps, nextState)

컴포넌트가 리렌더링 되기 전에 "렌더링을 할지 말지"를 결정할 수 있게 해주는 성능 최적화용 메서드 (불필요한 렌더링을 방지-> 성능 최적화)
props나 state가 변경되었을 때, render()를 다시 호출할지 말지 결정하는 함수

shouldComponentUpdate(nextProps, nextState) {
  // 기본값은 항상 true
  return true; // false로 바꾸면 render() 막음
}

// 버튼을 클릭하면 count가 증가하지만, 짝수일 때만 화면이 실제로 다시 렌더링
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('변화 감지:', nextState.count);
    return nextState.count % 2 === 0; // 짝수일 때만 리렌더링
  }

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    console.log('렌더링됨');
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>+1</button>
      </div>
    );
  }
}

getSnapshotBeforeUpdate()

DOM이 변경되기 직전에 실행되는 함수

컴포넌트가 업데이트되어 DOM에 반영되기 직전에 호출된다.
여기서 반환한 값은 componentDidUpdate로 전달된다.
-> 스크롤 위치, 포커스, DOM 위치 정보 등을 기록할 때 사용

// 형식 
getSnapshotBeforeUpdate(prevProps, prevState) {
  // DOM 업데이트 전에 실행됨
  // 예: 스크롤 위치 저장
  return snapshot;
}
// 생명주기 메소드
componentDidUpdate(prevProps, prevState, snapshot) {
  // getSnapshotBeforeUpdate가 반환한 snapshot을 여기에 받음
}

// 스크롤 위치 기억하기
class ChatBox extends React.Component {
  constructor(props) {
    super(props);
    this.messagesEndRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 새 메시지가 오기 전에 스크롤이 제일 아래였는지 확인
    const wasScrolledToBottom =
      this.messagesEndRef.current.scrollHeight -
        this.messagesEndRef.current.scrollTop ===
      this.messagesEndRef.current.clientHeight;
    return wasScrolledToBottom;
  }

  componentDidUpdate(prevProps, prevState, wasScrolledToBottom) {
    // 새 메시지 후에도 자동으로 스크롤을 유지
    if (wasScrolledToBottom) {
      this.messagesEndRef.current.scrollTop =
        this.messagesEndRef.current.scrollHeight;
    }
  }

  render() {
    return (
      <div
        ref={this.messagesEndRef}
        style={{ height: '300px', overflowY: 'scroll' }}
      >
        {this.props.messages.map((msg, index) => (
          <p key={index}>{msg}</p>
        ))}
      </div>
    );
  }
}

componentDidUpdate()

컴포넌트가 업데이트되고 나서 호출되는 메서드

props나 state가 변경되어 컴포넌트가 리렌더링된 직후 호출된다.
업데이트 이후의 처리가 필요한 경우 여기에 작성
ex) 서버에 변경사항 전송, 새 props에 따라 추가 작업, getSnapshotBeforeUpdate에서 받은 값 사용 등에 활용

// 이전 props 값, 이전 State 값, getSnapshotBeforeUpdate() 에서 반환된 값
componentDidUpdate(prevProps, prevState, snapshot) {
  // 업데이트 직후 실행됨
}

// 버튼을 클릭하면 count가 바뀌고 → componentDidUpdate()가 실행
// 이전 상태와 현재 상태를 비교하여 변화 감지
class Counter extends React.Component {
  state = { count: 0 };

  componentDidUpdate(prevProps, prevState) {
    // 무한 루프 주의
    if (prevState.count !== this.state.count) {
      console.log(`count가 ${prevState.count}${this.state.count}로 변경됨`);
    }
  }

  render() {
    return (
      <button onClick={() => this.setState({ count: this.state.count + 1 })}>
        {this.state.count}
      </button>
    );
  }
}

componentWillUnmount()

포넌트가 화면에서 제거되기 직전에 호출
주 용도는 정리(clean-up) 작업

ex) 이벤트 리스너 제거, 타이머(clearTimeout, clearInterval), 외부 라이브러리 리소스 해제, 웹소켓 연결 종료 등

componentWillUnmount() {
  // 정리 작업
}

// 타이머 제거 
class Timer extends React.Component {
  state = { seconds: 0 };

  componentDidMount() {
    this.interval = setInterval(() => {
      this.setState((prevState) => ({ seconds: prevState.seconds + 1 }));
    }, 1000);
  }

  // 메모리 누수 방지
  componentWillUnmount() {
    clearInterval(this.interval); // 타이머 제거
    console.log('타이머 제거됨');
  }

  render() {
    return <div>타이머: {this.state.seconds}</div>;
  }
}

// 함수형 컴포넌트 
useEffect(() => {
  // mount 시 실행
  console.log('컴포넌트 마운트됨');

  return () => {
    // unmount 시 실행 (== componentWillUnmount)
    console.log('컴포넌트 언마운트됨');
  };
}, []); // 빈 배열이면 mount/unmount 타이밍에만 실행

componentDidCatch()

React 클래스형 컴포넌트에서 에러를 잡기 위한 생명주기 메서드
컴포넌트 트리에서 발생한 자식 컴포넌트의 JavaScript 오류를 잡고 처리

컴포넌트의 자식에서 렌더링 중, 생성자 중, 혹은 생명주기 메서드 중 오류가 발생했을 때 호출된다.
에러 경계(Error Boundary) 역할을 하며, 앱이 죽지 않게 방어해준다.
-> 오류를 화면에 표시하거나, 로그를 서버로 전송하는 데 사용

// 실제 발생한 에러 객체, 컴포넌트 스택 정보 (componentStack) 등
componentDidCatch(error, info) {
  // error: 발생한 에러 객체
  // info: 오류 발생 위치 등의 정보
}

// 에러 경계 컴포넌트
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    this.setState({ hasError: true });
    console.log('에러 발생:', error);
    console.log('에러 정보:', info.componentStack);
    // 서버로 에러 로깅 가능
  }

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

// 사용
<ErrorBoundary>
  <MyComponent />
</ErrorBoundary>

에러 경계가 잡을 수 있는 오류

✅ 렌더링 중 발생한 오류
✅ 생명주기 메서드 중 발생한 오류
✅ 자식 컴포넌트의 constructor에서 발생한 오류
❌ 이벤트 핸들러 내부 오류 (별도로 try/catch 필요)
❌ 비동기 코드(setTimeout, fetch) 오류

함수형 컴포넌트에는 없음

🤍회고

사용자와의 상호작용을 처리하는 이벤트 시스템과 컴포넌트의 생명주기 흐름에 대해 공부했다. 클래스형 컴포넌트에서 사용하는 생명주기 메서드들이 많아 외우기도 어렵고, 각각의 쓰임새가 혼동되었다.

profile
친애하는 개발자

0개의 댓글