React/공식 문서

이벤트 처리와 State

favor 2026. 4. 2. 15:19

트리로서 UI 이해하기

React 앱은 서로 중첩된 많은 컴포넌트로 구성되어 있음
React와 많은 다른 UI 라이브러리는 UI를 트리로 모델링 함

- 렌더 트리
- 모듈 의존성 트리 

 

 

✔️ 트리로서의 UI

트리 : 요소 사이의 관계 모델

React도 React 앱의 컴포넌트 간의 관계를 관리하고 모델링하기 위해 트리 구조를 사용

 

✔️ 렌더 트리

렌더 트리

  • 관점
    • 누가 누구를 화면에 그리는가? 
  • 대상
    • 오직 리액트 컴포넌트만 나옴
  • 비유
    • 가족 족보

 

컴포넌트의 주요 특징 

  • 다른 컴포넌트의 컴포넌트를 구성하는 것
    • 컴포넌트 중첩 시, 부모 컴포넌트와 자식 컴포넌트의 개념이 생김
    • 각 부모 컴포넌트는 다른 컴포넌트의 자식이 될 수 있음

 

React 앱을 렌더링할 때, 이 관계를 렌더 트리라고 알려진 트리로 모델링할 수 있음 

// App.js

import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';

export default function App() {
  return (
    <>
      <FancyText title text="Get Inspired App" />
      <InspirationGenerator>
        <Copyright year={2004} />
      </InspirationGenerator>
    </>
  );
}

 

React 렌더 트리에서 루트 노드는 앱의 Root 컴포넌트

위의 경우 루트 컴포넌트는 App <-- React가 렌더링하는 첫 번째 컴포넌트 

 

렌더 트리는 React 앱의 단일 렌더링을 나타내지만,

조건부 렌더링을 사용하면 부모 컴포넌트가 전달된 데이터에 따라 다른 자식을 렌더링할 수 있음 

// App.js

import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';

export default function App() {
  return (
    <>
      <FancyText title text="Get Inspired App" />
      <InspirationGenerator>
        <Copyright year={2004} />
      </InspirationGenerator>
    </>
  );
}

위 예시에서, inspiration.type이 무엇이냐에 따라 <FancyText> 또는 <Color>를 렌더링할 수 있음

렌더 트리는 각 렌더링마다 다르게 만들어질 수 있음

 

컴포넌트 구조

  • 최상위 컴포넌트
    • 루트 컴포넌트에 가장 가까운 컴포넌트
    • 그 아래의 모든 컴포넌트의 렌더링 성능에 영향을 미침
    • 가장 복잡성이 높음
  • 리프 컴포넌트
    • 트리의 맨 아래에 위치
    • 자식 컴포넌트가 없음
    • 자주 다시 렌더링 됨

 

✔️ 모듈 의존성 트리 (의존성 트리)

모듈 의존성 트리 

  • 관점
    • 누가 어떤 파일을 import 하는가?
  • 대상
    • 컴포넌트 + 일반 JS + CSS + 이미지 등 모든 파일
  • 비유
    • 도서관 인용 목록 (이 책을 쓰기 위해 참고한 문헌들)

 

모듈 의존성의 트리의 각 노드는 모듈

각 가지는 해당 모듈의 import 문을 나타냄

 

 

의존성 트리에서 루트 노드는 루트 모듈 == 엔트리 포인트 파일 

ㄴ 일반적으로 루트 컴포넌트를 포함하는 모듈 

 

✔️ 렌더 트리 - 모듈 의존성 트리

렌더 트리 - 'UI의 족보'

모듈 의존성 트리 - '파일들의 수입/수출 관계도'

 

렌더 트리와 모듈 의존성 트리의 차이점

  렌더 트리  모듈 의존성 트리
노드 (최소 단위) 컴포넌트 (UI 조각) 모듈 (파일 그 자체)
포함 범위 화면에 그려지는 컴포넌트만 컴포넌트, JS 로직, CSS, 이미지 등 전부
연결 기준 JSX의 부모-자식 관계 import 문의 연결 관계
핵심 질문 "화면에 누가 누구 안에 있는가?" "파일을 읽어오기 위해 누가 누구를 불렀는가?"

 

의존성 트리는 React 앱을 실행하는 데 필요한 모듈을 결정하는 데 유용

프로덕션용으로 빌드할 때, 일반적으로 클라이언트에 제공할 모든 필요 JavaScript를 번들로 묶는 빌드 단계가 있음

ㄴ 해당 작업을 담당하는 도구 = 번들러

--> 번들러는 의존성 트리를 사용하여 포함해야 할 모듈을 결정

 

 

 


이벤트에 응답하기

React에서는 JSX에 이벤트 핸들러를 추가할 수 있음
이벤트 핸들러 : 사용자 상호작용에 따라 유발되는 사용자 정의 함수 
ㄴ ex) 클릭, 마우스 호버, 폼 인풋 포커스 등 

 

✔️ 이벤트 핸들러 추가하기

이벤트 핸들러 추가 방법

  1. 함수 정의
  2. 이를 적절한 JSX 태그에 prop 형태로 전달

 

예제) 사용자가 버튼을 클릭할 경우 메세지를 보여주기 

  1. Button 컴포넌트 내부에 handleClick 함수를 선언
  2. 해당 함수 내부 로직을 구현
  3. <button> JSX에 onClick={handleClick}을 추가 
    prop 형태로 전달 
// App.js

export default function Button() {
  function handleClick() { // 이벤트 핸들러
    alert('You clicked me!');
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

 

이벤트 핸들러 함수의 특징

  • 주로 이벤트가 발생하는 컴포넌트 내부에서 정의
  • handle로 시작하고 그 뒤에 이벤트명을 붙인 함수명을 가짐 

 

이벤트 핸들러를 JSX 내에서 인라인으로 정의할 수 있는 또다른 방법

<button onClick={function handleClick() {
  alert('You clicked me!');
}}>
<button onClick={() => {
  alert('You clicked me!');
}}>

^ 화살표 함수 

 

✔️ 이벤트 핸들러로 전달한 함수들은 호출이 아닌 전달이 되어야 함 

함수를 전달하기 (올바른 예시) 함수를 호출하기 (잘못된 예시)
<button onClick={handleClick}> <button onClick={handleClick()}>

 

  • 첫 번째 예시
    • handleClick 함수는 onClick 이벤트 핸들러에 전달됨
    • React는 이 내용을 기억하고 사용자가 버튼을 클릭하였을 때만 함수를 호출하도록 함
  • 두 번째 예시
    • handleClick() 끝의 ()가 렌더링 과정 중 클릭이 없었음에도 불구하고 즉시 함수를 실행하도록 만듦
    • JSX {} 내의 자바스크립트가 즉시 실행되기 때문 

 

인라인으로 코드를 작성할 때에도 동일한 내용이 다른 형태로 나타남

함수를 전달하기 (올바른 예시) 함수를 호출하기 (잘못된 예시)
<button onClick={() => alert('...')}> <button onClick={alert('...')}>

 

  • 첫 번째 예시
    • 익명 함수
    • 매 렌더링마다 내부 코드를 실행하는 것이 아니라, 함수를 생성하여 추후 이벤트에 의해 호출되게 함
  • 두 번째 예시
    • 클릭 시 실행되는 것이 아니라, 컴포넌트가 렌더링 된 시점에서 실행됨 

 

✔️ 이벤트 핸들러 내에서 Prop 읽기

이벤트 핸들러는 컴포넌트 내부에서 선언됨

--> 해당 컴포넌트의 prop에 접근 가능 

 

// App.js

function AlertButton({ message, children }) {
  return (
    <button onClick={() => alert(message)}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <AlertButton message="Playing!">
        Play Movie
      </AlertButton>
      <AlertButton message="Uploading!">
        Upload Image
      </AlertButton>
    </div>
  );
}

^ message prop(속성)으로 넘겨주어 이벤트 핸들러에서 사용 가능 

 

✔️ 이벤트 핸들러를 Prop으로 전달하기  

위의 경우와 달리, 부모 컴포넌트로 자식의 이벤트 핸들러를 지정하는 경우 

ㄴ 부모 : 이벤트 핸들러 선언, 자식 : 이벤트 핸들러 사용

 

// App.js

// onClick이라는 속성을 받아 <button> 태그의 onClick 속성에 연결해주고, 
// children을 받아 버튼 내부의 텍스트를 설정하는 역할 
function Button({ onClick, children }) { 
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

function PlayButton({ movieName }) {
  // 이벤트 핸들러 따로 생성 
  function handlePlayClick() {
    alert(`Playing ${movieName}!`);
  }

  return (
    <Button onClick={handlePlayClick}>
      Play "{movieName}"
    </Button>
  );
}

function UploadButton() {
  // 이벤트 핸들러 따로 생성하지 않고 익명 함수 사용
  return (
    <Button onClick={() => alert('Uploading!')}>
      Upload Image
    </Button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <PlayButton movieName="Kiki's Delivery Service" />
      <UploadButton />
    </div>
  );
}

 

✔️ 이벤트 핸들러 Prop 명명하기

  • 빌트인 컴포넌트
    • HTML에 원래 있는 태그
    • ex) <button> <div> <span> <input>
    • onClick, onMouseEnter과 같은 브라우저 이벤트 이름만을 지원 
    • <div onSmash={...}> ← 사용 불가능 
  • 커스텀 컴포넌트 (사용자 정의 컴포넌트)
    • 이벤트 핸들러 prop의 이름을 원하는 대로 명명 가능 
    • 관습적으로 on으로 시작하여 대문자 영문으로 이어짐 
    • <Button onSmash={...}> ← 사용 가능 

 

// App.js

function Button({ onSmash, children }) {
  return (
    // <button> 태그는 onClick prop을 필요로 함 
    <button onClick={onSmash}> 
      {children}
    </button>
  );
}

export default function App() {
  return (
    // <Button> 컴포넌트는 원하는 대로 명명 가능
    <div>
      <Button onSmash={() => alert('Playing!')}> 
        Play Movie
      </Button>
      <Button onSmash={() => alert('Uploading!')}>
        Upload Image
      </Button>
    </div>
  );
}

 

✔️ 이벤트 전파

이벤트 핸들러는 해당 컴포넌트가 가진 어떤 자식 컴포넌트의 이벤트를 수신할 수도 있음 

= 이벤트가 트리를 따라 'bubble' 되거나 '전파된다'고 표현 

이벤트는 발생한 지점에서 시작하여 트리를 따라 위로 전달됨 

 

// App.js

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('You clicked on the toolbar!');
    }}>
      <button onClick={() => alert('Playing!')}>
        Play Movie
      </button>
      <button onClick={() => alert('Uploading!')}>
        Upload Image
      </button>
    </div>
  );
}

^ 둘 중의 어느 버튼을 클릭하든지 해당 버튼의 onClick이 먼저 실행된 후, 부모인 <div>의 onClick이 뒤이어 실행됨

 

✔️ 전파 멈추기

이벤트 핸들러는 이벤트 오브젝트(객체)를 유일한 매개변수로 받음 

event를 의미하는 e로 호출되는 것이 일반적

ㄴ 이 오브젝트를 이벤트의 정보를 읽어들이는 데 사용할 수 있음

 

이벤트 오브젝트는 전파를 멈출 수 있게 해줌

이벤트가 부모 컴포넌트에 닿지 못하도록 막으려면 e.stopPropagation()를 호출 

// App.js

function Button({ onClick, children }) {
  return (
    <button onClick={e => {
      e.stopPropagation();
      onClick();
    }}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('You clicked on the toolbar!');
    }}>
      <Button onClick={() => alert('Playing!')}>
        Play Movie
      </Button>
      <Button onClick={() => alert('Uploading!')}>
        Upload Image
      </Button>
    </div>
  );
}

 

버튼 클릭 시 진행되는 절차

  1. React가 <button>에 전달된 onClick 핸들러를 호출 
  2. Button에 정의된 해당 핸들러는 다음을 수행 
    • e.stopPropagation()을 호출하여 이벤트가 더이상 bubbling 되지 않도록 방지
    • Toolbar 컴포넌트가 전달해 준 onClick 함수를 호출 
  3. Toolbar 컴포넌트에서 정의된 위 함수가 버튼의 alert를 표시 
  4. 전파가 중단되었으므로 부모인 <div>의 onClick은 실행되지 않음 

 

✔️ 전파의 대안으로 핸들러를 전달하기

  • 전파
    • 자동 시스템
  • 전파의 대안 
    • 수동 시스템 
    • 이벤트가 자동으로 부모에게 전파되게 하는 것을 막고, 부모가 준 함수를 직접 호출 
    • 코드를 읽기 쉽고, 관리가 편해짐 

 

function Button({ onClick, children }) {
  return (
    <button onClick={e => {
      <!-- 1. 전파 중단 -->
      e.stopPropagation(); 
      
      <!-- 2. 이 사이에 다른 코드를 넣어서 부모 함수 실행 전 추가 동작 가능 -->
      
      <!-- 3. 부모 함수 수동 실행 -->
      onClick(); 
    }}>
      {children}
    </button>
  );
}

 

✔️ 기본 동작 방지하기

일부 브라우저 이벤트는 그와 관련된 기본 브라우저 동작을 가짐 

ex) <form>의 제출 이벤트 - 내부 버튼 클릭 시 페이지 전체를 reload

 

기본 동작이 발생하지 않도록 막기 위해 e.preventDefault()를 이벤트 오브젝트에서 호출 가능

// App.js

export default function Signup() {
  return (
    <form onSubmit={e => {
      e.preventDefault();
      alert('Submitting!');
    }}>
      <input />
      <button>Send</button>
    </form>
  );
}

 

* e.stopPropagation() - e.preventDefault()

  • e.stopPropagation()
    • 이벤트 핸들러가 상위 태그에서 실행되지 않도록 멈춤
  • e.preventDefault()
    • 기본 브라우저 동작을 가진 일부 이벤트가 해당 기본 동작을 실행하지 않도록 방지 

 

 

 


State: 컴포넌트의 기억 저장소

컴포넌트는 현재 입력값, 현재 이미지, 장바구니와 같은 것들을 '기억'해야 함
React는 이런 종류의 컴포넌트별 메모리를 state라고 부름

 

✔️ 일반 변수로 충분하지 않은 경우

// App.js

import { sculptureList } from './data.js';

export default function Gallery() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i>
        by {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} of {sculptureList.length})
      </h3>
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

^ Next 버튼을 클릭하면 index가 증가하고 다음 조각상을 표시해야 하지만, 해당 코드는 제대로 동작하지 않음

 

handleClick 이벤트 핸들러는 지역 변수 index를 업데이트하고 있지만 이러한 변화가 보이지 않는 이유

  1. 지역 변수는 렌더링 간에 유지되지 않음
    • React는 이 컴포넌트는 첫 번째 렌더링 이후에 렌더링을 할 때 모든 것을 초기화  
  2. 지역 변수를 변경해도 렌더링을 일으키지 않음 
    • React는 새로운 데이터로 컴포넌트를 다시 렌더링해야 한다는 것을 인식하지 못함 

 

컴포넌트를 새로운 데이터로 업데이트하기 위해서 필요한 것

  1. 렌더링 사이에 데이터를 유지
  2. React가 새로운 데이터로 컴포넌트를 렌더링하도록 유발 

 

useState 훅이 제공하는 것

  1. 렌더링 간에 데이터를 유지하기 위한 state 변수
  2. 변수를 업데이트하고 React가 컴포넌트를 다시 렌더링하도록 유발하는 state setter 함수

 

✔️ state 변수 추가하기

state 변수를 추가하기 위해서는 import 필요

import { useState } from 'react';

 

 

const [index, setIndex] = useState(0);

^ index는 state 변수, setIndex는 setter 함수 

여기서 [문법을 배열 구조 분해라고 하며, 배열로부터 값을 읽을 수 있게 해줌 

 

* useState()

해당 함수를 호출하면 무조건 두 개가 들어있는 배열을 반환해줌

  • 첫 번째 칸
    • 현재 보관 중인 값 = state
  • 두 번째 칸
    • 그 값을 수정할 수 있는 리모컨 = setter 함수 

 

// App.js

// useState import
import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0); // 0은 index의 초기값

  function handleClick() {
    setIndex(index + 1);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i>
        by {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} of {sculptureList.length})
      </h3>
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

 

✔️ 첫 번째 훅 만나기

React에서 useState와 같이 use로 시작하는 다른 모든 함수를 이라고 함

 

훅 : React가 렌더링 중일 때만 사용할 수 있는 특별한 함수

ㄴ 이를 통해 다양한 React 기능을 연결할 수 있음 

 

훅(use로 시작하는 함수들)은 컴포넌트의 최상위 수준 또는 커스텀 훅에서만 호출 가능

ㄴ 조건문 내에서 선언할 수 X

 

✔️ useState 해부하기

useState 호출 == React에 이 컴포넌트가 무언가를 기억하기를 원한다고 말하는 것

 

const [index, setIndex] = useState(0);

^ React가 index를 기억하기를 원함

 

함수 작동 방식

  1. 컴포넌트가 처음 렌더링
    • index의 초기값으로 useState를 사용해 0을 전달 --> [0, setIndex]를 반환
    • React는 0을 최신 state 값으로 기억 
  2. state를 업데이트
    • 사용자가 버튼을 클릭하면 setIndex(index + 1)를 호출 
      • index가 0이었으므로 setIndex(1)이 됨 
    • setIndex(1)은 React에 index는 1임을 기억하게 하고 또다른 렌더링을 유발 
  3. 컴포넌트가 두 번째로 렌더링
    • React는 여전히 useState(0)를 보지만, index를 1로 설정한 것을 기억
    • --> [1, setIndex]를 반환 
  4. 위 과정 반복 

 

* useState 호출 시 이름 규칙

const [something, setSomething]과 같이 지정하는 것이 규칙  

 

✔️ 컴포넌트에 여러 state 변수 지정하기

하나의 컴포넌트에 원하는 만큼 많은 타입의 state 변수를 가질 수 있음

 

// App.js

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0); // 첫 번째 state 변수 - 숫자 타입
  const [showMore, setShowMore] = useState(false); // 두 번째 state 변수 - boolean 타입

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i>
        by {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
    </>
  );
}

 

✔️ state는 격리되고 비공개로 유지

state는 화면에서 컴포넌트 인스턴스에 지역적

== 동일한 컴포넌트를 두 번 렌더링한다면 각 복사본은 완전히 격리된 state를 가짐 

ㄴ 그중 하나를 변경해도 다른 하나에 영향을 미치지 X

'React > 공식 문서' 카테고리의 다른 글

useReducer  (0) 2026.05.21
상태 구조 설계와 공유  (0) 2026.05.07
불변 업데이트 패턴  (0) 2026.04.28
렌더링과 State 업데이트  (0) 2026.04.08
Props와 리스트 렌더링  (0) 2026.03.25